ASL的全称是ACPI Source language,ASL现在作为BIOS/UEFI的一部分,它包含在BIOS的源代码里面。
BIOS/UEFI中也有相关的ACPI操作的接口,它最终会将ACPI相关的内容放到内存的某段空间中,并将指向该区域的指针传递给OS。OS使用这里的内容来操作获取硬件信息并操作相关的硬件。这样的好处是,OS不需要直接去与底层硬件沟通,它只要直接操作这些ASL生成的内容就可以了。另一个好处是,只要支持ACPI,不管OS是Windows还是Linux,都可以完成与硬件交互,也就是OS Independent。
一、ASL基本准则
作者:freevanx
- 变量命名不超过4个字符,且不能以数字开头。
- 变量或者函数命名,不分大小写。
- Scope形成作用域,概念类似于C++中的namespace,Java中的package。
- Method 或者 Function 定义函数,函数可以定义在 Device 下或者 Scope 下,但是不能脱离 Scope 定义 单独的函数,也就是说,函数必须依附于对象(Scope or device)
- 以"_"字符开头的函数,都是系统保留的,不得给自己的函数取这样的名字
- ASL 中没有运算符号(逻辑或者算术都是如此),但有与此等价的相应系统函数代替
- 符号“\”引用根作用域,“^”引用父级或称上级作用域
- 作用域,或者称路径,有相对和绝对之分。相对作用域从当前作用域开始,向上延伸。也就是说在当 前作用域中使用函数和变量时,解析器会首先从当前作用域中寻找它的定义,如果找不到,则会从父级 或称上级作用域中继续寻找,一直找到当前作用域的 root 为止。绝对路径,则从定义此变量或者函数的 root 作用域开始,一级级的写下去,一直写到此变量的作用域,作用域的引用使用符号“.”,例如 \SB.PCI0.ABCD 。
- 函数最多可传递 8 个参数,在函数里用 Arg0~Arg7 引用,不可以自己定义名字
- 在函数中最多可以使用 8 个局部变量,用 Local0~Local7 表示,不用定义,但是在把局部变量的值 赋给其他变量之前,局部变量必须是有效的值,也就是说,至少有一次把值赋给局部变量的操作
- 声明变量时不需要显式声明其类型,只学系统和应用型语言的童鞋可能会感到强大的不适应,而会 Perl 或者 Python 这类 Script 语言的童鞋则见怪不怪。
二、数据类型,赋值,基本运算
1. 数据类型 ASL 中支持的数据类型有:
- 整数- Integer,
- 字符串 - String,
- 事件 - Event,
- 数组 - Buffer,
- 对象集合 - Package
2. 定义变量
定义一个整数 Name(MYTS, 0)
定义一个字符串 Name(TSTR, "Hello ASL")
3. 赋值
最常用的赋值函数只有一个,即 Store(),
如 Store(0x1234, Local0) // Local0 = 0x1234;
Store("Hello ASL", Local0) // Local0 = "Hello ASL"
4. 算术运算
算数运算的操作符如下图所示,请牢记第一节列出的准则,千万不要使用 + - * / 等符号计算 举例如下:
- Add(3, 5, Local0) // Local0 = 3 +5
- And (0xF4, 0x39, Local0) // Local0 = 0xF4 & 0x39
- Divide(100, 9, Local1, Local0) // Local0 = 100/9, Local1 = 100 % 9
- Mod(100, 9, Local0) // Local0 = 100 % 9
- Multiply(34, 25, Local0) // Local0 = 34 * 25
- Nor(0x34, 0xF8, Local0) // Local0 = (~0x34) & (~0xF8),
- 无对应的操作符 Not(0x00, Local0) // Local0 = ~0x00
- Or(0x3F, 0xF4, Local0) // Local0 = 0x3F | 0xF4
- ShiftLeft(3, 6, Local0) // Local0 = 3 > 6
- Subtract(100, 24, Local0) // Local0 = 100 - 24
- Xor(0x3F, 0x90, Local0) // Local0 = 0x3F ^ 0x90
除了上面列出的使用方法之外,这些函数还会将计算结果通过返回值传递回来,这样就方便一次连接很 长的算式。 例如: Store(Add(5, 4), Local0) // Local0 = 5 + 4
三、函数,
流程控制
1. 定义函数
Method(TMED) { }
2. 定义有两个输入参数的函数
Method(TMED, 2) { // Arg0 - 第一个输入参数 // Arg1 - 第二个输入参数 }
3. 在函数中使用局部变量
Method(TMED, 2) {
Store(Arg0, Local0)
Store(Arg1, Local1)
Add(Local0, Local1, Local0)
}
4. 在函数中使用返回值
Method(TMED, 2) {
Store(Arg0, Local0)
Store(Arg1, Local1)
Add(Local0, Local1, Local0)
Return(Local0)
}
5. 调用函数 TMED(3, 5) 6. 保存函数的返回值 Store(TMED(4, 5), TMPD)
Scope(_SB)
{
Device(LID0)
{
Name(_HID, EISAID("PNP0C0D"))
Method(_STA, 0, NotSerialized)
{
Return(Zero)
}
}
}
Name(_S0, Package(4) {Zero, Zero, Zero, Zero})
If(SS1)
{
Name(_S1, Package(4) {One, Zero, Zero, Zero})
}
If(SS3)
{
Name(_S3, Package(4) {0x05, Zero, Zero, Zero})
}
If(SS4)
{
Name(_S4, Package(4) {0x06, Zero, Zero, Zero})
}
Name(_S5, Package(4) {0x07, Zero, Zero, Zero})
Method(PTS, 1, NotSerialized)
{
If(Arg0)
{
\_SB.PCI0.LPCB.SPTS(Arg0)
\_SB.PCI0.NPTS(Arg0)
\_SB.SARM(Arg0)
\_SB.PCI0.LPCB.SIOS(Arg0)
}
}
Method(WAK, 1, NotSerialized)
{
\_SB.PCI0.LPCB.SWAK(Arg0)
\_SB.PCI0.NWAK(Arg0)
\_SB.PCI0.LPCB.S1RS(Arg0)
\_SB.PCI0.LPCB.SIOW(Arg0)
}
}
四、OperationRegion 的使用,IO,Memory,PCI,EC 读写
OperationRegion 是 ACPI 定义的一种操作 Register 方式,可以操作的 Register 包括 IO Memory PCI 等 等,ACPI 4.0 支持的 OperationRegion 共有以下几种。此外,用户还可以自己写一个 ACPI 驱动,注册自 己的 OperationRegion。
就当前来说,并不是上面所有的 OperationRegion 都受到支持且可以使用,一些 OperationRegion,受限 于编译器,OS 下 AML 的 Interpreter 支持等等因素,是不能确定能够在当前 ASL 中使用的,类似的 OperationRegion 有 CMOS,PCIBARTarget,IPMI,SMBus。另外,对于其他的一些 OperationRegion,ACPI Spec 有一些特殊的规定
- OS 必须保证 SystemIO OperationRegion 在任何情况下都可以使用
- OS 必须保证 PCI Root Bus 下的 PCI_Config OperationRegion 一定可用
- OS 必须保证,SystemMemory OperationRegion 在访问通过 Memory Map Report 的 Memory 时,一定可 用。事实上,这一条就是说明,只要是在有效地址空间中的 Memory 访问,OS 必须保证 Memory OperationRegion 可用
此外其他 OperationRegion,必须通过_Reg Method 去判断,如果 OperationRegion 已经 connect,则此 时此 OperationRegion 可用,如果没有 connect,或者已经 Disconnect,则不可通过此 OperationRegion 去访问设备的地址空间。
IO OperationRegion 接下来,将展示一个 IO OperationRegion 的使用,我们使用定义的 OperationRegion,将 debug code 输出到 80 Port
//示例开始
OperationRegion (DBGP, SystemIO, 0x80, 4)
Field (DBGP, ByteAcc, Lock, Preserve)
{
P80L, 8
}
Store(0xA3, P80L)
// 输出 A3 到 80 port
Memory OperationRegion
接下来,我们通过 Memory OperationRegion 展示一个相当好用函数。我们知道,对于 PCIe 设备来说, 有两种访问方法,一种是通过传统的 PCI 兼容方式,另外一种是通过 MMIO,不但可以访问 PCI 的 256 byte 的 PCI Space,而且可以访问全部的 4K PCI space,那么接下来,我们将展示这样一组函数,他能够让 你在 ASL 里面自由的访问 PCI Express 的 Configuration Space。
//示例开始
#define PCIE_BASE 0xE0000000
Method (RDPB, 1) {
Add (Arg0, PCIE_BASE, Local0) // Add PCI Express MMIO base address
OperationRegion (PECF, SystemMemory, Local0, 0x1)
Field (PECF, ByteAcc, Nolock, Preserve) {
MCFG, 8 ,
}
Return (MCFG)
}
上面展示的函数是直接通过 MMIO 访问 PCI Express 设备的 configuration space,但是上面函数只适合 比较熟练的开发者使用,因为需要自己计算 PCIe 设备的地址,那么,我们把上面的函数稍微转换一下, 变成下面的函数,这样就够直观了。
// Arg0 - bus no
// Arg1 - dev no
// Arg2 - func no
// Arg3 - register offset
Method (RDPB, 4) {
ShiftLeft(Arg0, 20, Local0) // 计算 bus number PCIe address
Or(ShiftLeft(Arg1, 15), Local0, Local0) // 计算 device number PCIe address
Or(ShiftLeft(Arg2, 12), Local0, Local0) // 计算 function number PCIe address
Or(Arg3, Local0, Local0) // 计算 register,形成最终 PCIe address
Add (Arg0, PCIE_BASE, Local0) // Add PCI Express MMIO base address
OperationRegion (PECF, SystemMemory, Local0, 0x1)
Field (PECF, ByteAcc, Nolock, Preserve) {
MCFG, 8 ,
}
Return (MCFG)
}
有了上面的函数,我们可以直接在 ASL 读某个 PCIe 设备的一个 byte,例如,我们读 PCI 设备,bus 0, dev 31, func 3, register 0x40,可以使用如下语句:
Store(RDPB(0, 31, 3, 0x40), Local0)
上面是读 PCIe Register 的函数,接下来,我们将上面的函数稍作修改,写一个写 byte 到 PCIe register 的函数。
// Arg0 - bus no
// Arg1 - dev no
// Arg2 - func no
// Arg3 - register offset
// Arg4 - value
Method (WRPB, 4) {
ShiftLeft(Arg0, 20, Local0) // 计算 bus number PCIe address
Or(ShiftLeft(Arg1, 15), Local0, Local0) // 计算 device number PCIe address
Or(ShiftLeft(Arg2, 12), Local0, Local0) // 计算 function number PCIe address
Or(Arg3, Local0, Local0) // 计算 register,形成最终 PCIe address
Add (Arg0, PCIE_BASE, Local0) // Add PCI Express MMIO base address
OperationRegion (PECF, SystemMemory, Local0, 0x1)
Field (PECF, ByteAcc, Nolock, Preserve) {
MCFG, 8 ,
}
Store(Arg4, MCFG)
}
可以使用以下语句,将一个 byte 写入到 bus 0, dev 31, func 3, register 0x40
WRPB(0, 31, 3, 0x40, 5)
PCI_CONFIG OperationRegion
可以通过定义 PCI OperationRegion 来访问 PCI 和 PCI Express 设备的配置空间。不过,可以从 PCI OperationRegion 的定义看到,OperationRegion 本身只定义 Register 在 Configuration Space 的位置 和长度,但不能确定 PCI 设备 bus number,device number,和 function number,所以也就不能确定设 备的地址。 ACPI 引入了其他一些 Method 来确定 ACPI 中 PCI 设备的地址。 _SEG 函数定义 PCI 设备的 Segment,在 x86 架构中来说,一般不使用_SEG,所有的 PCI 设备默认都在 Segment 0. _BBN 函数定义 PCI Root Bridge 的 bus number,一般来说在 PCI Root Bridge 下定义_BBN(0),意指从 bus 0 开始。有 了 segment 和 bus,最后我们在 PCI 设备下定义一个_ADR 属性,确定此 PCI 设备的 device number 和 function number,_ADR 的返回值中,高 16 位表示 device number,低 16 位表示 function number,接 下来将展示一段 sample code,读写 bus 0 ,device 29,function 1 USB controller 的 PCI Configuration Space
//示例开始
Name(_ADR, 0x001d0001) // Device (HI WORD)=29, Func (LO WORD)=1
OperationRegion(USBR, PCI_Config, 0xC4, 1)
Field(USBR, ANYACC, NOLOCK, PRESERVE) {
URES, 8
}
Method(TEMD) {
Store(3, URES)
}
EC OperationRegion
EC OperationRegion 是定义 EC Space 操作的,可以在 ASL 里面定义 EC OperationRegion,直接读写 EC OperationRegion,OS 的 ACPI 或者 EC Driver 或将这些操作转换为对 EC Space 的读写。根据 ACPI spec, 对于一个读操作,driver 会向 EC 发送 0x80 command 读其中的 value,对于写操作,driver 会向 EC 发 0x81 command 将一个 value 写道 EC Space。接下来定义一个 EC OperationRegion,假设 EC Space offset 0x00 是 CPU 的温度。
//示例开始
OperationRegion (ECF2, EmbeddedControl, 0x00, 0xFF)
Field (ECF2, ByteAcc, Lock, Preserve) {
CTMP, 8, //CPU Temp
}
Store(CTMP, Local0) // Read CPU temperature from EC Space
与上面其他 OperationRegion 不同的是,EC OperationRegion 并不是任何时刻都可以使用,所以我们要 follow ACPI spec,在同一 scope 中定义一个_Reg 属性,来判断 EC OperationRegion 是否可用
//示例开始
Name(ECON, 0) // Variable to remember EC
OperationRegion Status Method(_REG, 0x2) {
if(LEqual(Arg0, 0x03)) // Is it EC OperationRegion? Yes EC =3
{
If(Arg1) // Is OperationRegion Connect?
{
Store(0x01, ECON) // Available
}
Else // OperationRegion Disconnect
{
Store(0x00, ECON) // unavailable
}
}
}