ACPI SpecV3.0学习总结

本文非原创文章,是对网上资料的整理,但忘了出处,在此未标明源地址表示抱歉。


1.涉及的TABLE

SDTH   : System Description Table Header (这个不是Table,它是每个Table都包含的头)

RSDP   : Root System Description Pointer  ('RSD PTR')

RSDT    : Root System Description Table  (signature is 'RSDT')

FADT    : Fixed ACPI Description Table  ('FACP')

FACS    : Firmware ACPI Control Structure ('FACS')

DSDT    : Differentiated System Description Table  ('DSDT')

PSDT    : Persistent System Description Table  ('PSDT')(它是1.0版本定义的,早已被移除)

SSDT    : Secondary System Description Table  ('SSDT')

MADT    : Multipile ACPI Description Table  ('APIC')

SBST    : Smart Battery Table  ('SBST')

XSDT    : Extended System Description Table  ('XSDT')

ECDT    : Embedded Conroller Boot Resources Table  ('ECDT')

SLIT    : System Locality Distance Information Table  ('SLIT')

SRAT    : System Resource Affinity Table  ('SRAT')

MCFG    : PCI-Memory Mapped Configuration table and sub-table  ('MCFG')

SPCR    : Serial Port Console Redirection table  ('SPCR')

BERT    : Boot Error Record Table  ('BERT')

SBFT    : Simple Boot Flag Table  ('BOOT')

CPET?    : Corrected Platform Error Polling Table  ('CPEP')

DBGT?    : Debug Port Table  ('DBGP')

DMAT?    : DMA Remapping Table  ('DMAR')

TCPT?    : Trusted Computing Platform Alliance Table  ('TCPA')

WDRT    : Watchdog Resource Table  ('WDRT')

ASFT?    : Alert Standard Format Table  ('ASF!')

    

注:具体见ACPI规范3.0bTable 5-6. 这里只是一部分。另外, ACPI定义的所有tablesblocksstructures,全都是little-endian

2.术语

ASL        : ACPI Source Language

AML        : ACPI Machine Language

DSL        : Digital Simulation Language

E820       : a system memory map protocol, provided in ACPI spec, ch14 for 3.0b

EFI        : Enhanced Firmware Interface

HPET       : High Precision Event Timer

GPE        : General-Purpose Event

GSI        : Global System Interrupts

OSL        : OS Service Layer

OSPM       : OS Power Management, LinuxOS中实现对ACPI支持的代码

PRT        : PCI IRQ Routing Table

PXE        : Preboot Execution Environment

SAPIC      : Streamlined APIC, IA64上使用的APIC。其local SAPICI/O SAPIC分别对 应着IA32 x86-64上的Local APICI/O APIC

SBF        : Simple Boot Flag

SCI        : System Control Interrupt  (OS-visible interrupts, triggered by ACPI events)

SMBIOS/DMI : System Management BIOS/Desktop Management Interface. PCBIOS规范。

TOM        : Top Of Memory

UUID       : Universal Uniform IDentifiers

xface      : Linux内核ACPI源文件的命名法,表示Interface。例如tbxface.c实现table的接口。

3.具体表介绍

3.1 RSDP, RSDT and XSDT

    RSDP是位于系统内存地址空间中的,它的值由firmware设置。

    RSDP包含了两个指针(还有其他字段,见Linuxstruct acpi_table_rsdp),分别保存着RSDT表的物理地址(32), 和XSDT表的物理地址(64)

 

    Linux寻找RSDP的代码见acpi_os_get_root_pointer()函数。

 

    ACPI2.0+开始,XSDT就取代了RSDTRSDTACPI 1.0中的,现代的OEM厂商一般也还提供RSDT,但那只是为了向后兼容ACPI-1.0而已。 或者用RSDT,或者用XSDT,不管选择用那个,它都包含了其他ACPI表数组的地址。

 

Linux下这2者的定义是:

struct acpi_table_rsdt {

struct acpi_table_header header;    /* Common ACPI table header */

u32 table_offset_entry[1];        /* Array of pointers to ACPI tables */

};

 

struct acpi_table_xsdt {

struct acpi_table_header header;    /* Common ACPI table header */

u64 table_offset_entry[1];        /* Array of pointers to ACPI tables */

        /* FIXME: 为什么数组大小是1*/

};

3.2 ACPI Table Header

ACPI所有的描述表(FACS表除外?Linuxacpi_table_facs结构不包含headerACPI规范中也没有)都包含着一个Header, 所有描述表的Header结构都是一样的。Linux把它定义为:

 

struct acpi_table_header {

char signature[ACPI_NAME_SIZE];        /* ASCII table signature */

u32 length;                /* Length of table in bytes, including this header */

u8 revision;                /* ACPI Specification minor version # */

u8 checksum;                /* To make sum of entire table == 0 */

char oem_id[ACPI_OEM_ID_SIZE];        /* ASCII OEM identification */

char oem_table_id[ACPI_OEM_TABLE_ID_SIZE]; /* ASCII OEM table identification */

u32 oem_revision;                /* OEM revision number */

char asl_compiler_id[ACPI_NAME_SIZE];      /* ASCII ASL compiler vendor ID */

u32 asl_compiler_revision;            /* ASL compiler version */

};

3.3  (RSDT/XSDT之外的) ACPI描述表数组的第一个:FADT

这个数组的第一个元素由XSDT中的table_offset_entry[0]指定位置。被指向的第一个表通常是FADT(Fixed ACPIDescription Table),它的几个主要作用:

        

1) 保存着FACSDSDT表的地址(64位也是)

2) 包含一些entries,每个entry有固定的length,描述一个硬件的ACPI feature(我的理解就是,硬件对ACPI的支持程度)

 

LinuxFADT的定义见struct acpi_table_fadt结构。

3.4 DSDT(Differentiated System Description Table)

    

FADT中的字段指向,见上一条笔记。

    DSDT表包含一个Definition Block,叫作'Differentiated Definition Block for the DSDT',它包含了实现与配置信息(implementation and configuration information)OSPM用这些信息来实现:电源管理,热量管理,以及(ACPI硬件寄存器所描述的信息之外的)即插即用。

 

    //FIXME: 奇怪的是,Linux没有提供一个struct acpi_table_dsdt 结构的定义,只提供了一个 acpi_system_read_dsdt()函数。

    //FIXED: 注意,这个acpi_system_read_dsdt()函数不是内核用来load DSDT表的,而

    是为了让用户程序(例如cp)通过/proc/acpi/dsdt来读取DSDT表的(in AML format),以便给内核开发者诊断问题。

    很多platform vendor提供的BIOS,特别是笔记本上,其DSDT表往往有BUG,导致Linux的电源管理出现问题。

    Linux内核提供了CONFIG_ACPI_CUSTOM_DSDTCONFIG_ACPI_CUSTOM_DSDT_FILE配置选项,允许用户自己提供DSDT文件,替代BIOS中有BUG的那个。

 

    这个用户自己提供的DSDT文件,其实就是一个AML语言描述的数据块。 可以用pmtools中的acpidumpacpixtract程序把ACPI的表从BIOS中提取出来,再用iasl工具(Intel ACPICA中的ASL编译器、AML反汇编器)反汇编它,修正BUG,然后编译。这个原理,参考Linux内核的acpi_os_table_override()函数。

3.5 SSDT

    它是DSDT表的扩展。如果有这个表,RSDT/XSDT中就有指向它的指针。 SSDT表可能有不止一个。Definition Blocks, 都是定义在DSDT或者SSDT(s)表里的。其它的表没有 定义块。

3.6 FACS(Firmware ACPI Control Structure)

    DSDT表一样,FACS表也由FADT表中的字段指向。见笔记<3>

3.7 CPUIdle States (C-states)

    C0C。一般来说,都是共有从C0C45个状态。 C0是正常状态,即CPU全频率工作,不省电。

    n越大,CPU越省电,但从它转到C0的所需时间也越长。

    Linux下,可以通过kernel parameter:  processor.max_cstate来改变的上限。

3.8 CPUPerformance States (P-States), CPU频率管理

    P0PP0时,CPU以最高主频工作;其他状态下,n越大,CPU频率越低。

    注意,只有C-State处于C0状态时,这些P-States才是有意义的。 如果CPU处于C状态(n>0),调整它的 频率没有任何意义。

3.9 CPUThrottling States (T-States)

    Throttling是个什么意思呢?--CPU每秒多少个时钟周期s,我们称之为Cycles Per Seconds (CPS)。可以让CPU在一个CPS中有一定比例的空闲时间。

    T-StatesT0T。 当处于T0态时,CPU没有空闲周期;其他状态则n越大,CPU1秒钟内的空闲周期数越多。

    除了T0外的T-States,和“7, CPUPerformance States (P-States), CPU频率管理”不同,后者只是"让每秒的周期数少一些"; 它也和CPU的空闲状态(例如C1状态)不同,后者是CPU1秒钟里所有的周期都是空闲的。

    Throttling不会降低电压,它只是让每秒钟都有几个时钟周期是空闲的。--注意,给定一个工作量,Throttling会导致CPU需要更长的时间来完成它,从而耗费更多的电。(电压不变,时间加长,自然耗电变多)

3.10.  CPU"Sleep States" From USENIX:

in ACPI there are 6 power state:

S0, S1, S2, S3, S4, and S5.

S0    : The Running state.

S1    : the suspend state. in this state, the CPU will suspend activity but remains its

  contexts.

S2 & S3    : sleep states. in these states,memory contexts are held but CPU contexts are lost.

  the difference between S2 and S3 are in CPU re-initialization done by firmware and

  device reinitialization.

S4    : a sleep state in which contexts are saved to disk. the context will be restored

  unpon the return to S0. This is identical to soft-off for hardware. This state can

  be implemented by either OS or firmware.

S5    : the soft-off state. All activity will stop and all contexts are lost.

 

ACPI规范说,还规定了另一个说法,叫作Global System State:

 

G0 == {S0}        : Working.  

G1 == {S1, S2, S3, S4}    : Sleeeping.

G2 == {S5}        : Soft off

G3            : Mechanical off

3.11 MADT, Multiple APIC Description Table

     ACPI规范认为,一共有3种中断模型: Dual-8259, APICSAPICFirmware可以同时提供对它们的支持,但OSPM只能选择一个。

3.11.1  LinuxMADT表的结构定义为:

struct acpi_table_madt {

struct acpi_table_header header;

u32 address             /* physical address of local APIC */

u32 flags;

};

 

其中MADT表在44字节偏移处,有个APIC Structure[n]结构数组,每个元素的第一个字节是该Structure的类型,第二个字节是该Structure的长度。Linux把这两个字节封装成了一个acpi_subtable_header结构:

struct acpi_subtable_header {

u8 type;

u8 length;

};

3.11.2 APIC Structure的类型有:

0    : Processor Local APIC

1    : I/O APIC

2    : interrupt Source Override

3    : Non-maskable Interrupt Source(NMI)

4    : Local APIC NMI Structure

5    : Local APIC Address Override Structure

6    : I/O SAPIC

7    : Local SAPIC

8    : Platform Interrupt Sources

9-127    : Reserved. OSPM skips structures of the reserved type

128-255    : Reserved for OEM use

 

08的这九个结构,在Linux下分别是:

 

0, acpi_madt_local_apic,

 

1, acpi_madt_io_apic,

 

2, acpi_madt_interrupt_override,

   对于同时支持APICdual-8259A两种中断模型的平台,GSI中断的0-15必须被映射到8259A0-15引脚上。如果不是这样,那么就必须提供Interrupt Override

   例如,如果你有一个ISAPIT,连接到ISA IRQ 0;但是在APIC模式下,它连接到I/O APIC IRQ 2。你就需要提供Interrupt Source Override,在source entry == 0处,指定其GSI2

 

3, acpi_madt_nmi_source,

   该结构指定,I/O APIC的中断输入中,哪些应被当作NMI

 

4, acpi_madt_local_apic_nmi,

   该结构指定,每个CPULocal APIC,其中断输入(LINT),哪一个连接的是NMI

5, acpi_madt_local_override,

   MADT中的addresslocal APIC的地址,32位。为了提供对64位系统的支持,可以提供local APIC Address Override Structure,这样local APIC的地址就可以被Override64位。

 

6, acpi_madt_io_sapic,

   IA64I/O SAPIC

 

7, acpi_madt_local_sapic,

   IA64Local SAPIC

 

8, acpi_madt_interrupt_source.

   用来管理PMI(Platform Management Interrupts). PMIIA-64上的,跟IA32SMI类似。

 

这几个结构,可以参考Linux找到它们时打印信息的代码来理解其结构:acpi_table_print_madt_entry()函数。

3.11.3  Local APIC/SAPICentry order:

为了POST以及其后的正确引导, 有两条规则应该遵守:

1). OSPM应该以 处理器在MADT表中出现的顺序来初始化它们

2). Firmware应该把MADT中的第一个Entry列为引导处理器BP

 

对于多线程处理器,道理是一样的:

1.  OSPM应该以 逻辑处理器在MADT表中出现的顺序来初始化它们

2Firmware应该把每个处理器的第一个逻辑处理器,在MADT表中

列在任何2nd逻辑处理器之前。

3.11.4 参考9.1/9.2中的信息,看看Linux下对local APICI/O APIC的定义:

/* 0Processor Local APIC */

struct acpi_madt_local_apic {

struct acpi_subtable_header header;

u8 processor_id;    /* ACPI processor ID */

u8 id;            /* Processor's local APIC id */

u32 lapic_flags;

};

 

/* 1: IO APIC */

struct acpi_madt_io_apic {

struct acpi_subtable_header header;

u8 id;            /* I/O APIC ID */

u8 reserved;        /* reserved. must be zero */

u32 address;        /* IO APIC physical address */

u32 global_irq_base;    /* Global system interrupt where INIT lines start */

};

 

Linux上,引导时通过acpi_parse_madt_lapic_entries()acpi_parse_madt_ioapic_entries()两个函数来获取BIOSACPI提供的Local APICI/O APIC的信息。

3.11.5 多个IO-APIC

 I/O APIC有一或多个。在ACPI-enabled系统中,有两种中断模型:APIC模型和PC-AT的一对主从8259A 模型。

 APIC模型下,每个IO APIC支持的irq输入数目,可以是不同的, OSPM读取每个IO APICMax Redirection Register来获取这个数目.

 参考acpi_madt_io_apic结构,如果一个io apic是系统中的第二个io apic,而第一个io apic的中断数目是24, 那么第二个io apicglobal_irq_base就是24 -- 亦即,第二个io apic的中断输入从第24开始递增。

3.12. SRAT(System Resource Affinity Table)

    该表可选.

3.13 E820及其它(ACPI spec, CH14)

    传统的PC使用INT 15H这个BIOS调用来获得内存布局; EFI的系统,则可以通过EFI的一个boot serviceGetMemoryMap()函数,来获取内存布局.

OS loader获取的memory map,然后把它传递给OSPM

 

1). ACPI定义了5种类型的内存范围:

Value  

Mnemonic

Description

1

AddressRangeMemory

该范围是OS可用的RAM

2

AddressRangeReserved

system使用或保留,OS不得使用

3

AddressRangeACPI

ACPI占用的内存。当OS读取了ACPI Tables后,它就可以使用这块RAM了。

4

AddressRangeNVS

ACPI NVS内存

5

AddressRangeUnusuable

该范围的内存有错误,OS不可使用。

Other

Undefined

未定义,即保留给将来。OSPM应该象对待AddressRangeReserved一样对待它。

    

 

2). INT 15H, E820H - 查询memory map    

只能在real mode使用。 "int 15, e820h"的意思是,给EAX赋值e820h,然后int 0x15来调用BIOS15号调用。

PC机上一共有3memmap探测方法,Linux采用的顺序是:先使用e820h,然后使用e801h,然后使用88h

 

e820h    : lets us assemble a memory map

e801h    : returns a 32-bit memory size

88h    : returns 0-64m memory

 

arch/i386/boot/setup.S

 

ACPI规定的是,要作出一个int 15he820h BIOS调用,要准备以下的输入:

EAX    : E820h

EBX    : 如果是第一次调用,为0。 如果不是,则存放上次调用之后的Continuation valueACPI规范说,EBX应该包含上一次调用的返回值,意思是每次调用后BIOS都自动把continuation value写入了ebx中。如果BIOSebx写的值为0,则说明EOF,不要进行下一次调用了。

ES:DI   : Buffer地址,指向一个Address Range Descriptor结构,BIOS把信息写入这个结构。

ECX    : Buffer size

EDX    : 签名。应该是"SMAP"4个字母的ASCII值。 Linux写作#define SMAP  0x534d4150

 

而输出是:

CF    : 1表示出错

EAX    : 签名。为"SMAP"表示正确,可以判断ebx值进行下一次调用;否则为出错,应该放弃e820方法

ES:DI    : 和输入一样

ECX    : Buffer size. BIOSES:DI写了多少字节。 最小是20字节。

EBX    : Continuation Value.

 

Address Range Descriptor Structure:

----------------------------------

0        BaseAddrLow        Low 32 Bits of Base Address

4        BassAddrHigh        High 32 Bits of Base Address

8        LengthLow        Low 32 Bits of Length in Bytes

12        LengthHigh        High 32 Bits of Length in Bytes

16        Type            Address Type of this range

20        Extended Attributes    see below

 

其中BassAddrLowBaseAddrHigh一起组成了64位的Base Addr。 是该range的起始物理地址。

LengthLowLengthHigh一起组成了64位的长度,即该rangesize

Type,就是1)中说的5种类型之一了:)

 

扩展属性我就不抄了,见ACPI Spec CH14。表14-5.

 

3). 如果是EFI enabled

 

OS loader使用GetMemoryMap()函数--它是EFI提供的Boot Services之一--来获取memmap,并且把它传递给OSPM

EFI没研究,暂且不说。

 

4). LinuxE820 memmap的实现

 

Linux定义了几个内存地址:

 

#define E820MAP 0x2d0

#define E820NR  0x1e8

 

E820最多能有多少entries

 

#define E820MAX 128

 

而在setup.S中:

 

设置ES:DI的值为E820MAP,以便调用int 15he820h时让BIOSmemmap写入这个地址:

movw     $E820MAP, %di

在每次调用int 15he820h时,都增加(E820NR)的值:

incb    (E820NR)

这样,当调用完毕,(E820NR)这块内存就保存了entries数目。

 

Linu引导时,设置memmap的调用路径是:

 

start_kernel() > setup_arch() > memory_setup() > sanitize_e820_map(E820_MAP, &E820_MAP_NR)

   > copy_e820_map(E820_MAP, E820_MAP_NR)

 

其中E820_MAPE820_MAP_NR的定义:

 

 #define E820_MAP_NR (*(char*) (PARAM+E820NR))

 #define E820_MAP    ((struct e820entry *) (PARAM+E820MAP))

 

//FIXME:PARAM是什么意思? 猜测是Linux用来保存boot parameters的页面,见arch/i386/kernel/

head.S

 

其中E820NRE820MAP是两个16位的内存地址,已经在setup.S中调用bios call时设置好了。见上边

的笔记。

 

/**

 * 一个个range地添加从bios获得的内存布局信息

 *

 * 作者注释:

 * =========

 * 我们检查一下从BIOS获得的memmap是不是至少包含2个区间。 因为setup.S中的探测结果可能不

 * 那么完美,而我们又知道大多数PC机有两个内存区域:一个从0640k,另一个从1M往上。

 *

 * 如果BIOS得来的结果是正确的,那么我们就使用它给的memmap;如果不正确,我们就自己伪造

 * 一个(案,即是在这里返回-1,让调用者machine_specific_memory_setup()去伪造。)

 */

int __init copy_e820_map(struct e820entry *biosmap, int nr_map)

{

/* 只有一个,或者竟然是负的?那么就忽略它 */

if (nr_map < 2)

return -1;

 

do {

unsigned long long start = biosmap->addr;

unsigned long long size = bisomap->size;

unsigned long long end = start + size;

unsigned long type = biosmap->type;

...

 

if ( type = E820_RAM ) { //ACPI中可用的RAM类型,那么我们就设置它

if (start < 0x100000ULL && end > 0xA0000ULL) {

if (start < 0xA0000ULL)

add_memory_region(start, 0xA0000ULL-start, type);

if (end <= 0x100000ULL)

continue;

start = 0x100000ULL;

size = end - start;

}

}

 

/* 添加一个Memory Range

 * 注意! e820这个全局变量,就是在该函数中设置的值!

 */

add_memory_region(start, size, type);

 

} while (biosmap++, --nr_map);

 

return 0;

}

 

这个copy_e820_map()函数执行完毕,则memory map就已经设置到全局变量e820中了。

4. ACPI Namespace Definition Block

只有Unload一个Definition Block时,name才会从名空间中移除。

 

只有Load/Unload 定义块 时,Namespace中内容才可能改变。

 

Definition Block只可能出现在DSDTSSDT(s)中。其它表没有。 Linux内核中加载namespace的函数为acpi_tb_load_namespace(),其注释说:Load the namespace from the DSDT and all SSDTs/PSDTs found in the RSDT/XSDT.

 

(FIXME: Dell Optiplex 745机器用acpidump,acpixtract,iasl -d反汇编出来的.dsl

来看,一个DSDTSSDT只有一个DefinitionBlock。 不知道这是否是ACPI规范了的)

 

Name的命名约定:

 

1. 每个Name都是固定的32bit(4字节)大小。

 

2. Name的第一个字符必须是'A'-'Z',或者下划线'_'

 

3. Name不足4个字节的,ASL编译器会对它进行填充。 惯用的填充方式就是在

   结尾填充下划线'_'

 

4. 以下划线'_'开头的Name是由ACPI规范预留的,也就是说,只有ACPI规范定义了

   一个以'_'开头的Name, 供应商才能提供它。 不允许自己定义以'_'开头的Name

 

5. '\'开头的Name,表示引用namespace的根。('\'不算在4字节长度之内)

 

6. '^'开头的Name,表示引用本namespaceparent('^'不算在4字节长

   度之内)

 

预定义了的namespaces(见规范3.05.3.1部分)

 

\_GPE        : General events in GPE register block

\_PR        : ACPI 1.0 Processor Namespace.

\_SB        : All Device/Bus Objects under this namespace

\_SI        : System Indicator.

\_TZ        : ACPI 1.0 Thermal Zonen namespace.

5. Linux至少加载ACPI3Tables: FADT, FACS, DSDT (RSDT不算在内)

    

acpi_tb_tables_loaded()函数:

 

if (acpi_gbl_root_table_list.count >= 3) {

return (TRUE);

else

return (FALSE);

 

其中DSDTFACSacpi_gbl_root_table_list数组中的索引是固定的,DSDT0, FACS1:

 

/* Predefined (fixed) table indexes */

#define ACPI_TABLE_INDEX_DSDT    (0)

#define ACPI_TABLE_INDEX_FACS    (1)

6. ACPI in Linux: 启动和初试化

6.1 最先执行的ACPI函数不是acpi_early_init()

start_kernel() > setup_arch() > acpi_boot_table_init() > acpi_table_init() > acpi_initialize_tables():

 

start_kernel() > setup_arch() > acpi_boot_init() > acpi_table_parse(boot)

        > acpi_table_parse(fadt)

  > acpi_process_madt() > Note

  > acpi_table_parse(hpet)

 

6.1.1 acpi_process_madt()函数:

static void __init acpi_process_madt(void)

{

#ifdef CONFIG_X86_LOCAL_APIC

int error;

 

/** 寻找MADT表,找到的话,在其上运行acpi_parse_madt()函数 */

if ( !acpi_table_parse(ACPI_SIG_MADT, acpi_parse_madt) ) {

/*

 * Parse MADT LAPIC entries -- 找到MADT中的LAPIC

 * ADDRESS OVERRIDE结构, 再找MADT中的Local APIC结构,

 * 把找到的APIC的物理地址映射到FIXMAP保留的线性地址上

 */

error = acpi_pase_madt_lapic_entries();

if (!error) { //success

acpi_lacpi = 1;

 

#ifdef CONFIG_X86_GENERICARCH

generic_bigsmp_probe();

#endif

/*

 * Parse MADT IO-APIC entries

 */

error = acpi_parse_madt_ioapic_entries();

if (!error) {

acpi_irq_model = ACPI_IRQ_MODEL_IOAPIC;

acpi_irq_balance_set(NULL);

acpi_ioapic = 1;

 

smp_found_config = 1;

setup_apic_routing();

}

}

 

if(error == -EINVAL) {

printk("Invalid BIOS MADT, disabling ACPI\n");

disable_acpi();

}

}

 

#endif//Local APIC

return;

}

这个函数是X86 SMP机器的关键代码,它负责找出系统中全部的LAPICIO-APIC

6.1.2 先看看acpi_initialze_tables函数:

acpi_status __init acpi_initialize_tables(struct acpi_table_desc *initial_table_array,

  u32 initial_table_count, u8 allow_resice)

{

acpi_physical_address rsdp_address;

acpi_status status;

 

/* 如果initial_table_arrayNULL,我们就申请一个;

 * 如果非NULL,说明它是整个ACPI表数组的首地址

 *

 * 其定义为:

 * static struct acpi_table_desc initial_table[ACPI_MAX_TABLES] __initdata;

 *

 */

if (!initial_table_array) {

/*设置flag中的ACPI_ROOT_ALLOW_RESIZE*/

status = acpi_allocate_root_table(initial_table_count);

} else {

/* Root Table Array has been statically allocated

 * by the host.

 * --什么意思?我猜测是:ACPI表数组(initial_tables)已经由BIOS静态分配好了,

 *  猜测是位于RAM中。

 */

ACPI_MEMSET(initial_table_array, 0,

initial_table_count * sizeof(struct acpi_table_desc) );

 

/* XXX :

 * 下面这3行代码很重要。在这个赋值操作发生之后,ACPI一直使用全局变量

 * acpi_gbl_root_table_list来进行操作。

 */

acpi_gbl_root_table_list.tables = initial_table_array;

acpi_gbl_root_table_list.size = initial_table_count; //表的个数,不是字节数

acpi_gbl_root_table_list.flags = ACPI_ROOT_ORIGIN_UNKOWN;

}

 

 

/**

 * 取得RSDP的地址。又分为EFI和非EFI两种情况,注意看实现方法

 */

rsdp_address = acpi_os_get_root_pointer();

if (!rsdp_address)

return_ACPI_STATUS(AE_NOT_FOUND);

 

/**

 * 已经有RSDP了,就意味着知道了RSDTXSDT的地址,好了,取出所有的Tables

 * XXX : Note,这步非常关键,因为它负责读取所有的ACPI Tables

 */

status = acpi_tb_parse_root_table(rsdp_address, ACPI_TABLE_ORIGIN_MAPPED);

 

return_ACPI_STATUS(status);

}

 

(Section 0)内核脚注:

===================

 

acpi_boot_table_init() and acpi_boot_init() called from

setup_arch(), always:

1. checksums all tables;

2. enumerates lapics

3. enumerates io-apics

 

acpi_table_init() is seperate to allow reading SRAT without other

side effects

 

side effects of acpi_boot_init:

acpi_lapic =  1 if LAPIC found

acpi_ioapic = 1 if IO-APIC found

if (acpi_lapic && acpi_ioapic) smp_found_config = 1;

if acpi_blacklisted() acpi_disabled = 1;

acpi_irq_model = ...

...

6.2  acpi_early_init()何时执行

2.6.22版本内核中start_kernel() >acpi_early_init()

  >rest_init()

  >kernel_init(KERNEL THREAD)

>do_basic_setup()

   >do_initcalls()

 

可见,acpi_early_init()比带有__init属性的函数执行得要早。

另外,注释说:acpi_early_init() is before LAPIC and SMP init.

 

SMPLAPIC的初试化在哪里呢?答曰:

 

start_kernel() > rest_init() > kernel_init() > smp_prepare_cpus() >

smp_ops.smp_prepare_cpus() [这个即是native_smp_prepare_cpus()] >

smp_boot_cpus() > setup_local_APIC()

  > smpboot_setup_io_apic()

6.3 acpi_early_init()都干了些什么

...

status = acpi_reallocate_root_table();    /* dynamic memory中申请ACPI tables的空间

   ,然后从别处把它们copy过来 */

...

status = acpi_initialize_subsystem();    /* 初试化所有ACPI相关的全局变量 */

...

status = acpi_load_tables();            /* DSDT/SSDTs/PSDT表获取数据,构建ACPI namespace */

...

6.4 看看acpi_reallocate_root_table()函数

...

struct acpi_table_desc *tables;

acpi_size new_size;

...

new_size = (acpi_gbl_root_table_list.count +

ACPI_ROOT_TABLE_SIZE_INCREMENT) * sizeof(struct acpi_table_desc);

 

tables = ACPI_ALLOCATE_ZEROED(new_size); /*linux下该宏的功能相当于kzalloc()函数*/

 

ACPI_MEMCPY(tables, acpi_gbl_root_table_list.tables, new_size); /* memcpy */

...

6.5 PCI设备的IRQ Routing

有个带__init属性的pci_acpi_init()函数,它有这么2句:

pcibios_enable_irq    = acpi_pci_irq_enable;

pcibios_disable_irq    = acpi_pci_irq_disable;

 

而这两个函数指针类型的变量,其原型为:

 

int (*pcibios_enable_irq)(struct pci_dev *dev) = NULL;

void (*pcibios_disable_irq)(struct pci_dev *dev) = NULL;  

这两行赋值代码,其作用要等到PCI驱动程序调用pci_enable_device()pci_disable_device()的时候,才能真正发挥出来:

 

pci_enable_device() > pci_enable_device_bars() > do_pci_enable_device() >

pcibios_enable_device() > pcibios_enable_irq() -- 在这里,调用的就是

acpi_pci_irq_enable()

 

Linux定义的PCI IRQ Routing Table

 

   struct acpi_prt_entry {

   struct list_head        node;

   struct acpi_pci_id      id;

   u8                      pin;

   struct {

   acpi_handle handle;

   u32 index;

   } link;

   u32        irq;

   };

 

   struct acpi_prt_list {

   int count;

   struct list_head entries;

   };

 

   static struct acpi_prt_list acpi_prt;

 

   struct acpi_pci_routing_table {

   u32 length;

   u32 pin;

   acpi_integer address;

   u32 source_index;

   char source[4];

   };

   /*

*             XXX

*

* Type 1: Dynamic

* the 'source' field specifies the PCI interrupt link

* device used to configure the IRQ assigned to this

* slot|dev|pin. the 'source_index' field incicates which

* resource descriptor in the resource templeate(of the

* link device) this interrupt is allocated from.

*

*

* Type 2: Static

* The 'source' field is NULL, and the 'source_index' field

* specifies the IRQ value, which is hardwired to specific

* interrupt inputs on the interrupt controller.

*/

 

参考acpi_pci_irq_add_entry()函数。

 

6.6 ACPILinux设备驱动模型的集成

//FIXME: 我还不理解!!

 

/*{{{*/

static struct acpi_driver acpi_pci_root_driver = {

.name    = "pci_root",

.class    = ACPI_PCI_ROOT_CLASS,

.ids    = ACPI_PCI_ROOT_HID,

.ops    = {

.add    = acpi_pci_root_add,

.remove    = acpi_pci_root_remove,

.start    = acpi_pci_root_start,

},

};

 

struct acpi_driver {

char name[80];

char class[20];

char *ids;    /* supported hardware IDs */

struct acpi_device_ops ops;

struct device_driver drv; /* XXX : struct device那层的 */

struct module *owner;

};

 

struct acpi_device_ops {

acpi_op_add    add;

acpi_op_remove    remove;

acpi_op_lock    lock;

acpi_op_start    start;

acpi_op_stop    stop;

acpi_op_suspend    suspend;

acpi_op_resume    resume;

acpi_op_scan    scan;

acpi_op_bind    bind;

acpi_op_unbind    unbind;

acpi_op_shutdown shutdown;

};

 

struct acpi_bus_ops {

...;

};

 

struct acpi_device {

acpi_handle        handle;

struct acpi_device    *parent;

struct list_head    children;

struct list_head    node;

struct list_head    wakeup_list;

struct list_head    g_list;

struct acpi_device_status status;

...;

void             *driver_data;

struct acpi_driver    *driver;

struct acpi_device_ops    *ops;

struct device        dev; //XXX: 注意,这就是和内核的驱动核心层的接口

struct acpi_bus_ops    bus_ops;

...;

};

 

 

ACPIPRT(PCI IRQ Routing Table)

 

struct acpi_prt_list {

int            count; //number of entries

struct list_head    entries;

};

/*}}}*/

6.7 ACPI中寻找一个Table,然后针对它运行handler

/*{{{*/

241 int __init acpi_table_parse(char *id, acpi_table_handler handler)

242 {

243         struct acpi_table_header *table = NULL;

244

245         if (!handler)

246                 return -EINVAL;

247

/** 单独处理MADT表,是因为有些BIOS有多个MADT,这是硬件BUG

 *  check_multipile_madt()函数的注释

 */

248         if (strncmp(id, ACPI_SIG_MADT, 4) == 0)

249                 acpi_get_table(id, acpi_apic_instance, &table);

250         else

251                 acpi_get_table(id, 0, &table);

252

253         if (table) {

254                 handler(table);

255                 return 0;

256         } else

257                 return 1;

258 }

 

 

好,再分析acpi_get_table()函数:

/******************************************************************************

 * FUNCTION:    acpi_get_table

 *

 * PARAMETERS:  Signature           - ACPI signature of needed

 *         table

 *         Instance            - Which instance (for SSDTs)

 *         out_table           - Where the pointer to the table is returned

 *        

 * RETURN:      Status and pointer to table

 *

 * DESCRIPTION: Finds and verifies an ACPI table.

******************************************************************************/

acpi_status

acpi_get_table(char *signature, acpi_native_uint instance, struct acpi_table_header **out_table)

{

acpi_native_uint i;

acpi_native_uint j;

acpi_status status;

 

/* Parameter validation */

 

if (!signature || !out_table) {

return (AE_BAD_PARAMETER);

}

 

/**

 * 遍历acpi_gbl_root_table_list

 */

for (i = 0, j = 0; i < acpi_gbl_root_table_list.count; i++) {

if (!ACPI_COMPARE_NAME

(&(acpi_gbl_root_table_list.tables[i].signature),

 signature)) {

continue;

}

 

if (++j < instance) {

continue;

}

 

status = acpi_tb_verify_table(&acpi_gbl_root_table_list.tables[i]);

if (ACPI_SUCCESS(status)) {

/* 取得某Table的地址 */

*out_table = acpi_gbl_root_table_list.tables[i].pointer;

}

 

if (!acpi_gbl_permanent_mmap) {

acpi_gbl_root_table_list.tables[i].pointer = NULL;

}

 

 

return (status);

}

 

return (AE_NOT_FOUND);

}

/*}}}*/

 

6.8 acpi_bus, acpi_deviceLinux 2.6 Device Driver Model下的组织

/*{{{*/

acpi_scan_init()是个有__init属性的函数。

 

static int __init acpi_scan_init(void)

{

int result;

struct acpi_bus_ops ops;

 

if (acpi_disabled)

return 0;

 

memset(&ops, 0, sizeof(ops));

ops.acpi_op_add = 1;

ops.acpi_op_start = 1;

 

/* 向核心注册acpi总线类型

 * 就象在pci_driver_init()中注册PCI总线一样:

 *     bus_register(&pci_bus_type)

 */

result = bus_register(&acpi_bus_type);

if (result) {

/* We don't want to quit even if we failed to add

 * suspend/resume */

printk(KERN_ERR PREFIX "Could not register bus type\n");

}

 

/*

 * Create the root device in the bus's device tree

 *

 * 注意,acpi_rootdrivers/acpi/bus.c中定义:struct acpi_device *acpi_root

 *       (我猜测)它是ACPI namespaceroot node

 */

result = acpi_add_single_object(&acpi_root, NULL, ACPI_ROOT_OBJECT,

ACPI_BUS_TYPE_SYSTEM, &ops);

if (result)

goto Done;

 

/*

 * Enumerate devcies in the ACPI namespace

 *

 * acpi_root开始,枚举namespace中的所有对象

 */

result = acpi_bus_scan_fixed(acpi_root);

if (!result)

result = acpi_bus_scan(acpi_root, &ops);

 

if (result)

acpi_device_unregister(acpi_root, ACPI_BUS_REMOVAL_NORMAL);

 

Done:

return result;

}

/*}}}*/

6.9 Local APICBase Address

是个物理地址。

 

#ifdef CONFIG_X86_LOCAL_APIC

static u64 acpi_lapic_addr __initdata = APIC_DEFAULT_PHYS_BASE;

#endif

 

#define APIC_DEFAULT_PHYS_BASE 0xfee00000

 

start_kernel > setup_arch > acpi_boot_init > acpi_process_madt >

acpi_table_parse/acpi_parse_madt:

 

if(madt->address) {

acpi_lapic_addr = (u64) madt->address;

 

printk( "Local APIC address 0x%08x\n", madt->address );

}

6.10 ACPI如何映射一个table

/*{{{*/

acpi_boot_table_init() > acpi_table_init() > acpi_initialize_tables() :

 

acpi_physical_address rsdp_address = acpi_os_get_root_pointer();

 

status = acpi_tb_parse_root_table(rsdp_address, ACPI_TABLE_ORIGIN_MAPPED);

 

> acpi_tb_install_table() :

struct acpi_table_header *table;

table = acpi_os_map_memory(address, sizeof(struct acpi_table_header));

 

 

//FIXME: 为什么要有ioremap__acpi_map_table()两种方式?

 

void __iomem *acpi_os_map_memory(acpi_physical_address phys, acpi_size size)

{

if (phys > ULONG_MAX) {

printk(KERN_ERR PREFIX "Cannot map memory that high\n");

return NULL;

}

if (acpi_gbl_permanent_mmap)

/*

 * ioremap checks to ensure this is in reserved space

 */

return ioremap((unsigned long)phys, size);

else

return __acpi_map_table((unsigned long)phys, size);

}

 

/*}}}*/

 

6.11 Questions and (possibly) Answers

 

1. Linuxinitial_tables变量从未被赋值,所以acpi_initialize_tables()函数

   中的if判断永远为真

   

   //FIXME: is it a trivial BUG?

 

  • 2
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ACPI(Advanced Configuration Management)是1997年由INTEL/MICROSOFT/TOSHIBA提出的新型电源管理规范,意图是让系统而不是BIOS来全面控制电源管理,使系统更加省电。 其特点主要有:提供立刻开机功能,即开机后可立即恢复到上次关机时的状态,光驱、软驱和硬盘在未使用时会自动关掉电源,使用时再打开;支持在开电状态下既插即拔,随时更换功能。 ACPI主要支持三种节电方式,1、(suspend即挂起)显示屏自动断电;只是主机通电。这时敲任意键即可恢复原来状态。2、(save to ram 或suspend to ram 即挂起到内存)系统把当前信息储存在内存中,只有内存等几个关键部件通电,这时计算机处在高度节电状态,按任意键后,计算机从内存中读取信息很快恢复到原来状态。3、(save to disk或suspend to disk即挂起到硬盘)计算机自动关机,关机前将当前数据存储在硬盘上,用户下次按开关键开机时计算机将无须启动系统,直接从硬盘读取数据,恢复原来状态。   ACPI可实现以下功能:   1、用户可以使外设在指定时间开关;   2、使用笔记本电脑的用户可以指定计算机在低电压的情况下进入低功耗状态,以保证重要的应用程序运行;   3、操作系统可以在应用程序对时间要求不高的情况下降低时钟频率;   4、操作系统可以根据外设和主板的具体需求为它分配能源;   5、在无人使用计算机时可以使计算机进入休眠状态,但保证一些通信设备打开;   6、即插即用设备在插入时能够由ACPI来控制。
The Advanced Configuration and Power Interface (ACPI) specification was developed to establish industry common interfaces enabling robust operating system (OS)-directed motherboard device configuration and power management of both devices and entire systems. ACPI is the key element in Operating Systemdirected configuration and Power Management (OSPM). ACPI evolved the existing pre-ACPI collection of power management BIOS code, Advanced Power Management (APM) application programming interfaces (APIs, PNPBIOS APIs, Multiprocessor Specification (MPS) tables and so on into a well-defined power management and configuration interface specification. ACPI provides the means for an orderly transition from existing (legacy) hardware to ACPI hardware, and it allows for both ACPI and legacy mechanisms to exist in a single machine and to be used as needed. Further, system architectures being built at the time of the original ACPI specification’s inception, stretched the limits of historical “Plug and Play” interfaces. ACPI evolved existing motherboard configuration interfaces to support advanced architectures in a more robust, and potentially more efficient manner. The interfaces and OSPM concepts defined within this specification are suitable to all classes of computers including (but not limited to) desktop, mobile, workstation, and server machines. From a power management perspective, OSPM/ACPI promotes the concept that systems should conserve energy by transitioning unused devices into lower power states including placing the entire system in a low-power state (sleeping state) when possible. This document describes ACPI hardware interfaces, ACPI software interfaces and ACPI data structures that, when implemented, enable support for robust OS-directed configuration and power management (OSPM).
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值