0%

转载-Linux2.6.6内核下ACPI PCI Hot-Plug的实现机制(上)

本文转载自 https://www.ibm.com/developerworks/cn/linux/l-acpi/part1/index.html

一.ACPI 热拔插的简介

由 INTEL,MICROSOFT 及 TOSHIBA 所共同开发而成的 ACPI(Advanced Configuration & Power Interface,先进架构电源配置标准)能使软、硬件、操作系统(OS),主机板和外围设备,依照一定的方式管理用电情况,系统硬件产生的 Hot-Plug 事件,让操作系统从用户的角度上直接支配即插即用设备,不同于以往直接通过基于 BIOS 的方式的管理。
这种技术对系统平台、外插板卡硬件上都有特定的要求:系统集成热插拔控制集成电路(PHP ASIC)和 PCI 插槽的逆电流控制器,这样在系统启动过程中可以自动监测 PCI 插槽上是否有设备,当探测到 PCI 插槽上无设备时,能够自动将该插槽断电,在系统的 ACPI BIOS 中包含一系列硬件存储空 PCI 插槽的资源信息如地址段、中断号等以便提供给 Hot Plug 的插卡所用的资源列表 ACPI Table,这些资源列表在上电之后用于进行 PCI 设备的枚举和配置,目前南桥芯片上都集成了 ACPI 协议,例如在 Intel 82801DB I/O Controller Hub 4(ICH4)。
ACPI 基本的体系结构如图 1 所指示:
ACPI 系统由主板总线系统的电气特性支持、主板 BIOS 支持、ACPI 层、操作系统热插拔功能的总线驱动构成。


随着 Intel 64 位 PCI 技术的成熟,Microsoft、Novell 和 SCO 开发的操作系统都开始全面支持 PCI设备 ACPI 热插拔技术(PCI Hot Plug)。
Linux 内核从 2.4 开始支持 ACPI 技术,而到了 2.6.0 以上版本的内核全面支持 ACPI 下的 Hot-plug 规范;根据这个基础,在下面,我们将要从底层的角度和应用的角度上详细介绍基于”传统的”x86 体系下 ACPI PCI 热插拔设备 Linux kernel 下的驱动构成和 ACPI 层上工作模式和流程(在上半部分将介绍基本原理和命令形模式),向非 x86 平台移植的注意事项,以及一些 Linux ACPI 热拔插设备驱动程序开发的注意事项。

二.ACPI 驱动体系简介

ACPI 驱动体系是支持 ACPI Hot Plug 的基础,在论述 Hot Plug 之前首先要介绍 ACPI 体系,根据ACPI 规格定义的 ACPI 驱动体系(简称 ACPI CA),目前 ACPI 组织已经提供了完整的 Unix 版本的ACPI 驱动体系,这个体系主要目的在于让操作系统和当前的 ACPI 硬件隔离开,让 Linux 中通过一系列的接口来访问 ACPI 层。下面列出 ACPI CA 的接口,例如电源管理和配置,热拔插等等:
在 ACPI 规范中将 ACPI 体系分割成 ACPI 核心层(Core subsystem),用于提供基本的 ACPI 服务(AML翻译和命名空间管理);OS 服务层(OS service)提供针对不同的操作系统的和 ACPI 单元接口服务,下面将详细介绍它们。
a. 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 状态寄存器和使能寄存器,系统状态获得。 Event handling:事件管理模块是用于管理系统控制中断(SCI)的发生和 GPE 事件的响应,SCI 包括 ACPI 时钟中断,以及 GPE 事件管理。这个单元负责”分发”地址空间和操作空间(OperationRegion)的事件到当前的操作系统层,并负责调用相关的句柄来进行处理。

b. OS 服务层
ACPI OS 服务层(OSL)可以让 ACPI 逻辑模块在本地操作系统上运行。OS 服务层通过可在主机操作系统中使用的接口,设备驱动程序,将从 ACP 核心的服务转换成本地操作系统的访问和调用;而操作系统层通过 OSL 向 ACPI 核心层发出呼叫;OSL 层对 ACPI 核心层实现了一系列完成操作系统独立功能的标准接口(例如存储分配和硬件访问)。 OSL 的组成模块介绍:

  1. OS引导服务:在OS 载入过程中引导服务是一些初始化的功能,在大多数其它的操作系统初始化之前执行。这些服务包括 ACPI 子系统的初始化。
  2. 设备驱动载入服务: 对于出现在 ACPI 命名空间中的设备节点,操作系统必须有一个模块用以探测到它们并载入驱动,读入配置空间,设备驱动载入服务提供这项装置。
  3. 操作系统运行服务: 运行服务包括大部分 ACPI 系统和 OS 交互的外围接口,用于当前内核的进程/线程操作,以及提供和当前操作系统接口的互斥,信号,进程队列,休眠,暂停等,以及事件日志及电源管理功能。
  4. 异步服务:异步功能包括中断服务(系统控制中断),事件处理和分配(既定事件,GPE 事件,通知事件和操作区访问事件),以及错误处理。

从 OS 至 ACPI 子系统的请求:ACPI 核心层和 ACPI OS 层,以及操作系统之间的关系如下:

从上面我们看到了在 Linux2.6.6 中提供了一个完整的符合 ACPI 规范的 ACPI 核心层和 OS 接口函数,不过我们下面论述到的 ACPI PCI 设备热拔插只会用到其中的很小一个部分。

三.ACPI 体系中的重要名词

DSDT: DSDT 称做 Differentiated Definition Block,存在于 BIOS 中并与当前的硬件平台兼容的,提供了系统的硬件特性(例如某些设备的内部寄存器和存储器)的应用策略和配置,在系统初始化的时候,DSDT 被当前系统启动时初始化到命名空间中。
FADT:FADT 中包含了 ACPI 的硬件寄存器组(GPE)的应用和配置(包含它们的硬件地址)也包括DSDT表的硬件地址。
ACPI Namespace: 对于ACPI层来说,内存维持了一个目录形式的指向每个设备,以及 GPE 的命名空间,这个名字树是通过初始化的时候由 DSDT 创建的,名字树可以通过 loadtable 方法从 BIOS 中载入 DSDT 改变,而每个设备在 ACPI 层中都被描述成一个对象,包含有对这个设备特性和操作策略的描述列表,系统所有类型设备都是保存在同一个名字树下。在 ACPI OS 层上调用 _ADR 来获得 Namesapce 的设备名,Namespace 的例子见例 1-1:
OSPM(OS-directed Power Management):OSPM 操作系统支持 ACPI 的一个部分,操作系统 (OS)可以从操作系统下驱动程序的角度控制 ACPI 子模块,同时支持 ACPI 包括 SCI 中断,设备事件,系统事件模式,这些事件模式可以充分支持 Hot-plug 方式。
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 层运行相关的控制程方法通知 ACPI 层;GPEx_EN 表示每个事件的使能位,一般说来在南桥(ICH4)中有这几个寄存器,它们的硬件地址保存在 FADT 中。GPE 事件就是通过 GPE 寄存器组引发 SCI 中断后,通告 OSPM 层有关设备的事件,例如下面介绍 Hot-Plug 的时候会详细或者简略地介绍到总线枚举,设备检查,设备唤醒,设备弹出几个事件。


ACPI Source Language(ASL):ASL 语言是 ACPI 层用于描述特定的 ACPI 对象的 ACPI 专用语言,并且包括了 ACPI 对象的控制方法(Control method),OEM 厂商和 BIOS 设计者在 BIOS 中使用 ASL 定义所有的设备为 ACPI 对象,并且可以生成 ASL 格式的专门的控制方法,1-1 例就是关于 ASL 的例子:
ASL 的语法规参看 ACPI Specification Revision 2.0
AML 和 AML 分析器:AML 是 ACPI 控制方法的虚拟机器语言,AML 执行过程也就是 ACPI 核心驱动层,ACPI 控制方法使用 AML 来进行编写,但是通常而言对编写者来说是写成 ASL 的方式,通过 AML 翻译器进行翻译,AML 翻译器不但具备 ASL 的翻译的功能,而且可以执行 AML 方法,当用 ASL 编写的 DSDT 表被载入到命名空间的时候,将会被 AML 翻译器翻译成执行时候可以辨别的机器码,例如关键字 SCOPE 在进入 AML 编译器之前中是以一个 ACSII 编码保存在 DSDT 中,但 DSDT 被载入命名空间之后将变成 0x10 的单字节数值(AML 操作值为 ScopeOP)。对 AML 的编译过程和转换方式,ASL 中的关键字可以参看 ACPI Specification Revision 2.0 中 section 17 。

四.在 Linux 中的 ACPI 的 Hot-Plug PCI 工作流程

A.Hot-Plug 的执行简述:
首先在系统上电之后,在系统初始化过程中会通过载入 DSDT 并在内存中动态建立命名空间,在命名空间中,针对每个设备都有响应的”方法节点”以描述设备相对应的具体执行操作,(热拔插事件的处理过程在设备命名空间中为一个方法节点,如下面例子中 Method(LXX){… …}部分)。以下是一个热拔插的 CD ROM 设备在命名空间的运行方法描述例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Device(IDE0) // PCI IDE控制器
_ADR 0x40001 // PCI 设备4功能模块1
_SUN {} //IDE控制器的状态查询方法
Device(PRIM)
_ADR 0 // IDE设备的第一通道
Device(SECD)
_ADR 1 // IDE设备的第二通道
Device(CDRM)
_ADR 0 // IDE设备的第二通道
//上的CDROM设备
_EJ0 {
// 针对IDE设备的第二通道所要拔除的设备的方法节点,一般上对其停止供电
}
_STA{
}
_GPE //命名空间中对GPE Block的表示方法
Method(_Lxx) { // 某个GPEx_STS的状态位发生变化时的响应处理方法节点(以下为插入
//事件的执行方法)
Sleep(250) // 延迟250微秒
If (InsertionEvent) {//检查是否为插入事件
PWRN() // 如果是设备插入,就给设备上电
}
Notify(CDRM, 1)//向上层(操作系统的驱动系统层,以下简称驱动系统层)通告检测到
//了CDRM设备插入,那么需要唤醒相应的驱动系统层的PCI设备初
//始化程序。
}

** 1-1. 命名空间的例子**
命名空间中对设备对象的访问状态:对于命名空间中的设备对象,可以采用以下的方法来访问和Hot-Plug相关的部分,对当前PCI插槽上设备的操作可由操作系统的驱动层主动向ACPI层发起:

  • _ADR方法来获得命名空间中的PCI设备(Slot)的地址,返回的地址高16位为Slot号,低16位为Function号。
  • _EJ0控制方法来运行设备的热拔出,当使用ACPI核心层接口acpi_evaluate_object调用这个方法的时候,设备就从系统中拔除,它可以调用马达,指示灯,螺线管等等设备。
  • _STA控制方法,获知设备的当前状态,调用该方法以验证是否设备在插槽之内。
  • _SUN方法指示在物理插槽中有哪个设备(功能模块)在系统中。
  • _PS0和PS3这些可选的控制方法开启和关闭对应插槽上的功能设备。

如果设备状态模式检测的信号脚已经连接到对应的GPEx_STS的位上,这时候命名空间中GPEx_STS位的事件要使用ASL编写的_LXX(用于表示某个GPE事件中GPEx_STS状态响应位来指明当前的设备插入事件)来描述事件执行策略。并通过初始化节点对象绑定通告函数(Notify Handler)通告到OSPM执行相关设备的PCI枚举操作,以下是设备插入后所发生的事件:

  1. 用户插入PCI热拔插设备到Slot内;
  2. HPPC(南桥中热插拔控制器)生成GPE事件;
  3. ACPI核心控制芯片组产生SCI中断;
  4. ACPI硬件会清除这个GPE事件的响应位,并且运行对应GPE位在命名空间中的_LXX控制方法;
  5. _LXX控制方法将根据HPPC的相应控制位来决定当前事件是否为一个设备插入事件,以及哪个插槽有设备插入;
  6. _LXX控制方法将向驱动层发出通告(Notify)表示当前的PCI总线上的某一个插槽有设备插入;
  7. ACPI驱动层(这里开始就进入ACPI OS层部分了)运行_STA方法获得第6步所知的设备的当前状态,如果运行_STA方法返回0x0a表示当前设备不能使能。
  8. ACPI驱动层告诉PCI层准备对新插入的设备进行枚举;
  9. PCI设备层读入相关设备(PCI功能模块)的配置信息;
    1. 对该PCI卡上所有设备根据设备制造厂商获得相关的驱动程序;
    2. 使能PCI卡上所有的功能模块;
    3. ACPI驱动层运行_PS0控制方法,根据PCI设备的电源管理规范,使能PCI设备上的电源管理状态寄存器,使PCI设备上电;
    4. 正常运行/访问设备。

现在大家对热拔插的模式已经清楚了,下面先从底层,也就是AML翻译器的对命名空间的执行模式开始着手,介绍当前设备在热拔插过程中对驱动层发布命令和GPE事件的响应过程之执行方式,包括:

  1. 从操作系统驱动层向ACPI底层如何发布一个ACPI命令,及其执行过程,也就是执行型命令;
  2. 一个GPE消息引发一个SCI中断的时候ACPI层的响应过程,也就是事件通告/响应型模式;
  3. 如何移植一个ACPI到另外的平台(非x86平台),需要对当前的ACPI 和硬件平台相关的部分做什么样的修改;
  4. ACPI中AML的翻译器翻译和执行AML编码流过程。

    阅读下文的时候请对照Linux ACPI的源代码一同阅读:

五.例子:执行型 ACPI 命令模式

例子 :ACPI 的核心层调用接口:status = acpi_evaluate_object
(func->handle, “_EJ0”, &arg_list, NULL)
简介:
acpi_evaluate_object 为 ACPI 核心层的调用接口,为用户提供核心层的调用,代入的参数是设备的设备名称,可以在命名空间中把它看作设备在命名空间中的路径,按照该名称可以找到对应设备所在的名字节点(需要执行该方法进行的设备),另外的参数就是参数列表和方法名,例如对于设备的”_EJ0”方法而言,带入的参数为一个整型值,为1表示设备弹出,而方法名就是用于定位设备节点中的方法节点的。

  1. 我们以下面的例子来解释如何执行一个方法节点,例如图 5 中的 EJ0 方法中使用了 “STORE” 操作符表示执行设备弹出的时候需要针对控制寄存器 REGC 写入一个控制值:
    1
    2
    3
    Method(_EJ0, Arg1) {    
    STORE (Arg1,REGC)
    }

例1-2. 方法节点_EJ0

  1. 调用 acpi_ns_lookup 在命名空间(Namespace)中定位设备节点和需要执行的方法在设备所对应的节点(如图 5中的方法节点),该方法的节点在名字空间节点中以 acpi_namespace_node 的数据结构形式返回(见图 5)。

  2. 锁定名字空间的这个节点,避免有其他进程对当前解析节点的修改。

  3. 从设备节点中获取当前操作的对象描述数据结构,每个操作节点都有相对应于一个 union acpi_operand_object object 的枚举类集合用于对设备的所执行的具体方法和带入参数进行描述,包含了所有类型节点的对应对象,例如 _EJ0 所对应的是一个方法对象 struct acpi_object_method,对象中有两个重要的字段aml_length和aml_start,分别表示该对象在名字空间中AML语言描述的位置和长度,以便在分析运行该方法之前对该方法的定位和扫描(见图5)。

  4. 创建一个工作状态结构struct acpi_walk_state *walk_state。

  5. 第一次扫描首先获得的第一个操作符是MethodOP(AML编码为0x14),在walk_state中有三个重要字段对当前的操作和带入的参数进行描述:第一个是opcode,表示当前进行操作的操作符,例如上述的例子是MethodOP;另外一个是op_info表示的当前操作符的描述集合,在(\DRIVERS\ACPI\parser\psopcode.c)有一个数组acpi_gbl_aml_op_info是这些操作符的特性参数集合,例如操作符的序列号(也就是AML的编码),操作符号名,操作符的对象类型,例如MethodOP的对象类型是ACPI_TYPE_METHOD,参数类型等;第一次扫描的时候从这个特性参数集合中获取当前节点的操作的对象参数acpi_operand_object,最后一个在在walk_state的重要的字段是操作数数组:union acpi_operand_object *operands [ACPI_OBJ_NUM_OPERANDS+1],前面介绍过了;上例中包含了两个操作对象, “Method”和”STORE”,第一次扫描过程获得当前执行方法内所有的操作对象,在执行第一次扫描的时候载入work_state中的operands字段这些操作对象,同时包含执行方法所需要的参数也同时带入到operands数组中;其载入过程是非常复杂,针对每种类型的对象(例如整型和IO空间等类型)的合法性校验以及不同的载入操作。

    上述的例子中”STORE”操作节点在载入walk_state以后,opcode为0x14,而operands[0]为Arg1名字空间节点所 表示的对象,operands[1]为寄存器REGC名字空间节点所表示的对象(寄存器长度以及物理基址)。上面的例子是非常简单的,只有两个操作对象;更多执行方法包含有复杂的操作对象,包括IF,WHILE之类的抉择性分析,以及FOR之类的条件循环。第一次扫描类似于一种合法性检查。

  6. 有了第一次扫描获得的操作对象集合,就开始第二次扫描,这次扫描的方式只是重新对已经找到的操作节点扫描。

  7. 现在就是执行这些操作了,我们从上述例子中知道最简单的一个操作至少要包含一个操作对象,在这个例子中是StoreOP,执行这些操作的时候,而真正的执行过程是STORE (Arg1,REGC),这个需要执行的操作是带有参数的,那么首先就需要把参数放在operands中;接下来对操作对象集合中的元素进行整理,并且进行合法性校验,这个步骤比较复杂,关键是涉及到各个操作符和参数对象之间的关联性和合法性的检测。

  8. 最后一个步骤就是把已经经过上述步骤整理好的操作对象集合operands发布到AML分析器执行程序列表中去,这个执行程序列表实际为一个数组,数组中是对应各种操作对象执行函数指针,选择这些函数事实上是根据前面所叙述的操作符的描述集合op_info获得当前操作对象的特性,例如我们看到在acpi_gbl_aml_op_info中StoreOP操作符所指向的特性为AML_TYPE_EXEC_1A_1T_1R,那么对应于当前的STORE这个操作符执行函数为acpi_ex_opcode_1A_1T_1R。

在acpi_ex_opcode_1A_1T_1R这个函数中有对STORE的具体执行过程–将当前的参数值存储在指定的名字空间区域的IO区域内,这个IO区域在第一次扫描DSDT时检索到OperationRegion(用于表示硬件空间的地址,包括IO端口,硬件设备的寄存器组等等)构筑名字空间的时候会建立(参见下面的图5),这个过程可以参看acpi_ex_store函数,这个函数最后会落实到调用acpi_ex_field_datum_io函数中,在此可以看到一个buffer类型的端口的操作,这种端口的操作类型是acpi_object_buffer_field,里面有一个字段所指向的具体的名字空间内的IO地址,最后会调用ACPI内部的一个宏把值拷贝到这个地址上。具体内容就不在这里详细叙述了。