组件
空闲
电源级别
相关性
策略
设备电源管理接口
电源管理入口点
如果在设备处于空闲状态时可以减少设备能耗,则该设备是电源可管理设备。从概念上讲,电源可管理设备由许多电源可管理硬件单元组成,这些硬件单元称为组件。
设备驱动程序通知系统有关设备组件及其相关电源级别的信息。因此,在驱动程序初始化期间,驱动程序会在其 attach(9E) 入口点中创建 pm-components(9P) 属性。
大多数电源可管理设备仅实现单个组件。例如,磁盘就是一个电源可管理的单组件设备,当磁盘处于空闲状态时,可以停止磁盘主轴马达以节省电能。
如果一个设备具有多个可单独控制的电源可管理单元,则该设备应实现多个组件。
例如,配有监视器的帧缓存器卡就是一个电源可管理的双组件设备。帧缓存器电子设备是第一个组件 [组件 0]。未使用帧缓存器电子设备时,其能耗将会降低。监视器是第二个组件 [组件 1]。未使用监视器时,监视器也可以进入低能耗模式。系统将帧缓存器电子设备和监视器视为一个由两个组件组成的设备。
对于电源管理框架而言,所有组件均“一视同仁”,并且组件之间完全无关。如果组件状态不完全兼容,则设备驱动程序必须确保不会出现不需要的状态组合。例如,帧缓存器/监视器卡具有以下几种可能状态: D0、D1、D2 和 D3。与卡连接的监视器具有以下几种可能状态: On、Standby、Suspend 和 Off。这些状态并不一定相互兼容。例如,如果监视器处于 On 状态,则帧缓存器必须处于 D0 状态(即完全打开)。如果在帧缓存器处于 D3 状态时,其驱动程序收到一个请求,要求打开监视器电源使监视器处于 On 状态,则在将监视器设置为 On 之前,驱动程序必须调用 pm_raise_power(9F) 才能启动帧缓存器。如果系统在监视器处于 On 状态时请求降低帧缓存器的电能供给,则驱动程序必须拒绝该请求。
每个设备组件都可处于以下两种状态之一:繁忙或空闲。设备驱动程序通过调用 pm_busy_component(9F) 和 pm_idle_component(9F) 通知框架设备状态的更改。最初创建组件时,组件被视为空闲状态。
通过设备导出的 pm-components 属性,设备电源管理框架可了解设备支持的电源级别。电源级别值必须是正整数。对电源级别的解释由设备驱动程序编写者确定。在 pm-components 属性中必须按单一递增顺序列出电源级别。该框架将 0 电源级别解释为关闭。如果框架由于相关性必须打开设备电源,则它会将每个组件都设置为其最高电源级别。
以下示例给出了某驱动程序的 .conf 文件中的 pm-components 项,该驱动程序实现了一个电源管理组件(即磁盘主轴马达)。磁盘轴马达是组件 0。该轴马达支持两个电源级别。这两个级别表示“停止”和“全速旋转”。
pm-components="NAME=Spindle Motor", "0=Stopped", "1=Full Speed";
以下示例说明如何在驱动程序的 attach() 例程中实现示例 12–1。
static char *pmcomps[] = { "NAME=Spindle Motor", "0=Stopped", "1=Full Speed" }; /* ... */ xxattach(dev_info_t *dip, ddi_attach_cmd_t cmd) { /* ... */ if (ddi_prop_update_string_array(DDI_DEV_T_NONE, dip, "pm-components", &pmcomp[0], sizeof (pmcomps) / sizeof (char *)) != DDI_PROP_SUCCESS) goto failed; /* ... */
以下示例给出了实现两个组件的帧缓存器。组件 0 是支持四个不同电源级别的帧缓存器电子设备。组件 1 表示所连接的监视器的电源管理状态。
pm-components="NAME=Frame Buffer", "0=Off", "1=Suspend", \ "2=Standby", "3=On", "NAME=Monitor", "0=Off", "1=Suspend", "2=Standby", "3=On";
首次连接设备驱动程序时,框架并不了解设备的电源级别。在以下情况下,会进行电源转换:
驱动程序调用 pm_raise_power(9F) 或 pm_lower_power(9F)。
由于超出时间阈值,框架降低了组件的电源级别。
另一个设备更换了电源,而这两个设备之间存在相关性。请参见电源管理相关性。
进行电源转换后,框架将开始跟踪每个设备组件的电源级别。如果驱动程序已通知框架电源级别,则也会进行跟踪。驱动程序通过调用 pm_power_has_changed(9F) 通知框架电源级别的更改。
系统将计算每个可能的电源转换的缺省阈值。这些阈值基于系统空闲阈值。可以使用 pmconfig 或 power.conf(4) 覆盖缺省阈值。当组件电源级别未知时,将使用基于系统空闲阈值的其他缺省阈值。
某些设备的电源应仅在关闭其他设备的电源时关闭。例如,如果允许关闭 CD-ROM 驱动器的电源,则可能会丢失一些必需功能,如弹出 CD 的功能。
为了防止设备独立关闭电源,可以使该设备依赖于电源可能保持打开的其他设备。通常,设备依赖于帧缓存器,因为在用户使用系统时监视器通常处于打开状态。
power.conf(4) 文件指定设备之间的相关性。(设备树中的父节点隐式依赖于其子节点。电源管理框架会自动处理此相关性。)可以使用以下格式的 power.conf(4) 项指定特定的相关性:
device-dependency dependent-phys-path phys-path
其中,dependent-phys-path 是电源保持打开状态的设备,如 CD-ROM 驱动器。phys-path 表示要依赖于其电源状态的设备,如帧缓存器。
在 power.conf 中为插入系统的每个新设备添加一个项将非常麻烦。可以使用以下语法,以一种更通用的方式指明相关性:
device-dependency-property property phys-path
这种项要求任何导出属性 property 的设备都必须依赖于 phys-path 指定的设备。由于此相关性尤其适用于可移除介质设备,因此缺省情况下 /etc/power.conf 包含以下行:
device_dependent-property removable-media /dev/fb
使用此语法,除非关闭控制台帧缓存器的电源,否则无法关闭导出 removable-media 属性的设备的电源。
有关更多信息,请参见 power.conf(4) 和 removable-media(9P) 手册页。
如果 pmconfig 或 power.conf(4) 启用了自动电源管理,则具有 pm-components(9P) 属性的所有设备都将自动使用电源管理。当组件空闲一段缺省时间后,组件将自动降低到下一个最低电源级别。缺省时间段由电源管理框架计算,用于在系统空闲阈值内将整个设备设置为其最低能耗状态。
缺省情况下,1999 年 7 月 1 日后首次发布的所有 SPARC 桌面系统上都启用了自动电源管理。对于所有其他系统,缺省情况下禁用此功能。要确定您的计算机上是否启用了自动电源管理,请参阅 power.conf(4) 手册页中的说明。
可以使用 power.conf(4) 覆盖框架计算的缺省值。
支持包含电源可管理组件的设备的设备驱动程序必须创建 pm-components(9P) 属性。该属性向系统指明设备包含电源可管理组件。pm-components 还向系统指明可用的电源级别。通常,驱动程序通过从其 attach(9E) 入口点调用 ddi_prop_update_string_array(9F) 来通知系统。另一种通知系统的方法是使用 driver.conf(4) 文件。有关详细信息,请参见 pm-components(9P) 手册页。
驱动程序必须始终使框架了解从空闲到繁忙或从繁忙到空闲的设备状态转换。进行这些转换的位置完全特定于设备。繁忙与空闲状态之间的转换取决于设备的性质以及特定组件具备的状态转换特性。例如,SCSI 磁盘目标驱动程序通常导出单个组件,该组件表示 SCSI 目标磁盘驱动器是启动状态还是停止状态。当驱动器有未解决的请求时,该组件将标记为繁忙。完成最后一个排队请求后,该组件将标记为空闲。某些组件创建后从未标记为繁忙。例如,pm-components(9P) 创建的组件就一直处于空闲状态。
pm_busy_component(9F) 和 pm_idle_component(9F) 接口将通知电源管理框架繁忙/空闲状态转换。pm_busy_component(9F) 调用的语法如下所示:
int pm_busy_component(dev_info_t *dip, int component);
pm_busy_component(9F) 将 component 标记为繁忙。当组件为繁忙状态时,不应关闭该组件的电源。如果已关闭组件电源,则将该组件标记为繁忙不会更改电源级别。为此,驱动程序需要调用 pm_raise_power(9F)。对 pm_busy_component(9F) 的调用具有累积性,因此要使组件处于空闲状态,需要调用相应次数的 pm_idle_component(9F)。
pm_idle_component(9F) 例程的语法如下所示:
int pm_idle_component(dev_info_t *dip, int component);
pm_idle_component(9F) 将 component 标记为空闲。可以关闭空闲组件的电源。要使组件处于空闲状态,必须针对 pm_busy_component(9F) 的每次调用调用 pm_idle_component(9F) 一次。
设备驱动程序可以调用 pm_raise_power(9F) 来请求将组件至少设置为给定的电源级别。使用已关闭电源的组件之前,必须采用这种方式设置电源级别。例如,如果已关闭磁盘电源,则 SCSI 磁盘目标驱动程序的 read(9E) 例程可能需要启动磁盘。pm_raise_power(9F) 函数请求电源管理框架将设备能耗状态转换为较高的电源级别。通常,由框架来降低组件的电源级别。但是,设备驱动程序在分离时应调用 pm_lower_power(9F),以便尽可能地降低未使用设备的能耗。
对于某些设备来说,关闭电源可能会产生风险。例如,某些磁带机在关闭电源时会损坏磁带。同样,某些磁盘驱动器在开关电源过程中的容错能力有限,因为每次开关电源都会导致磁头停放。应使用 no-involuntary-power-cycles(9P) 属性通知系统,设备驱动程序应控制设备的所有关开机循环。此方法可防止在分离设备驱动程序时切断设备电源,除非驱动程序已从其 detach(9E) 入口点调用 pm_lower_power(9F) 关闭设备电源。
驱动程序发现某个操作所需组件的电源级别不够高时,会调用 pm_raise_power(9F) 函数。该接口会使驱动程序将组件的当前电源级别提高到所需级别。该调用还会将依赖于该设备的所有设备恢复到全功率。
如果在不再需要访问某个设备后分离该设备,则将调用 pm_lower_power(9F)。调用 pm_lower_power(9F) 可将每个组件设置为最低电源级别,从而使设备在未使用时尽可能少地消耗电量。必须从 detach() 入口点调用 pm_lower_power() 函数。如果从驱动程序的任何其他部分调用 pm_lower_power() 函数,该函数不会起作用。
调用 pm_power_has_changed(9F) 函数可通知框架有关电源转换的情况。转换可能是由于设备更改了自己的电源级别而导致。转换也可能是由于暂停/恢复等操作而导致。pm_power_has_changed(9F) 的语法与 pm_raise_power(9F) 的语法相同。
电源管理框架使用 power(9E) 入口点。
power() 使用以下语法:
int power(dev_info_t *dip, int component, int level);
需要更改组件的电源级别时,系统将调用 power(9E) 入口点。该入口点执行的操作特定于设备驱动程序。在上面提到的 SCSI 目标磁盘驱动程序示例中,如果将电源级别设置为 0,则会发送 SCSI 命令停止磁盘运转,而如果将电源级别设置为全电源级别,则会发送 SCSI 命令启动磁盘。
如果电源转换导致设备丢失状态,则驱动程序必须将任何必需的状态保存在内存中,以便将来恢复。如果电源转换要求先恢复保存的状态,然后才能再次使用设备,则驱动程序必须恢复该状态。该框架并未对哪些电源事务会导致丢失自动电源管理设备的状态作出假设,也未对哪些电源事务会要求恢复自动电源管理设备的状态作出假设。以下示例给出了一个 power() 例程样例。
int xxpower(dev_info_t *dip, int component, int level) { struct xxstate *xsp; int instance; instance = ddi_get_instance(dip); xsp = ddi_get_soft_state(statep, instance); /* * Make sure the request is valid */ if (!xx_valid_power_level(component, level)) return (DDI_FAILURE); mutex_enter(&xsp->mu); /* * If the device is busy, don't lower its power level */ if (xsp->xx_busy[component] && xsp->xx_power_level[component] > level) { mutex_exit(&xsp->mu); return (DDI_FAILURE); } if (xsp->xx_power_level[component] != level) { /* * device- and component-specific setting of power level * goes here */ xsp->xx_power_level[component] = level; } mutex_exit(&xsp->mu); return (DDI_SUCCESS); }
以下示例是包含两个组件的设备的 power() 例程,其中,组件 1 为打开状态时,组件 0 必须也为打开状态。
int xxpower(dev_info_t *dip, int component, int level) { struct xxstate *xsp; int instance; instance = ddi_get_instance(dip); xsp = ddi_get_soft_state(statep, instance); /* * Make sure the request is valid */ if (!xx_valid_power_level(component, level)) return (DDI_FAILURE); mutex_enter(&xsp->mu); /* * If the device is busy, don't lower its power level */ if (xsp->xx_busy[component] && xsp->xx_power_level[component] > level) { mutex_exit(&xsp->mu); return (DDI_FAILURE); } /* * This code implements inter-component dependencies: * If we are bringing up component 1 and component 0 * is off, we must bring component 0 up first, and if * we are asked to shut down component 0 while component * 1 is up we must refuse */ if (component == 1 && level > 0 && xsp->xx_power_level[0] == 0) { xsp->xx_busy[0]++; if (pm_busy_component(dip, 0) != DDI_SUCCESS) { /* * This can only happen if the args to * pm_busy_component() * are wrong, or pm-components property was not * exported by the driver. */ xsp->xx_busy[0]--; mutex_exit(&xsp->mu); cmn_err(CE_WARN, "xxpower pm_busy_component() failed"); return (DDI_FAILURE); } mutex_exit(&xsp->mu); if (pm_raise_power(dip, 0, XX_FULL_POWER_0) != DDI_SUCCESS) return (DDI_FAILURE); mutex_enter(&xsp->mu); } if (component == 0 && level == 0 && xsp->xx_power_level[1] != 0) { mutex_exit(&xsp->mu); return (DDI_FAILURE); } if (xsp->xx_power_level[component] != level) { /* * device- and component-specific setting of power level * goes here */ xsp->xx_power_level[component] = level; } mutex_exit(&xsp->mu); return (DDI_SUCCESS); }