第 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);
}
}