acpi 学习笔记

更新时间:2024-01-19 18:11:02 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

ACPI学习笔记 - 不断修订中-last updated on Oct 15

刚起步, 3.0b规范还只看了一部分, 本文档对别人应该没什么意义,只是记录我的学习过程而已。 肯定有大量误解,欢迎BUG FIXING。 /**

* file : ACPI Notes with Linux.txt

* AUTHOR : albcamus * Copyright : GNU FDL(Free Documentation License) * XXX : continuously correcting and improving */

TERMINOLOGY: ===========

I.) TABLES OF ACPI

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'). 注意它是ACPI Spec-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.0b的Table 5-6. 这里只是一部分。

Note, ACPI定义的所有tables、blocks和structures,全都是little-endian。

II.) THE OTHER TERMINOLOGIES

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, 指Linux等OS中实现对ACPI支持的代码 PRT : PCI IRQ Routing Table

PXE : Preboot Execution Environment

SAPIC : Streamlined APIC, IA64上使用的APIC。其local SAPIC和I/O SAPIC分别对应着IA32

和x86-64上的Local APIC和I/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. PC的BIOS规范。

TOM : Top Of Memory

UUID : Universal Uniform IDentifiers

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

1, RSDP, RSDT and XSDT

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

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

(32位), 和XSDT表的物理地址(64位)。

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

从ACPI2.0+开始,XSDT就取代了RSDT。 RSDT是ACPI 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? */ };

2, ACPI Table Header

ACPI所有的描述表(FACS表除外?Linux的acpi_table_facs结构不包含header,ACPI规范中也没有)都包含

着一个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, (RSDT/XSDT之外的) ACPI描述表数组的第一个:FADT

这个数组的第一个元素由XSDT中的table_offset_entry[0]指定位置。

被指向的第一个表通常是FADT(Fixed ACPI Description Table),它的几个主要作用:

1) 保存着FACS和DSDT表的地址(64位也是)

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

硬件对ACPI的支持程度)

Linux下FADT的定义见struct acpi_table_fadt结构。

4. DSDT表(Differentiated System Description Table)

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

//Thanks for wheelz!

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_DSDT和CONFIG_ACPI_CUSTOM_DSDT_FILE配置选项,允许用户自己提供

DSDT文件,替代BIOS中有BUG的那个。

这个用户自己提供的DSDT文件,其实就是一个AML语言描述的数据块。 可以用pmtools中的acpidump和

acpixtract程序把ACPI的表从BIOS中提取出来,再用iasl工具(Intel ACPICA中的ASL编译器、

AML反汇编器)

反汇编它,修正BUG,然后编译。

这个原理,参考Linux内核的acpi_os_table_override()函数。

5. SSDT表

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

6, FACS表(Firmware ACPI Control Structure)

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

7, CPU的Idle States (C-states)

从C0到C。一般来说,都是共有从C0到C4这5个状态。 C0是正常状态,即CPU全频率工作,不省电。

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

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

8, CPU的Performance States (P-States), CPU频率管理

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

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

频率没有任何意义。

9, CPU的Throttling States (T-States)

Throttling是个什么意思呢?--CPU每秒多少个时钟周期s,我们称之为Cycles Per Seconds (CPS)。可以让

CPU在一个CPS中有一定比例的空闲时间。

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

除了T0外的T-States,和“7, CPU的Performance States (P-States), CPU频率管理”不同,后者只是\让每

秒的周期数少一些\; 它也和CPU的空闲状态(例如C1状态)不同,后者是CPU在1秒钟里所有的周期都是空闲的。

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

10. CPU的\

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

11, MADT表, Multiple APIC Description Table

ACPI规范认为,一共有3种中断模型: Dual-8259, APIC和SAPIC。 Firmware可以同时提供对它们的支持,

但OSPM只能选择一个。

1.) Linux的MADT表的结构定义为:

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

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

从0到8的这九个结构,在Linux下分别是:

0, acpi_madt_local_apic,

1, acpi_madt_io_apic,

2, acpi_madt_interrupt_override,

对于同时支持APIC和dual-8259A两种中断模型的平台,GSI中断的0-15必须被映射到8259A的0-15

引脚上。如果不是这样,那么就必须提供Interrupt Override。

例如,如果你有一个ISA的PIT,连接到ISA IRQ 0;但是在APIC模式下,它连接到I/O APIC 的

IRQ 2。你就需要提供Interrupt Source Override,在source entry == 0处,指定其GSI为2。

3, acpi_madt_nmi_source,

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

4, acpi_madt_local_apic_nmi,

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

5, acpi_madt_local_override,

MADT中的address是local APIC的地址,32位。为了提供对64位系统的支持,可以提供local

APIC Address Override Structure,这样local APIC的地址就可以被Override成64位。

6, acpi_madt_io_sapic, IA64的I/O SAPIC。

7, acpi_madt_local_sapic, IA64的Local SAPIC。

8, acpi_madt_interrupt_source.

用来管理PMI(Platform Management Interrupts)的. PMI是IA-64上的,跟IA32的SMI类似。

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

3.) Local APIC/SAPIC的entry order:

为了POST以及其后的正确引导, 有两条规则应该遵守: 1). OSPM应该以 处理器在MADT表中出现的顺序来初始化它们 2). Firmware应该把MADT中的第一个Entry列为引导处理器BP

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

1. OSPM应该以 逻辑处理器在MADT表中出现的顺序来初始化它们 2。 Firmware应该把每个处理器的第一个逻辑处理器,在MADT表中 列在任何2nd逻辑处理器之前。

4.) 参考9.1/9.2中的信息,看看Linux下对local APIC和I/O APIC的定义:

/* 0:Processor 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()

两个函数来获取BIOS中ACPI提供的Local APIC和I/O APIC的信息。

5.) 多个IO-APIC

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

APIC模型下,每个IO APIC支持的irq输入数目,可以是不同的, OSPM读取每个IO APIC的Max

Redirection Register来获取这个数目.

参考acpi_madt_io_apic结构,如果一个io apic是系统中的第二个io apic,而第一个io apic的中断

数目是24, 那么第二个io apic的global_irq_base就是24 -- 亦即,第二个io apic的中断输入从第

24开始递增。

12. SRAT表(System Resource Affinity Table)

该表可选.

13, E820及其它(ACPI spec, CH14)

传统的PC使用INT 15H这个BIOS调用来获得内存布局; 用EFI的系统,则可以通过EFI的一个boot service:

GetMemoryMap()函数,来获取内存布局.

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使用。 \的意思是,给EAX赋值e820h,然后int 0x15来调用BIOS的15号 调用。

PC机上一共有3中memmap探测方法,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 15h,e820h BIOS调用,要准备以下的输入:

EAX : E820h

EBX : 如果是第一次调用,为0。 如果不是,则存放上次调用之后的Continuation value。ACPI

规范说,EBX应该包含上一次调用的返回值,意思是每次调用后BIOS都自动把continuation value写入了ebx中。如果BIOS往ebx写的值为0,则说明EOF,不要进行下一次调用了。 ES:DI : Buffer地址,指向一个Address Range Descriptor结构,BIOS把信息写入这个结构。

ECX : Buffer size

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

而输出是:

CF : 为1表示出错

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

ES:DI : 和输入一样

ECX : Buffer size. BIOS往ES: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

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

LengthLow和LengthHigh一起组成了64位的长度,即该range的size。 Type,就是1)中说的5种类型之一了:)

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

3). 如果是EFI enabled

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

OSPM。

EFI没研究,暂且不说。

4). Linux对E820 memmap的实现

Linux定义了几个内存地址:

#define E820MAP 0x2d0 #define E820NR 0x1e8

和E820最多能有多少entries:

#define E820MAX 128

而在setup.S中:

设置ES:DI的值为E820MAP,以便调用int 15h,e820h时让BIOS把memmap写入这个地址:

movw $E820MAP, %di

在每次调用int 15h,e820h时,都增加(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_MAP和E820_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

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

/**

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

* 作者注释:

* =========

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

* 那么完美,而我们又知道大多数PC机有两个内存区域:一个从0到640k,另一个从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中了。

14. ACPI Namespace 和 Definition Block

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

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

Definition Block只可能出现在DSDT和SSDT(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 来看,一个DSDT或SSDT只有一个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,表示引用本namespace的parent。('^'不算在4字节长 度之内)

预定义了的namespaces(见规范3.0之5.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.

15. Linux至少加载ACPI的3个Tables: FADT, FACS, DSDT (RSDT不算在内)

见acpi_tb_tables_loaded()函数:

if (acpi_gbl_root_table_list.count >= 3) { return (TRUE); else

return (FALSE);

其中DSDT和FACS在acpi_gbl_root_table_list数组中的索引是固定的,DSDT是0, FACS是1:

/* Predefined (fixed) table indexes */ #define ACPI_TABLE_INDEX_DSDT (0) #define ACPI_TABLE_INDEX_FACS (1)

16, ACPI in Linux: 启动和初试化 /*{{{*/

0). NND,最先执行的ACPI函数不是acpi_early_init(),害我cscope查了半天!

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

> acpi_boot_init() > acpi_table_parse(boot) > acpi_table_parse(fadt) > acpi_process_madt() > Note > acpi_table_parse(hpet)

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(\

disable_acpi(); } }

#endif//Local APIC return; }

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

先看看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_array为NULL,我们就申请一个; * 如果非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了,就意味着知道了RSDT或XSDT的地址,好了,取出所有的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 = ... ...

1). 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.

SMP和LAPIC的初试化在哪里呢?答曰:

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()

2). 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 */ ...

3). 看看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 */ ...

4). 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()函数。

/*}}}*/

51.1) ACPI和Linux设备驱动模型的集成//FIXME: 我还不理解!!

/*{{{*/

static struct acpi_driver acpi_pci_root_driver = { .name = \

.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];

} /*}}}*/

53, acpi_bus, acpi_device在Linux 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 \ }

/*

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

* 注意,acpi_root在drivers/acpi/bus.c中定义:struct acpi_device *acpi_root * (我猜测)它是ACPI namespace的root 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; } /*}}}*/

56, Local APIC的Base 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( \ }

61, 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 \

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

/*}}}*/

62, Questions and (possibly) Answers

1. Linux的initial_tables变量从未被赋值,所以acpi_initialize_tables()函数 中的if判断永远为真

//FIXME: is it a trivial BUG?

创建于: 2007-09-09 21:06:27,修改于: 2008-12-07 03:19:53,已浏览5948次,有评论55

网友评论

网友:REDHGG 时间:2007-09-21 17:51:42 IP地址:61.144.246.★

写的挺好的,,学了点,懒得看DATASHEET

网友:redhgg

时间:2007-09-24 10:48:03 IP地址:61.144.246.★

你认为为什么XP下没有ACPIVIEW这个软件呢??你对ACPI TALBE的获得是怎

网友:albcamus

时间:2007-09-25 13:08:00 IP地址:61.148.56.★

那就写封信给M$骂它们吧。

Intel 的acpica和pmtools两个包中有acpidump、acpixtract和iasl等工具,可从BIOS中dump出来, 也可以反汇编、修改、编译,再写回BIOS。

网友:REDHGG

时间:2007-09-27 10:33:53 IP地址:61.144.246.★

找了半天终于找到了XP下的 ACPI SCOPE

最近要用VC把他重新写一遍。痛苦 都不知道怎么从REGISTER中读 你是搞BIOS的?

网友:albcamus

时间:2007-09-27 12:32:51 IP地址:61.148.56.★

不是,是Linux

网友:redhgg

时间:2007-10-11 17:28:08 IP地址:61.144.246.★

你学这个是为什么呢???我回头看看如果对着规格表什么的学习没有目标其实

网友:albcamus

时间:2007-10-12 13:29:05 IP地址:61.148.56.★

http://wiki.zh-kernel.org/project/linux-acpi

zh-kernel.org上的ACPI文档,由Intel OTC的Shaohua Li维护。

网友:redhgg

时间:2007-10-16 15:59:06 IP地址:61.144.246.★

原来是INTEL的高人。膜拜一下。多多学习

网友:redhgg

时间:2007-10-19 09:43:00 IP地址:61.144.246.★

再问个问题啊,我准备写个AML语言转换到ASL语言的翻译器,写了部分以后发代码没有严格按照3。0的规格表。似乎省略了一些东西。应该是被优化了。这吗???我找来找去也找不到。只好问你碰碰运气了

网友:aaron

时间:2007-10-19 14:03:32 IP地址:202.101.8.★

我现在在Intel实习,正在研究 acpica的几个软件,可以交流一下哈。 aaron.huangcheng@hotmail.com

网友:本站网友 时间:2007-10-19 14:10:06 IP地址:202.101.8.★

我不是很清楚Node, handler, object, 以及相关tables之间的关系

按理说ACPI对tables的操作都是从AcpiGbl_rootTablesList下手的,但是还有几个全局变量

AcpiGbl_ExceptionHandler,AcpiGbl_SystemNotify之类的,跟AcpiGbl_RootTablesList有和系?

网友:redhgg

时间:2007-10-19 16:22:02 IP地址:61.144.246.★

我靠 这帖子火了

网友:albcamus

时间:2007-10-22 09:24:04 IP地址:61.148.56.★

> 我现在在Intel实习,正在研究 acpica的几个软件,可以交流一下哈。 > aaron.huangcheng@hotmail.com

很荣幸! 不过想问一下:是不是熟悉N种OS Kernel的老朋友「圆点坐标」?

> 我准备写个AML语言转换到ASL语言的翻译器,写了部分以后发现机器中的按照3。0的规格表。似乎省略了一些东西。应该是被优化了。这个优化你有资找去也找不到。只好问你碰碰运气了

这个好像不好办,如果OEM的实现没有遵照ACPI规范,可能Intel的IASL都不以试试Microsoft的ASL.EXE: http://www.acpi.info/toolkit.htm

网友:redhgg 时间:2007-10-22 09:49:41 IP地址:61.144.246.★

看了 ,,挺有用的。谢谢

网友:redhgg

时间:2007-10-22 09:53:53 IP地址:61.144.246.★

我用过了iasl可以用。就是不知道他这个代码的优化是在哪儿。。。

网友:albcamus

时间:2007-10-22 12:55:49 IP地址:61.148.56.★

如果iasl能够正常的反汇编,就说明很可能BIOS厂商是用iasl编译的源文件化」应该也是iasl允许的那些 -- 在acpica/compiler/的C代码实现中好像有知道有没有用?

网友:REDHGG

时间:2007-10-22 13:47:57 IP地址:61.144.246.★

我看行!! 谢谢

网友:REDHGG

时间:2007-10-22 14:19:50 IP地址:61.144.246.★

我初步发现了第一个优化的规律了。不知道后面还有没有 多谢 呵呵

网友:aaron

时间:2007-10-22 15:52:21 IP地址:202.101.8.★

>>很荣幸! 不过想问一下:是不是熟悉N种OS Kernel的老朋友「圆点坐标」

呵呵,我不是哈。不过我周围这样的牛人很多。。。

>>我初步发现了第一个优化的规律了。不知道后面还有没有 多谢 呵呵

分享一下:)

网友:aaron

时间:2007-10-22 15:54:41 IP地址:202.101.8.★

>> 1. Linux的initial_tables变量从未被赋值,所以acpi_initialize_ 中的if判断永远为真

我也发现了。。。

网友:albcamus

时间:2007-10-22 17:23:33 IP地址:61.148.56.★

象这种情况,应该可以提交一个trivial patch吧?

网友:albcamus

时间:2007-10-22 17:24:35 IP地址:61.148.56.★

真是很荣幸,自己一个探索学习的草稿文章,竟然获得诸位的关注! :)

网友:本站网友

时间:2007-10-22 23:47:23 IP地址:219.134.80.★

分享一下我所知道的:

首先,BIOS会包一个ACPI的BIN文件,里面就是包含DSDT整个Definition B在Post过程中

会把ACPI BIN File解压出来(Award的BIOS大概是在POST 89h的位置),把~

0FFFFFh 之间的内存区间中,然后Move RSDT, FACP, DSDT tables to Reclaim table

本文来源:https://www.bwwdw.com/article/muoo.html

Top