第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
自动关闭阈值
繁忙状态
硬件状态
策略
电源管理入口点
经过一段可配置空闲时间后,系统可以自动关闭(即关闭电源)。该时间段称为自动关闭阈值。缺省情况下,将对在 1995 年 10 月 1 日到 1999 年 7 月 1 日间首次发布的 SPARC 桌面系统启用此行为。有关更多信息,请参见 power.conf(4) 手册页。可以使用 dtpower(1M) 或 power.conf(4) 覆盖自动关闭。
可以采用几种方法度量系统的繁忙状态。当前支持的内置度量标准项包括键盘字符、鼠标活动、tty 字符、平均负荷值、磁盘读取和 NFS 请求。其中任何一项都可使系统处于繁忙状态。除内置度量标准外,还定义了一个接口,用于运行用户指定的可以表明系统处于繁忙状态的进程。
导出 reg 属性的设备被视为具有硬件状态,关闭系统之前,必须保存该硬件状态。没有 reg 属性的设备被视为无状态设备。但是,设备驱动程序可以另外一种方式处理这种情况。
如果驱动程序导出值为 needs-suspend-resume 的 pm-hardware-state 属性,则必须调用具有硬件状态但没有 reg 属性的设备(如 SCSI 驱动程序),才能保存并恢复状态。否则,缺少 reg 属性即表示设备没有硬件状态。有关设备属性的信息,请参见第 4 章。
具有 reg 属性但没有硬件状态的设备可以导出值为 no-suspend-resume 的 pm-hardware-state 属性。将 no-suspend-resume 与 pm-hardware-state 属性配合使用,可防止框架调用驱动程序来保存并恢复该状态。有关电源管理属性的更多信息,请参见 pm-components(9P) 手册页。
dtpower(1M) 或 power.conf(4) 启用了自动关闭。
系统空闲的时间长度已达到自动关闭阈值(分钟)。
满足 power.conf 中指定的所有度量标准。
系统电源管理将命令DDI_SUSPEND 传递给 detach(9E) 驱动程序入口点,以请求驱动程序保存设备硬件状态。系统电源管理将命令 DDI_RESUME 传递给 attach(9E) 驱动程序入口点,以请求驱动程序恢复设备硬件状态。
detach(9E) 的语法如下所示:
int detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
具有 reg 属性或 pm-hardware-state 属性设置为 needs-suspend-resume 的设备必须能够保存设备的硬件状态。框架调用驱动程序的 detach(9E) 入口点使驱动程序保存状态,以便在系统电源重新打开后进行恢复。要处理 DDI_SUSPEND 命令,detach(9E) 必须执行以下任务:
在设备恢复之前,阻止启动进一步操作,但 dump(9E) 请求除外。
一直等到未完成的操作完成为止。如果可以重新启动未完成的操作,则可以中止该操作。
取消待处理的任何超时和回调。
将任何易失的硬件状态保存到内存。该状态包含设备寄存器的内容,此外还可以包含下载的固件。
如果驱动程序无法暂停设备并将其状态保存到内存,则驱动程序必须返回 DDI_FAILURE。然后,框架将中止系统电源管理操作。
在某些情况下,关闭设备电源存在一定风险。例如,如果关闭内含磁带的磁带机电源,则该磁带可能会损坏。在这种情况下,attach(9E) 应执行以下操作:
调用 ddi_removing_power(9F) 以确定 DDI_SUSPEND 命令是否会关闭设备的电源。
确定关闭电源是否会产生问题。
如果上述两种操作的结果都是肯定的,则应拒绝 DDI_SUSPEND 请求。示例 12-6 给出了使用 ddi_removing_power(9F) 检查 DDI_SUSPEND 命令是否会产生问题的 attach(9E) 例程。
必须接受转储请求。框架使用 dump(9E) 入口点写出包含内存内容的状态文件。有关使用该入口点时对设备驱动程序强加的限制,请参见 dump(9E) 手册页。
使用 DDI_SUSPEND 命令调用电源可管理组件的 detach(9E) 入口点时,应保存关闭设备电源时的状态。驱动程序应取消待处理的超时。驱动程序还应禁止对 pm_raise_power(9F) 的任何调用,但 dump(9E) 请求除外。通过使用 DDI_RESUME 命令调用 attach(9E) 来恢复设备时,可以恢复超时以及对 pm_raise_power () 的调用。驱动程序必须掌握其足够的状态信息,才能够正确处理这种可能发生的情况。以下示例显示了实现 DDI_SUSPEND 命令的 detach(9E) 例程。
示例 12-6 实现 DDI_SUSPEND 的 detach(9E) 例程
int xxdetach(dev_info_t *dip, ddi_detach_cmd_t cmd) { struct xxstate *xsp; int instance; instance = ddi_get_instance(dip); xsp = ddi_get_soft_state(statep, instance); switch (cmd) { case DDI_DETACH: /* ... */ case DDI_SUSPEND: /* * We do not allow DDI_SUSPEND if power will be removed and * we have a device that damages tape when power is removed * We do support DDI_SUSPEND for Device Reconfiguration. */ if (ddi_removing_power(dip) && xxdamages_tape(dip)) return (DDI_FAILURE); mutex_enter(&xsp->mu); xsp->xx_suspended = 1; /* stop new operations */ /* * Sleep waiting for all the commands to be completed * * If a callback is outstanding which cannot be cancelled * then either wait for the callback to complete or fail the * suspend request * * This section is only needed if the driver maintains a * running timeout */ if (xsp->xx_timeout_id) { timeout_id_t temp_timeout_id = xsp->xx_timeout_id; xsp->xx_timeout_id = 0; mutex_exit(&xsp->mu); untimeout(temp_timeout_id); mutex_enter(&xsp->mu); } if (!xsp->xx_state_saved) { /* * Save device register contents into * xsp->xx_device_state */ } mutex_exit(&xsp->mu); return (DDI_SUCCESS); default: return (DDI_FAILURE); }
attach(9E) 的语法如下所示:
int attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
恢复系统电源后,每个具有 reg 属性或具有值为 needs-suspend-resume 的 pm-hardware-state 属性的设备都会使用 DDI_RESUME 命令值调用其 attach(9E) 入口点。如果系统关闭被中止,则即使尚未关闭电源,也会调用每个暂停的驱动程序以进行恢复。因此,attach(9E) 中的恢复代码不能对系统是否已实际断电作出任何假设。
电源管理框架认为组件的电源级别在执行 DDI_RESUME 时未知。根据设备性质,驱动程序编写者有两种选择:
如果驱动程序无需打开组件电源即可确定设备组件的实际电源级别(如通过读取寄存器),则驱动程序应通过调用 pm_power_has_changed(9F) 来通知框架每个组件的电源级别。
如果驱动程序无法确定组件的电源级别,则驱动程序应在首次访问各个组件之前,在内部将每个组件标记为未知并调用 pm_raise_power(9F)。
以下示例显示了使用 DDI_RESUME 命令的 attach(9E) 例程。
示例 12-7 实现 DDI_RESUME 的 attach(9E) 例程
int xxattach(devinfo_t *dip, ddi_attach_cmd_t cmd) { struct xxstate *xsp; int instance; instance = ddi_get_instance(dip); xsp = ddi_get_soft_state(statep, instance); switch (cmd) { case DDI_ATTACH: /* ... */ case DDI_RESUME: mutex_enter(&xsp->mu); if (xsp->xx_pm_state_saved) { /* * Restore device register contents from * xsp->xx_device_state */ } /* * This section is optional and only needed if the * driver maintains a running timeout */ xsp->xx_timeout_id = timeout( /* ... */ ); xsp->xx_suspended = 0; /* allow new operations */ cv_broadcast(&xsp->xx_suspend_cv); /* If it is possible to determine in a device-specific * way what the power levels of components are without * powering the components up, * then the following code is recommended */ for (i = 0; i < num_components; i++) { xsp->xx_power_level[i] = xx_get_power_level(dip, i); if (xsp->xx_power_level[i] != XX_LEVEL_UNKNOWN) (void) pm_power_has_changed(dip, i, xsp->xx_power_level[i]); } mutex_exit(&xsp->mu); return(DDI_SUCCESS); default: return(DDI_FAILURE); } }