4. ASL 语言基础:语法结构、对象类型、数据类型、运算符、控制流语句
ASL(ACPI Source Language)是定义 ACPI 表(尤其是 DSDT/SSDT)行为的硬件抽象语言。它既不像 C 语言那样直接操作内存,也不像 Verilog 那样描述硬件连线,而是处于“固件策略层”:通过解释执行来配置电源管理、设备枚举和热插拔事件。掌握 ASL 语法是定制 ACPI 表的必修课。
4.1 ASL 语法结构
ASL 源文件由 DefinitionBlock 包裹,内部包含命名空间(Scope)、设备(Device)、方法(Method)和各类对象。基本骨架如下:
DefinitionBlock ("DSDT.aml", "DSDT", 2, "ACPI", "TEMPLATE", 0x00000001)
{
Scope (\_SB)
{
Device (PCI0)
{
Name (_HID, EisaId ("PNP0A08")) // PCI Express 总线
Method (_STA, 0, NotSerialized)
{
Return (0x0F)
}
}
}
}
| 语法元素 | 说明 | 示例 |
|---|---|---|
| DefinitionBlock | 定义整个 ACPI 表,包含表签名、OEM ID、修订号等 | DefinitionBlock ("SSDT.aml", "SSDT", ...) |
| Scope | 打开或进入已有命名空间路径 | Scope (\_SB.PCI0) |
| Device | 声明一个设备对象,可包含 _HID、_STA、_CRS 等控制方法 | Device (BAT0) |
| Method | 定义可被 AML 解释器调用的控制方法 | Method (_INI, 0, Serialized) |
| Name | 声明一个命名对象并赋予初始值 | Name (PBUF, Buffer (128) {}) |
命名规范:ASL 标识符最长 255 个字符,区分大小写,通常使用大写字母加数字。以反斜杠
\ 开头的路径表示从根命名空间开始,例如 \_SB.PCI0。
4.2 对象类型
ASL 中的“对象”是 AML 解释器可操作的最小单元。所有数据、设备、方法、事件都以对象形式存在。主要对象类型如下:
- 命名对象(Named Object):通过
Name或Device声明,具有全局或局部路径。 - 控制方法(Control Method):可执行代码块,通过
Method定义,支持参数和返回值。 - 操作区域(OperationRegion):映射到系统内存、IO 空间或 PCI 配置空间的区域,用于硬件访问。
- 事件(Event):用于同步和唤醒,通过
Event声明,配合Notify使用。 - 缓冲(Buffer):字节序列,常用于存储 _CRS 资源描述符。
- 包(Package):对象数组,可包含不同类型元素。
关键区别:命名对象在编译时分配路径,控制方法在运行时被调用。操作区域不占用 AML 堆栈空间,而是直接映射到硬件地址。
4.3 数据类型
ASL 的数据类型比 C 语言更抽象,所有数据最终以 AML 字节码形式存储。常用数据类型如下表:
| 类型 | 关键字 | 取值范围/示例 | 典型用途 |
|---|---|---|---|
| 整数 | Integer | 0 ~ 264-1 | 状态码、掩码、索引 |
| 字符串 | String | "ACPI", "PNP0C0A" | 设备 ID、路径名 |
| 缓冲 | Buffer | Buffer (4) { 0x00, 0xFF, ... } |
资源描述符、UUID |
| 包 | Package | Package (3) { 1, "abc", Buffer() } |
多值返回、配置列表 |
| 对象引用 | Object Reference | RefOf (\_SB.PCI0) |
传递对象句柄 |
| 事件 | Event | Event (EVA0) |
中断同步 |
注意:ASL 没有浮点类型。所有小数运算必须通过整数模拟或使用固定点算法。Buffer 和 Package 在声明时必须指定元素个数或长度。
4.4 运算符
ASL 运算符分为算术、逻辑、位运算和比较四类。所有运算符都返回 Integer 类型。常用运算符如下:
| 类别 | 运算符 | 说明 | 示例 |
|---|---|---|---|
| 算术 | + - * / |
加、减、乘、整除 | Add (A, B, C) |
% |
取模 | Mod (A, 10, R) |
|
<< >> |
左移、右移 | ShiftLeft (1, 3, R) |
|
~ |
按位取反 | Not (A, R) |
|
| 逻辑 | && || |
逻辑与、逻辑或 | LAnd (A, B, R) |
! |
逻辑非 | LNot (A, R) |
|
| 比较 | == != |
等于、不等于 | LEqual (A, B, R) |
< > <= >= |
大小比较 | LGreater (A, B, R) |
|
=== |
严格等于(类型+值) | LEqual (TypeOf(A), TypeOf(B), R) |
|
| 位运算 | & | ^ |
按位与、或、异或 | And (A, B, R) |
~ |
按位取反 | Not (A, R) |
函数式语法:ASL 运算符通常以函数形式调用,例如
Add (A, B, C) 等价于 C = A + B。第三个参数是目标存储位置。
4.5 控制流语句
ASL 支持条件分支、循环和跳转,但语法与 C 语言差异较大。所有控制流语句必须写在 Method 内部。
4.5.1 条件分支
If (LEqual (Local0, 0x0F))
{
Return (1)
}
ElseIf (LGreater (Local0, 0x0F))
{
Return (2)
}
Else
{
Return (0)
}
4.5.2 循环
While (LGreater (Local0, 0))
{
Multiply (Local0, 2, Local0)
Decrement (Local0)
}
4.5.3 无条件跳转
// 使用 BreakPoint 或 Goto(仅调试)
// 实际开发中常用 Return 提前退出
If (LEqual (Arg0, 0))
{
Return (0xFF)
}
避坑:ASL 没有
for 循环,必须用 While 配合计数器实现。循环内不要修改循环变量以外的全局对象,避免 AML 解释器状态不一致。
4.6 ASL 对象与命名空间关系图
下图展示 ASL 中命名对象、控制方法、操作区域和事件在命名空间中的组织方式:
4.7 实战要点与常见错误
- 路径引用:使用
^表示父作用域,^^表示祖父作用域,避免硬编码绝对路径。 - 方法序列化:如果方法可能被并发调用,声明为
Serialized防止重入。 - 返回值类型:方法必须返回 Integer、Buffer 或 Package,不能返回 Void。无返回值时用
Return (0)。 - 局部变量:Local0~Local7 是方法内临时变量,方法退出后失效。全局状态用
Name存储。 - 调试技巧:使用
Debug = <expression>输出到 ACPI 调试日志,但生产代码中应移除。