linux 电源管理
2011年06月16日
目录 [隐藏] 1 电源管理 1.1 高级配置与能源接口(ACPI) 1.1.1 ACPI规范介绍 1.1.2 ACPI 一些基本概念 1.1.3 ACPI的运行 1.1.4 ACPI驱动程序分析 1.1.5 pci的ACPI电源管理的实现 1.1.6 ACPI的事件响应 1.2 APM电源管理模式 电源管理 目前比较流行的电源管理技术主要有两种:高级电源管理(APM)、高级配置与能源接口(ACPl)。其中ACPI在两者之中比较先进,它将电源管理交由操作系统负责,可以有更灵活的管理方式。能否使用电源管理,以及是否可以启用ACPI,不仅仅要求操作系统进行支持,同时也依赖于硬件环境。例如计算机的主板如果不支持ACPI的话,操作系统也不可能利用ACPI进行电源管理。 在linux操作系统的比较新的内核中,缺省的配置均是使用ACPI进行电能管理。但是要注意一点,就是APM与ACPI两种管理方式中只能有一种进行实际的控制管理。如果在编译系统内核时两种管理方式都选择了,那么系统内核将会自动判断该计算机的硬件配置适合两种方式中的哪一种。如果该计算机的硬件配置对这两种管理方式都支持,那么操作系统将会自动选择ACPI方式进行管理。 高级配置与能源接口(ACPI) ACPI规范介绍 ACPI能使软、硬件、操作系统(OS),主机板和外围设备,依照一定的方式管理用电情况,系统硬件产生的 Hot-Plug 事件,让操作系统从用户的角度上直接支配即插即用设备,不同于以往直接通过基于 BIOS 的方式的管理。图 11 7:ACPI 体系结构 ACPI体系结构如上图,根据ACPI 规格定义的 ACPI 驱动体系(简称 ACPI CA),主要目的在于让操作系统和 ACPI 硬件隔离开,让 Linux 中通过一系列的接口来访问 ACPI 层。在 ACPI 规范中将 ACPI 体系分割成 ACPI 核心层(Core subsystem)和OS 服务层(OS service),ACPI 核心层用于提供基本的 ACPI 服务(AML 翻译和名字空间管理等),OS 服务层提供ACPI 单元针对不同的操作系统的接口服务,ACPI体系框架图如下图。图 11 8:ACPI体系框架图图 11 9:ACPI 核心层框架 下面介绍ACPI 核心层: 1. ACPI 核心层: ACPI 核心层分成几个相互关联的逻辑模块,每个模块之间包含一些相关的 ACPI API,当用户在编写相关的含有 ACPI 服务的驱动程序的时候,会调用这些相关模块的接口。 (1) AML Interpreter AML分析器负责分析和运行从本地计算机 BIOS 提供的 AML 文件流,一般说来 AML 翻译器为其他的 ACPI 服务模块提供方法节点运行和获得名字空间中某个方法节点的对象服务。 (2) ACPI Table Management 是一个负责载入,管理,分析,校验 ACPI 模块中所使用的各种来自系统 BIOS 的一些特殊的支持 ACPI 服务的表格,例如:RSDT,FSDT,FACS,DSDT等等,这些表在操作系统进行初始化的时候被载入内存。 (3) Namespace Management 在AML 翻译器之上提供名字空间服务,它负责创建和管理内部的名字空间。 (4) Resource Management 资源管理提供建立在名字空间资源的配置和获取,其中包括了 PCI的设备的地址区间,中断等重要参数。它所提供的服务包括:获取和设定当前的资源,获取设备上可能存在的地址区间以及 PCI 设备的中断路由表(IRQ Routing Tables),获取当前设备的电源支持能力(例如是否支持 S1-S5 状态)。 (5) ACPI H/W Management该模块用于控制对桥芯片上 ACPI 寄存器和时钟以及其他 ACPI 关联硬件的访问,例如 ACPI GPE 状态寄存器和使能寄存器,系统状态获得。 (6) Event handling:事件管理模块是用于管理系统控制中断(SCI)的发生和 GPE 事件的响应,SCI 包括 ACPI 时钟中断,以及 GPE 事件管理。这个单元负责"分发"地址空间和操作空间(OperationRegion)的事件到当前的操作系统层,并负责调用相关的句柄来进行处理。 2.OS 服务层: ACPI OS 服务层(OSL)可以让 ACPI 逻辑模块独立于操作系统上运行。OS 服务层通过驱动程序接口,将从 ACPI 核心的服务转换成本地操作系统的访问和调用。 OSL 的组成模块介绍: (1)OS 启动时的引导服务:在 OS 载入过程中引导服务包括ACPI 子系统的初始化,ACPI硬件初始化及各种设备在ACPI名字空间的_INI控制方法的运行。它在大多数其它的操作系统初始化之前执行。 (2)设备驱动程序载入服务: 对于出现在 ACPI 名字空间中的设备,操作系统必须有一个机制检测它们并装载将设备驱动程序。ACPI系统提供发设备和总线的编号、资源检测和资源设置。 (3)操作系统运行服务: 运行服务包括大部分 ACPI 系统和 OS 交互的外围接口,包括事件管理日志和电源管理功能。 (4)异步服务 异步功能包括中断服务(系统控制中断),事件处理和分发(固定事件,GPE 事件,通知事件和操作区访问事件),以及错误处理。 ACPI 一些基本概念 DSDT: DSDT 称做 Differentiated Definition Block,存在于 BIOS 中并与当前的硬件平台兼容的,提供了系统的硬件特性(例如某些设备的内部寄存器和存储器)的应用策略和配置,在系统初始化的时候,DSDT 被当前系统启动时初始化到名字空间中。 FADT:FADT 中包含了 ACPI 的硬件寄存器组(GPE)的应用和配置(包含它们的硬件地址)也包括DSDT表的硬件地址。 ACPI Namespace: ACPI名字空间是一个大的数据结构,被ACPI核心子系统构建并维护。ACPI名字空间数据来自用AML语言在DSDT中的定义。ACPI名字空间装有称为ACPI对象的体系结构。 在ACPI名字空间每对象有固定4个字节的名字,根对象是"/"。对象的范围是这个对象的所有孩子。 说明范围、名字和对象的例子如下: 下面是ASL语言源代码,它是对象_GPE 的范围,这个范围装有对象_L08 和 _L0A。 Scope (\_GPE) { Method (_L08) { Notify (\_SB.PCI0.DOCK, 1) } Method (_L0A) { Store (0, \_SB.PCI0.ISA.EC0.DCS) } } 这个例了中有三个ACPI名字空间的对象,它们描述如下: ?? 三个对象的名字是 _GPE, _L08, 和 _L0A。 ?? 父对象_GPE的孩子对象是 _L08 和 _L0A。 ?? 对象_L08的绝对路径是 "\_GPE._L08"。 ?? 对象 _GPE范围包括 _L08 和 _L0A 对象。 ?? 在名字空间对象 _L08 和 _L0A 没有与它们相关的范围。 ?? 对象_L08 和 _L0A 的类型是 ACPI_TYPE_METHOD. ?? 在样例中,在_GPE的范围里,对象 _L08的后面的对象是对象 _L0A。 ?? 对象 _GPE 的父对象是根对象 预定义的对象 在名字空间中有几个预定义的对象,它们一直存在ACPI名字空间中,把它们列出如下: "_GPE", ACPI_TYPE_ANY // General Purpose Event block "_PR_", ACPI_TYPE_ANY // Processor block "_SB_", ACPI_TYPE_ANY // System Bus block "_SI_", ACPI_TYPE_ANY // System Indicators block "_TZ_", ACPI_TYPE_ANY // Thermal Zone block "_REV", ACPI_TYPE_NUMBER // Revision "_OS_", ACPI_TYPE_STRING // OS Name "_GL_", ACPI_TYPE_MUTEX // Global Lock 下面图显示了ACPI名字空间的逻辑图:图 11 10:ACPI名字空间的结构 SCI 中断:(System Control Interrupt) 系统控制中断,SCI 中断是一种源自 ACPI 兼容芯片系统中断,系统映射不同的 ACPI 事件中断向量以便共享此中断,当底层硬件产生 SCI 中断的时候(例如设备插入事件引发中断),根据通知 OSPM 层处理相对应的 ACPI 事件,OSPM 层会调用预先安装的中断句柄。 GPE Block Device 和 GPE 事件:GPE Block Device 是平台设计者可按照 FADT(Fixed ACPI Descriptor Table) 描述表中响应 GPE 的寄存器组,GPE 的输入脚作为 GPE 设备描述块中的地址存在于 FADT 中,每个 GPE Block Device 可以容纳 128 个 GPE 事件,ACPI 层上提供两个通用目标寄存器组--GPE0_BLK 和 GPE1_BLK,(也就是说可以响应 256 个 GPE 事件)每个寄存器组中包含两个等长度的寄存器 GPEx_STS,GPEx_EN,他们的系统地址(硬件地址)都保存在 FADT 中,GPE Blocks 的操作描述以事件存在于 ACPI 名字空间中,例如设备插入/拔除事件发生的时候,相关的状态位(GPEx_STS中的位)会被外部的事件所置位,生成 SCI,让 OSPM 层运行相关的控制方法。GPEx_EN 表示每个事件的使能位。这些寄存器一般在南桥(ICH4)中,它们的硬件地址保存在 FADT 中。GPE Block Device示意图如下:图 11 11:GPE Block Device示意图 GPE 事件就是通过 GPE 寄存器组引发 SCI 中断后,通告 OSPM 层有关设备的事件。 ACPI Source Language(ASL):ASL 语言是 ACPI 层用于描述特定的 ACPI 对象的 ACPI 专用语言,并且包括了 ACPI 对象的控制方法(Control method),OEM 厂商和 BIOS 设计者在 BIOS 中使用 ASL 定义所有的设备为 ACPI 对象,并且可以生成 ASL 格式的专门的控制方法。 AML 和 AML 分析器:AML 是 ACPI 控制方法的虚拟机器语言,AML 执行过程也就是 ACPI 核心驱动层,ACPI 控制方法使用 AML 来进行编写,但是通常而言对编写者来说是写成 ASL 的方式,通过 AML 翻译器进行翻译,AML 翻译器不但具备 ASL 的翻译的功能,而且可以执行 AML 方法,当用 ASL 编写的 DSDT 表被载入到名字空间的时候,将会被 AML 翻译器翻译成执行时候可以辨别的机器码。 对于ACPI层来说,内存维持了一个目录形式的指向每个设备,以及 GPE 的名字空间,这个名字树是通过初始化的时候由 DSDT 创建的,名字树可以通过 loadtable 方法从 BIOS 中载入 DSDT 改变,而每个设备在 ACPI 层中都被描述成一个对象,包含有对这个设备特性和操作策略的描述列表,系统所有类型设备都是保存在同一个名字树下。在 ACPI OS 层上调用 _ADR 来获得 Namespace 的设备名。 ACPI的运行 1.初始化 ACPI核心子系统的初始化是由OS服务驱动的。分四个主要步骤: 先执行核心子系统的全局的数据初始化,再装载ACPI的FACS、FADT、DSDT表,这些表必须在名字空间建立之前拷贝进核心子系统。这些表从输入buffer或firmware载入。然后建立内部的名字空间,核心子系统分析DSDT表,找出对象并建立在内部的名字空间里,再接着设置机器的ACPI模式。核心子系统安装一个中断句柄给SCI(System Control Interrupts系统控制中断)并将硬件从不确定模式转到ACPI模式。 2.事件处理 系统控制中断(SCI)是由固定ACPI和通用事件(GPD general Purpose Events)产生的。通知事件通过在一个控制方法里的ASL通知的运行来产生的。事件是在一个控制方法运行期间,对地址空间或操作区域的访问引起的。事件的具体描述如下: 固定事件(Fixed Event):默认情况下由ACPI子系统事件句柄处理,也可由给每个事件安装的句柄处理。仅仅设备驱动程序或系统服务能安装这样的句柄。 通用事件(General Purpose Event):GPE常被与特定GPE相连的控制方法运行处理。按照ACPI规范,每个GPE有一个与之相连的方法,这个方法的名字形式是_Txx,T是GPE的类型,即E代表边缘触发,L代表水平触发。xx是GPE的16进制分级。这种控制方法不在SCI中断句柄的上下文运行,而是被操作系统排队以后再运行。如果一个GPE句柄(handler)被安装,首先,这个句柄被触发,接着相连的控制方法进行排队运行。 通知事件(Notify Event):一个ACPI通知事件是控制方法运行期间执行Notify代码的结果。通知事件发生在ACPI对象上,这个对象必须是设备或热区域。 3.地址空间和操作区域 ASL源代码和与它相关的代码使用地址空间机制来访问超出ASL的直接范围的数据。如地址空间被用来访问CMOS RAM和ACPI嵌入控制器。有几个预定义的地址空间及用户预定义的地址空间可被访问。 操作系统允许通过ASL操作区域结构访问各种地址空间。操作区域是一个地址空间的一个命名的窗口。在操作区域创建阶段,ASL编程器定义了窗口的大小和被操作区域访问的地址空间,这个访问窗口的地址称为fields。 AML分析器负责翻译称为Fields的ASL/AML引用到合适的地址空间,分析器这样解析在地址空间的位置:先是用在操作空间中操作区域的偏移地址来找到操作区域,再用操作区域中的field地址来表示。解析的地址、访问地址宽度和读写函数被传进地址空间句柄(handler),来执行对地址空间的实际读写操作。 地址空间句柄的安装 每个地址空间被特定的设备拥有。这个设备的范围(scope)里的地址空间引用将被设备地址空间句柄处理,这个机制允许多个地址空间或操作区域句柄安装到同一类型的地址空间上。如:一个有两个SMBus设备的平台,这两个设备一个是基于SMBus的控制器,另一个是基于SMBus的PCI总线,每个SMBus必须把它自己的地址空间给ASL,这种情况下有两个设备驱动程序和两个不同的地址空间句柄,指向同一个地址空间上。地址空间句柄被安装在一个ACPI名字空间的命名的对象或特殊对象ACPI_ROOT_OBJECT上。 ACPI定义的地址空间有: ?? System Memory ?? System I/O ?? PCI Configuration Space ?? System Management Bus (SMBus) ?? Embedded Controller ?? CMOS ?? PCI Bar Target ACPI CA子系统对下列地址空间使用默认的地址空间句柄ACPI_DEFAULT_HANDLER: ?? System Memory ?? System I/O ?? PCI Configuration Space 下面以PCI设备为例说明ACPI的运行流程: (1) 用户插入PCI设备到PCI插槽里。 (2) 主板南桥控制器生成GPE事件; (3) ACPI核心控制芯片组产生SCI中断; (4) ACPI硬件会清除这个GPE事件的响应位,并且运行对应GPE位在名字空间中的_LXX控制方法。 (5) _LXX控制方法将根据主板南桥控制器的相应控制位来决定哪个插槽有设备插入。 (6) _LXX控制方法将向驱动层发出通告(Notify)表示当前的PCI总线上的某一个插槽有设备插入; (7) ACPI驱动层(这里开始就进入ACPI OS层部分了)运行_STA方法获得第6步所知的设备的当前状态,如果运行_STA方法返回0x0a表示当前设备不能使能。 (8) ACPI驱动层告诉PCI层准备对新插入的设备进行枚举。 (9) PCI设备层读入相关设备(PCI功能模块)的配置信息: 对该PCI卡上所有设备根据设备制造厂商获得相关的驱动程序。 激活PCI卡上所有的功能模块。 ACPI驱动层运行_PS0控制方法,根据PCI设备的电源管理规范,使能PCI设备上的电源管理状态寄存器,使PCI设备上电。 正常运行/访问设备。 ACPI驱动程序分析 1. ACPI驱动程序的初始化 根据宏定义来判断运行APM还是ACPI电源管理模式,ACPI驱动程序的初始化是分两步进行的,先是在内核启动时的函数start_kernel()中调用acpi_early_init函数来初始化ACPI全局变量,装载各种表,初始化名字空间,再接着由内核调用acpi_init函数初始化对象,安装中断句柄,连接事件处理函数等。 先分析函数acpi_early_init,它被start_kernel函数调用,分析如下: 在init/main.c中start_kernel函数对ACPI的调用如下: asmlinkage void __init start_kernel(void) { …… acpi_early_init(); …… } 在drivers/acpi/bus.c中acpi_early_init函数如下: void __init acpi_early_init (void) { acpi_status status = AE_OK; //给固定ACPI描述表(FADT)fadt_descriptor_rev2分配空间 struct acpi_buffer buffer = {sizeof(acpi_fadt), &acpi_fadt}; …… //初始化子系统 status = acpi_initialize_subsystem(); …… //装载各种表并从表中得到数据装载名字空间 status = acpi_load_tables(); …… // 得到FADT的单独拷贝,以让其它驱动程序使用 status = acpi_get_table(ACPI_TABLE_FADT, 1, &buffer); …… //检查FADT各项值的正确性,使能ACPI模式 status = acpi_enable_subsystem(~(ACPI_NO_HARDWARE_INIT | ACPI_NO_ACPI_ENABLE)); …… } acpi_initialize_subsystem函数初始化各种全局变量,函数分析如下(在drivers/acpi/utilities/utxface.c中): acpi_status acpi_initialize_subsystem (void) { …… //初始化各种全局变量如链表、结构等,并分配空间 acpi_ut_init_globals (); …… /* Create the default mutex objects */ status = acpi_ut_mutex_initialize (); …… //初始化名字空间管理器并初始化名字空间节点树的根对象 status = acpi_ns_root_initialize (); …… } 图 11 12:函数acpi_init调用层次图 函数acpi_init注册子系统,初始化ACPI并判断是否使用ACPI电源管理模式,函数acpi_init调用层次图如上图,函数分析如下(在drivers/acpi/bus.c中): static int __init acpi_init (void) { int result = 0; ACPI_FUNCTION_TRACE("acpi_init"); //由宏定义决定acpi_disabled值 if (acpi_disabled) { printk(KERN_INFO PREFIX "Interpreter disabled.
"); return -ENODEV; } firmware_register(&acpi_subsys); result = acpi_bus_init(); //如果ACPI初始化失败,设置激活AMP电源管理标识 if (!result) { #ifdef CONFIG_PM if (!PM_IS_ACTIVE()) pm_active = 1; else { printk(KERN_INFO PREFIX "APM is already active, exiting
"); disable_acpi(); result = -ENODEV; } #endif } else disable_acpi(); return_VALUE(result); } 在arch/i386/kernel/setup.c中有ACPI电源管理中是否有效的宏定义,列出如下: #ifdef CONFIG_ACPI_INTERPRETER int acpi_disabled = 0; #else int acpi_disabled = 1; #endif 在include/linux/pm.h中有APM电源管理是否有效的宏定义,列出如下: #ifdef CONFIG_PM extern int pm_active; #define PM_IS_ACTIVE() (pm_active != 0) APM管理函数有效,如pm_register、pm_send、pm_find等函数有效 #else /* CONFIG_PM */ #define PM_IS_ACTIVE() 0 pm_register、pm_send、pm_find等函数为空 #endif static int __init acpi_bus_init (void) { …… //创建名为"kacpid"的工作队列 status = acpi_os_initialize1(); //初始化ACPI子系统 status = acpi_enable_subsystem(ACPI_NO_HARDWARE_INIT | ACPI_NO_ACPI_ENABLE); …… #ifdef CONFIG_ACPI_EC //查询ECDT表得到EC参数(ACPI Embedded Controller drvier) status = acpi_ec_ecdt_probe(); #endif //初始化对象 status = acpi_initialize_objects(ACPI_FULL_INITIALIZATION); …… //得到系统中断模型并给对象赋值"\_PIC" result = acpi_bus_init_irq(); // Register the for all standard device notifications. status = acpi_install_notify_handler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY, &acpi_bus_notify, NULL); …… } // Create the top ACPI proc directory acpi_root_dir = proc_mkdir(ACPI_BUS_FILE_ROOT, NULL); …… } acpi_enable_subsystem 检查FADT表有效性,进入ACPI模式,设备中断句柄及事件连接函数,函数分析如下(在drivers/acpi/utilities/utxface.c中): acpi_status acpi_enable_subsystem (u32 flags) { … if (!(flags & ACPI_NO_HARDWARE_INIT)) { //检查FADT表值是否正确 status = acpi_hw_initialize (); … } //使能 ACPI 模式 if (!(flags & ACPI_NO_ACPI_ENABLE)) { acpi_gbl_original_mode = acpi_hw_get_mode(); status = acpi_enable (); … } //安装缺省op_region句柄 if (!(flags & ACPI_NO_ADDRESS_SPACE_INIT)) { status = acpi_ev_install_region_handlers (); … } // Initialize ACPI Event handling (Fixed and General Purpose) if (!(flags & ACPI_NO_EVENT_INIT)) { status = acpi_ev_initialize_events (); … } /* Install the SCI handler and Global Lock handler */ if (!(flags & ACPI_NO_HANDLER_INIT)) { status = acpi_ev_install_xrupt_handlers (); … } return_ACPI_STATUS (status); } 2. 各种类型ACPI设备驱动程序的安装 在drivers/acpi目录下有各种ACPI驱动程序,它们有的以可选模块的形式安装,如风扇、热区域的驱动程序,有的以子系统的形式安装,如pci_root、pci_link驱动程序,这些驱动程序一方面向/proc文件系统注册,以便用户空间访问,另一方面,注册成ACPI总线驱动程序,放在ACPI总线设备链表中,并连接上每个设备的操作函数集到文件操作函数指针上。下面只列出两个设备的初始化函数: 在drivers/acpi/thermal.c中有热区域初始化函数如下: static int __init acpi_thermal_init (void) { acpi_thermal_dir = proc_mkdir(ACPI_THERMAL_CLASS, acpi_root_dir); … result = acpi_bus_register_driver(&acpi_thermal_driver); … } module_init(acpi_thermal_init); 在drivers/acpi/pci_root.c中有pci_root初始化函数如下: static int __init acpi_pci_root_init (void) { … if (acpi_bus_register_driver(&acpi_pci_root_driver) API分别在include/acpi/acpixf.h和include/acpi/acpiosxf.h中,其中,acpixf.h中提供了操作系统驱动程序访问ACPI的接口函数,acpiosxf.h中提供了ACPI模块访问操作系统的接口函数。现只列出常用的函数作一说明。 (1)操作系统驱动程序访问ACPI的接口函数 在drivers/acpi/namespace/nsxfeval.c中有驱动程序电源管理需访问ACPI名字空间而调用的接口函数,名字空间是个Node为节点的树形结构,存有各种设备的ACPI对象及方法等,驱动程序需访问名字空间才能得到对象从而对设备电源进行管理。其中,acpi_evaluate_object函数是常调用的,acpi_evaluate_object函数是从ACPI名字空间找到对应的Node,返回对象。由于名字空间管理很复杂,这里只从使用者的角度加以说明,而不分析它的实现过程。 acpi_evaluate_object函数功能是找到给定的对象并得到它的值acpi_object对象,其参数"Handle" 或 "Pathname" 必须有一个有效,函数及参数说明如下: acpi_status acpi_evaluate_object ( acpi_handle handle, acpi_string pathname, struct acpi_object_list *external_params, struct acpi_buffer *return_buffer) 参数 Handle - 对象句柄,实质上是指向Node的指针(叵选) 参数 Pathname - 对象路径字符串(可选) 参数 external_params - 传给方法的参数链表,以NULL结尾 参数 return_buffer - 存放方法的返回值acpi_object对象,如果是NULL,表示没有返回值 函数类型 acpi_status -返回操作成功与否的状态 acpi_walk_namespace函数的功能是DESCRIPTION: 在名字空间树上执行可修改深度的遍历,从start_handle 开始,当匹配type 参数的对象找到,就调用user_function 函数,如果用户函数返回一个非0值,即操作失败,立即返回给调用者。这函数可提供通用的名字空间遍历函数,可提供多种服务。函数及参数描述如下: acpi_status acpi_walk_namespace ( acpi_object_type type, acpi_handle start_object, u32 max_depth, acpi_walk_callback user_function, void *context, void **return_value) 参数 Type - 查找的acpi_object_type 参数 start_object - 名字空间中开始查找的Node 参数 max_depth - 查找的深度 参数 user_function - 当"Type"类型对象找到,调用的函数 参数 Context - 传给用户函数的上下文 参数 return_value - 如果过早结束,存放用户函数返回的值 返回值acpi_status 如果过早结束,存放用户函数返回的值,否则,为NULL. acpi_install_notify_handler函数给一个ACPI设备安装notifies 句柄,函数及其参数描述如下: acpi_status acpi_install_notify_handler ( acpi_handle device, u32 handler_type, acpi_notify_handler handler, void *context) Device -需要接收通告的设备句柄 handler_type - 句柄的类型: ACPI_SYSTEM_NOTIFY: 系统句柄 (00-7f) ACPI_DEVICE_NOTIFY: 驱动程序句柄(80-ff) ACPI_ALL_NOTIFY: 包括上两个 Handler - 句柄的地址,表示执行消息的回调函数 context - 传给每GPE事件上的句柄的数据 返回值: 返回状态值 (2) ACPI模块访问操作系统的接口函数 acpi_status acpi_os_queue_for_execution ( u32 priority, acpi_osd_exec_callback function, void *context); pci的ACPI电源管理的实现图 11 13:acpiphp_init函数的调用层次图 下面以PCI总线为例说明PCI驱动程序是如何调用ACPI函数来进行设备电源管理的,在acpiphp_init 模块初始化函数中有对ACPI函数的调用,函数的调用层次图如上图,函数分析如下(在drivers/pci/hotplug/acpiphp_core.c中): static int __init acpiphp_init(void) { … //从系统中读出所有的ACPI信息 retval = init_acpi(); if (retval) return retval; return init_slots(); } module_init(acpiphp_init); static int __init init_acpi(void) { int retval; //初始化内部数据结构 retval = acpiphp_glue_init(); //读初始的槽数 if (!retval) { num_slots = acpiphp_get_num_slots(); … } return retval; } 函数acpiphp_glue_init初始化所有的PCI热插拔-ACPI粘接数据结构,函数分析如下(在acpiphp_glue.c中): int __init acpiphp_glue_init(void) { … num = acpi_pci_register_driver(&acpi_pci_hp_driver); … } static struct acpi_pci_driver acpi_pci_hp_driver = { .add = add_bridge, .remove = remove_bridge, }; 函数add_bridge 查找可热插拔的插口,接着查找P2P 桥,函数列出如下(在drivers/pce/hotplug/acpiphp_glue.c中): static int add_bridge (acpi_handle handle) { acpi_status status; unsigned long tmp; int seg, bus; acpi_handle dummy_handle; //得到handle下面路径名为"_STA"的node,在dummy_handle中返回 status = acpi_get_handle(handle, "_STA", &dummy_handle); if (ACPI_SUCCESS(status)) { status = acpi_evaluate_integer(handle, "_STA", NULL, &tmp); … } //得到 PCI segment 数 status = acpi_evaluate_integer(handle, "_SEG", NULL, &tmp); seg = ACPI_SUCCESS(status) ? tmp : 0; //得到 PCI 总线数 status = acpi_evaluate_integer(handle, "_BBN", NULL, &tmp); if (ACPI_SUCCESS(status)) { bus = tmp; } else { warn("can't get bus number, assuming 0
"); bus = 0; } //调用acpi_walk_namespace函数检查这桥是否有可插拔卡的插槽 if (detect_ejectable_slots(handle) > 0) { dbg("found PCI host-bus bridge with hot-pluggable slots
"); add_host_bridge(handle, seg, bus); return 0; } tmp = seg 调用函数acpi_evaluate_object来实现的,这个函数分析如下(在drivers/acpi/namespace/nsxfeval.c中): acpi_status acpi_evaluate_integer ( acpi_handle handle, acpi_string pathname, struct acpi_object_list *arguments, unsigned long *data) { acpi_status status = AE_OK; union acpi_object element; struct acpi_buffer buffer = {sizeof(union acpi_object), &element}; … //在ACPI名字空间得到指定路径的对象存在buffer status = acpi_evaluate_object(handle, pathname, arguments, &buffer); … *data = element.integer.value; return_ACPI_STATUS(AE_OK); } ACPI的事件响应 与ACPI相关的寄存器一般都位于ICH4南桥。其描述保存在BIOS的FADT中,在Linux内核中通常以fadt_descriptor_rev2的数据格式表示,列出如下(在include/acpi/actbl2.h中): /* * ACPI 2.0 Fixed ACPI Description Table (FADT) */ struct fadt_descriptor_rev2 { ACPI_TABLE_HEADER_DEF /* ACPI common table header */ u32 V1_firmware_ctrl; /* 32-bit physical address of FACS */ u32 V1_dsdt; /* 32-bit physical address of DSDT */ …… u16 sci_int; /* SCI中断的中断矢量 */ …… u32 V1_gpe0_blk; /*GPE0块的端口地址 */ u32 V1_gpe1_blk; /*GPE1块的端口地址 */ …… u8 gpe1_blk_len; /* Byte Length of ports at gpe1_blk */ u8 gpe1_base; /*GPE1事件在GPE模型中开始偏移地址 */ …… u32 pwr_button : 1; /* Power按钮被处理 */ u32 sleep_button : 1; /* Sleep按钮被处理 */ …… u64 xfirmware_ctrl; /* 64-bit physical address of FACS */ u64 Xdsdt; /* 64-bit physical address of DSDT */ …… }; 通过FADT获得当前的事件寄存器描述之后,就知道当前ACPI系统中事件寄存器的硬件地址,长度,各个状态位的情况,在名字空间中通常会有专门的全局对象节点针对GPEx_STS寄存器的各个位具体执行操作节点进行描述,如下例:_GPE用来表示GPE寄存器,以及相应的需要通知OSPM处理动作,例如: _GPE Method(_L01) { // Update device Sleep(250) // Mechanical Delay Notify(CDRM, 1) } _GPE表示的是当前的通用事件寄存器的ASL描述,而Method(_L01)当前的通用寄存器的某个位的控制方法,_L表示这个位的触发状态为电平触发,01表示的通用寄存器的01位。 当一个GPE事件发生之后,所调用的方法节点通常会调用"NOFITY"操作符,通知OSPM进行处理,通常一个ASL的通告操作表示如下: Notify(\_SB.PCI0.P2P2,0) \_SB.PCI0.P2P2表示当Hot Plug阶段的该设备接收响应事件,0表示ACPI向OSPM通告消息BUS Check通告, 通知PCI总线驱动层进行枚举。 Notify操作ACPI层向操作系统驱动层通告当前消息,最终调用到acpi_os_queue_for_execution这个ACPI OS层的执行接口,它在Linux中执行方式就是把需要执行的任务(而acpi_ev_notify_ dispatch调用NOTIFY节点对象的实际执行者)插入任务队列中,由操作系统调用运行。 下面以PCI驱动程序初始过程中调用的函数add_host_bridge为例说明ACPI通知事件句柄的安装过程。函数add_host_bridge在drivers/pci/hotplug/acpiphp_glue.c中,只列出与ACPI相关的部分。 /* allocate and initialize host bridge data structure */ static void add_host_bridge(acpi_handle *handle, int seg, int bus) { …… /* decode resources */ status = acpi_walk_resources(handle, METHOD_NAME__CRS, decode_acpi_resource, bridge); …… acpiphp_dump_resource(bridge); …… init_bridge_misc(bridge); } /* initialize miscellaneous stuff for both root and PCI-to-PCI bridge */ static void init_bridge_misc(struct acpiphp_bridge *bridge) { …… /* subtract all resources already allocated */ acpiphp_detect_pci_resource(bridge); /* register all slot objects under this bridge */ status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge->handle, (u32)1, register_slot, bridge, NULL); /* install notify handler */ status = acpi_install_notify_handler(bridge->handle, ACPI_SYSTEM_NOTIFY, handle_hotplug_event_bridge, bridge); …… list_add(&bridge->list, &bridge_list); acpiphp_dump_resource(bridge); } APM电源管理模式 电源管理的数据结构如下(在include/linux/pm.h中): typedef int(*pm callback)(struct pm_dev *dev,pm_request rqst,void *data); struct pm_dev { pm_der_t type; unsigned long id; pm_callback callback;//设备的能源管理函数 void *data; unsigned long flags; int state; int prev_state; struct list_head entry; } 在内核中存在一个名为pm_devs的全局链表,每个电源管理设备注册时调用pm_register()进行注册,将其相应的pm_dev结构加入到pm_devs链表中。这个注册函数说明如下(在kernel/power/pm.c中): stuct pm_dev *pm_register(pm_dev_t type, 设备的类型 unsigned long id, pm_callback callback)设备的能源管理函数 电源管理的运行如下: (1)在某一设备初始化时采用pm_register函数对其电源管理进行注册。 (2)在每次对硬件的操作之前调用pm_access函数,确认设备是否处于准备好的状态。 (3)在进入和退出挂起状态时,调用pm_callback函数。 (4)在设备不再使用时,调用pm_dev_idle以利于设备的空闲检测。 (5)当卸载设备驱动时,调用pm_unregister。 APM电源管理的整个结构如下图,在用户空间有一个后台进程来进行电源管理,它检测设备的电源管理状态,接收来自其它应用程序的消息,并通过APM_BIOS设备驱动程序来对设备进行电源管理。 当电源管理后台进程打开在apm_bios设备文件,用ioctrl进行电源管理时,APM_BIOS设备驱动程序后调用到pm_send、pm_send_all等函数(在kernel/pm.c中),将对应的电源管理传给具体的电源管理设备驱动,pm_send_all函数会送给链表中的所有电源管理设备驱动,从而达到控制设备电源的目的。图 11 14:APM电源管理的整个结构 下面举例说明APM电源管理的实现。它由后台进程与内核中的驱动程序组成。其中,后台进程通过socket与其它应用程序通信,利用ioctrl来控制设备,后台进程的代码如下: #include #include #include #include #include #include #include #include #include #include #include "linux/apm_bios.h" #include "linux/fb.h" #include #include #include #include #include #include #include "pm_daemon.h" #include "pm_list.h" #include "pm_event.h" #define MAX(a, b) PM_STATUS_T PM_status = { display_on: 1, // display is on lcd_timeout: LCD_SLEEP_TIME, sleep_timeout: AP_SLEEP_TIME, }; /* * device descriptors: apm_bios and framebuffer */ INT32 apm, disp; /* * list structure */ typedef struct pm_list_t { struct pm_list_t *next; } PM_LIST_T; /* * socket list structure */ typedef struct pm_sk_list_t { struct pm_sk_list_t *next; INT32 sk; } PM_SK_LIST_T; static PM_SK_LIST_T *sk_list_app = NULL; /* pm app event structure */ /* app message = header + msg_body */ typedef struct { UINT16 msg_id; UINT16 msg_len; pid_t pid; } PM_APP_EVENT_H; /* this event type is used when checking message */ typedef struct { PM_APP_EVENT_H msg_h; UINT8 *msg_buf; } PM_APP_EVENT_T; INT32 main(INT32 argc, INT8 **argv) { INT32 sk, sk_app; struct sockaddr_un str_addr; socklen_t len_addr; char *env; fd_set frds, bak_frds; INT32 maxfd; INT32 i; INT32 length; UINT8 event_buf[EVENT_BUF_SIZE]; struct sigaction alarm_act; /* * open apm_bios device */ apm = open("/dev/amp_bios", O_RDWR); if (apm sk = sk; PM_list_add((PM_LIST_T **)&sk_list_app, (PM_LIST_T *)new); FD_SET(sk, &bak_frds); maxfd = MAX(maxfd, sk); printf( "accept connection: socket = %d, malloc mem at 0x%x
", sk, (unsigned long)new); } } } /* * check events from kernel space */ if (FD_ISSET(apm, &frds)) { apm_event_t *krnl_events = (apm_event_t *)event_buf; length = read(apm, krnl_events, sizeof(event_buf)); PM_check_krnl_event(krnl_events, length); } /* * check events from other application */ for (tmp = sk_list_app; tmp != NULL ; tmp = tmp->next) { if (FD_ISSET(tmp->sk, &frds)) { PM_APP_EVENT_T *app_msg = (PM_APP_EVENT_T *)event_buf; /* this function will get one message */ length = PM_read_app_event(tmp->sk, app_msg); /* * if count is less than 0, the socket will be closed */ if (length sk, &bak_frds); PM_list_del((PM_LIST_T **)&sk_list_app, (PM_LIST_T *)tmp); /* adjust maxfd */ if (tmp->sk == maxfd) { PM_SK_LIST_T *tmp1; maxfd = MAX(apm, sk_app); for (tmp1 = sk_list_app; tmp1 != NULL; tmp1 = tmp1->next) maxfd = MAX(tmp1->sk, maxfd); if (tapi_srv_fd >= 0) maxfd = MAX(tapi_srv_fd, maxfd); } printf( "close connection: socket = %d, free mem at 0x%x
", tmp->sk, (unsigned long)tmp); PM_cancel_app_vote(tmp->sk); close(tmp->sk); free_entry = *tmp; free(tmp); tmp = &free_entry; } else { PM_check_app_event(tmp->sk, app_msg, length); } if (app_msg->msg_buf != NULL) free(app_msg->msg_buf); } } /* check events from application */ } return 0; } /* * check app event */ INT32 PM_check_app_event(INT32 sk, PM_APP_EVENT_T *p, INT32 size) { INT32 i = 1; switch (PM_status.ap_state) { case IDLE: case SLEEP: break; case NORMAL: PM_exit_normal_by_app(sk, p); break; case OFF: break; default: break; } return i; } /* * make transition from normal when app event incoming */ void PM_exit_normal_by_app(INT32 sk, PM_APP_EVENT_T *p) { switch (p->msg_h.msg_id) { /* app request power off */ case PM_APP_REQUEST: if (p->msg_h.msg_len == sizeof(PM_APP_REQUEST_T)) { PM_APP_REQUEST_T app_request; memcpy(&app_request, p->msg_buf, sizeof(PM_APP_REQUEST_T)); switch (app_request) { /* request power off */ case PM_APP_REQ_POWER_OFF: poweroff(); break; default: break; } } default: break; } } } void poweroff() { ioctl(apm, APM_IOC_WAKEUP_DISABLE, 0x8000ffff); ioctl(apm, APM_IOC_POWEROFF, NULL); } 在内核中有名为amp_bios的设备及对应的驱动程序(在arch/arm/mach/amp.c中),列出相关代码如下: static int __init apm_init(void) { …… pm_active = 1; create_proc_info_entry("apm", 0, NULL, apm_get_info); misc_register(&apm_device); pm_power_off = pm_do_poweroff; return 0; } static struct file_operations apm_bios_fops = { owner: THIS_MODULE, read: do_read, poll: do_poll, ioctl: do_ioctl, open: do_open, release: do_release, }; static struct miscdevice apm_device = { APM_MINOR_DEV, "apm_bios", &apm_bios_fops }; 函数do_ioctl根据不同的命令选项,调用函数pm_send将请求发给各个电源设备结构的callback函数来处理,以达到控制电源的作用。 static int do_ioctl(struct inode * inode, struct file *filp, u_int cmd, u_long arg) { struct apm_user * as; struct pm_dev * pm; …… as = filp->private_data; if (check_apm_user(as, "ioctl")) return -EIO; if (!as->suser) return - EPERM; switch (cmd) { case APM_IOC_SUSPEND: pm_do_suspend(CPUMODE_SLEEP); break; case APM_IOC_SET_WAKEUP: if ((pm = pm_find((pm_dev_t)arg,NULL)) == NULL) return -EINVAL; pm_send(pm,PM_SET_WAKEUP,NULL); break; …… } ……