BIOS知识枝桠——ASL语言

  • Post author:
  • Post category:其他


在项目中SIO芯片部分控制着各种设备串口的功能,而对于该部分需要ASL语言来表述。目前ACPI的最新版本是6.3版本,相关的SPEC可以在

Specifications | Unified Extensible Firmware Interface Forum

中下载到,本文主要内容来源为SPEC第19章ACPI SOURCE LANGUAGE (ASL) REFERENCE以及一个基础语法的文件,主要写了自己项目中遇到的相关用法,后续遇到会继续补充。

ASL是一种用于定义ACPI对象(包括编写ACPI控制方法)的源语言。原始设备制造商OEM和平台固件开发人员用ASL定义对象和编写控制方法,然后使用翻译工具(编译器)生成控制方法的ACPI机器语言(AML)版本。

AML和ASL是不同的语言,尽管它们是密切相关的。每个acpi兼容的操作系统都必须支持AML。给定的用户可以定义一些任意的源语言(替换ASL),并编写一个工具将其转换为AML。OEM或平台固件供应商需要编写ASL,并能够单步AML进行调试。(调试器和类似的工具应该是aml级别的工具,而不是源代码级别的工具。)一个ASL翻译实现者必须了解如何读取ASL和生成AML。AML解释器作者必须了解如何执行AML。

对于数学和逻辑操作,ASL支持类似于C语言的标准符号操作符和表达式。还支持复合赋值操作符。由符号操作符和表达式生成的AML代码与为等价的遗留ASL操作符生成的AML代码相同。



基本准则

  1. 变量命名不超过 4 个字符,且不能以数字开头。
  2. 变量或者函数命名,不分大小写(VB?= Very bad)
  3. Scope 形成作用域,概念类似于 C++中的 namespace,Java 中的 package。
  4. Device 定义也会形成自己的作用域,类似于 C++中 class 的概念
  5. Method 或者 Function 定义函数,函数可以定义在 Device 下或者 Scope 下,但是不能脱离 Scope 定义单独的函数,也就是说,函数必须依附于对象(Scope or device)
  6. 以”_”字符开头的函数,都是系统保留的,不得给自己的函数取这样的名字
  7. ASL 中没有运算符号(逻辑或者算术都是如此),但有与此等价的相应系统函数代替
  8. 符号“\”引用根作用域,“^”引用父级或称上级作用域
  9. 作用域,或者称路径,有相对和绝对之分。相对作用域从当前作用域开始,向上延伸。也就是说在当前作用域中使用函数和变量时,解析器会首先从当前作用域中寻找它的定义,如果找不到,则会从父级或称上级作用域中继续寻找,一直找到当前作用域的 root 为止。绝对径,则从定义此变量或者函数的root 作用域开始,一级级写下去,一直写到此变量的作用域,作用域的引用使用符号“”,例如\SB.PCI0.ABCD 。
  10. 函数最多可传递 8 个参数,在函数里用 Arg0~Arg7 引用,不可以自己定义名字
  11. 在函数中最多可以使用 8 个局部变量,用 Local0~Local7 表示,不用定义,但是在把局部变量的值赋给其他变量之前,局部变量必须是有效的值,也就是说,至少有一次把值赋给局部变量的操作
  12. 声明变量时不需要显式声明其类型,会Perl 或者 Python 这类 Script 语言的见怪不怪。



数据运算

  1. 数据类型 ASL 中支持的数据类型有,

    a) 整数- Integer,

    b) 字符串 – String,

    c) 事件 – Event,

    d) 数组 – Buffer,

    e) 对象集合 – Package
  2. 定义变量

    Name(MYTS, 0) // 定义一个整数

    Name(TSTR, “Hello ASL”) // 定义一个字符串
  3. 赋值

    最常用的赋值函数只有一个,即 Store(), 如

    Store(0x1234, Local0) // Local0 = 0x1234;

    Store(“Hello ASL”, Local0) // Local0 = “Hello ASL”

    下图总结了基本的符号运算:数学运算符、逻辑运算符、赋值和复合赋值操作

    在这里插入图片描述



算术运算

在这里插入图片描述

当然嵌套也是可以的,例如: Store(Add(5, 4), Local0) // Local0 = 5 + 4

对于运算符的优先级 + – 最低 ,* / % 高一级 ,例如 Loca10 = (5+3) * 12 + 100/9 – 100%9

第一步,从最低优先级最后的 + – 开始,转换

Store(Subtract(Add((5+3) * 12 , 100/9), 100%9), Loca10)

第二步,“*”“/”“%”3 个运算处于同一优先级,可以同时转换

Store(Subtract(Add(Multiply((5+3) , 12) , Divide(100,9)), Mod(100,9)), Loca10)

最后,将括号内的加法转换

Store(Subtract(Add(Multiply(Add(5,3), 12) , Divide(100,9)), Mod(100,9)), Loca10)

这样就可以成功得到一个完全用 ASL 写成的算式。



逻辑运算

在这里插入图片描述

复合使用:Local0 = 30*( 5== 3) + (50/4 >= 30)

第一步,将最高优先级的“==”和“/”转换成 ASL 符号

Local0 = 30 * LEqual(5, 3) + (Divide(50, 4) >= 30)

第二步,将“*”和“>=”转换成 ASL

Local0 = Multiply(30 , LEqual(5, 3)) + (LGreaterEqual(Divide(50, 4) ,30))

第三步,将“+”和“=”转换成 ASL 符号

Store(Add (Multiply(30, LEqual(5, 3)), LGreaterEqual(Divide(50, 4) ,30)), Loca10)



函数,流程语句

1.定义函数Method(TMED){}

2. 定义有两个输入参数的函数

Method(TMED, 2)
{
// Arg0 - 第一个输入参数
// Arg1 - 第二个输入参数
}
  1. 在函数中使用局部变量
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)
}
  1. 调用函数

    TMED(3, 5)
  2. 保存函数的返回值
Store(TMED(4, 5), TMPD)


If的用法

Store (4, Local2)
If (And (Local0, Local2))
{
    XOr (Local0, Local2, Local0)
}


ElseIf 的用法

If (LEqual (local0, "Microsoft Windows NT"))
{
    Store (3, TOOS)
}
ElseIf (LEqual (Local0, "Microsoft Windows"))
{
    Store (1, TOOS)
}
ElseIf (LEqual (Local0, "Microsoft WindowsME:Millennium Edition"))
{
    Store (2, TOOS)
}


Else 的用法

If (LGreater (Local0, 5)
{
    Increment (CNT)
} Else If (Local0) {
    Add (CNT, 5, CNT)
} Else
{
    Decrement (CNT)
}


switch case default 的用法

switch(Arg2)
{
    case(0)
    {
        switch(Arg1)
        {
			case(0) {return (Buffer() {0x1F})}
			case(1) {return (Buffer() {0x3F})}
		}
		return (Buffer() {0x7F})
	}
	case(1)
	{
	… function 1 code …
	Return(Zero)
	}
	case(2)
	{
	… function 2 code …
	Return(Buffer(){0x00})
	}
	case(3) { … function 3 code …}
	case(4) { … function 4 code …}
	default {BreakPoint }
}


循环控制 While, Break, Continue


While的用法

While(LEqual(Local0, 45))
{
Noop
}

Break的用法

While(LEqual(Local0, 45))
{
    If(Mod (Local0, 4))
    {
        Break
    }
    Noop
}

Continue的用法

While(LEqual(Local0, 45))
{
	If(Mod (Local0, 4))
	{
		Continue
	}
	Noop
}



OperationRegion 的使用,IO,Memory,PCI,EC读写

Spec 有一些特殊的规定

  1. OS 必须保证 SystemIO OperationRegion 在任何情况下都可以使用
  2. OS 必须保证 PCI Root Bus 下的 PCI_Config OperationRegion 一定可用
  3. OS 必须保证,SystemMemory OperationRegion 在访问通过 Memory Map Report 的 Memory 时,一定可

    用。事实上,这一条就是说明,只要是在有效地址空间中的 Memory 访问,OS 必须保证 Memory

    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

对于 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 设备的配置空间。不过,可以从 PCIOperationRegion 的定义看到,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,直接读写 ECOperationRegion,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
    }
   


[其他博客](https://blog.csdn.net/tom__mao__/article/details/51724568)



版权声明:本文为weixin_45279063原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。