2024-12-23
- 参考
ACPI AML Runtime Debugger in Ubuntu 18.04 (x64)
- 之前一直以为ACPI是无法加打印的,但是在写ASL的过程中遇到了一些难于解决的问题,经过搜索后有了下面这篇文章。
- 要启用AML的打印,需要在编译内核时指定
CONFIG_ACPI_DEBUGGER=y
这个参数,否则是无法打印的,不过ubuntu在Ubuntu 18.04 (x64)之后就默认设置了这个参数,所以可以我们可以使用ubuntu系统来做AML的调试。下面所有的代码调试都是在ubuntu server 22.04.5上实现。
环境配置
#
- 要进行ACPI调试前需要安装acpidbg工具,该工具需要联网下载,但是我的调试服务器没办法联网,所以现在本地搭建了一个一样的虚拟机,通过虚拟机下载相应的deb包,随后拷到服务器上安装。
- 安装虚拟机过程略过,下面说一下虚拟机的网络配置,virtualbox默认会启动NAT网络地址转换,NAT能让虚拟机连上外网,但是无法通过SSH连上虚拟机;我们需要启用一个仅主机网络(相当于在物理机上创建了一个virtualbox专用的虚拟网卡),通过此虚拟网卡连接到虚拟机中。此虚拟网卡可以在virtualbox中配置IP地址。
-

-

- 随后进入到虚拟机的/etc/netplan中,创建文件
99_config.yaml
,填充如下内容(详细可参考
Configuring networks|Ubuntu)
network:
version: 2
renderer: networkd
ethernets:
enp0s8:
#IP配置取决于当前virtualbox总配置的IP地址
addresses:
- "192.168.168.168/24"
enp0s3:
#virtualbox nat,下面这些信息直接填照抄即可。
addresses:
- "10.0.2.15/24"
nameservers:
addresses: [8.8.8.8]
routes:
- to: default
via: 10.0.2.2
- 随后执行
netplan apply
让网络配置生效,此时就可以通过ssh连接到虚拟机内部了。
- 通过ssh连接后,通过如下命令下载相应的deb包,执行完后,deb包会被下载到
/var/cache/apt/archives
目录下,将该目录下的deb包打包传递到服务器上安装即可。
apt install linux-tools-`uname -r` linux-tools-generic --download-only
ACPI Debug
#
- 按照如下Printf的格式加入打印信息到ASL中,%o会被替换成后面相应的变量。
Method (_SUN, 0, NotSerialized) // _SUN: Slot User Number
{
Printf("VendorId = %o, DeviceId = %o, Slot = %o", VEND, DEVI, SLTN)
Return (SLTN)
}
- 随后编译烧入BIOS后,进入服务器的OS中,执行
acpidbg
,就可以进行调试了。acpidbg
常用的命令如namespace
、paths
、execute
。
namespace
会打印出ACPI的层级关系,如下图所示
root@server:~# acpidbg
- namespace
ACPI Namespace (from Namespace Root):
0 _GPE Scope 0000000008fab98e 000
0 _PR_ Scope 000000005953797b 000
1 C000 Processor 00000000a1ac5d21 01A ID 00 Len 06 Addr 0000000000000810
2 _PCT Package 00000000a7326f94 01B Elements 02
2 _PSS Package 0000000046ef281e 01B Elements 03
2 XPSS Package 000000005f13ae73 01B Elements 03
2 _PSD Package 000000009366b23a 01B Elements 01
2 PPCV Integer 000000009ee3a624 01B = 0000000000000000
2 _PPC Method 000000007965a545 01B Args 0 Len 0005 Aml 00000000be65bdfb
1 C001 Processor 000000005eb2da62 01A ID 01 Len 06 Addr 0000000000000810
2 _PCT Package 00000000ee112324 01B Elements 02
2 _PSS Package 0000000091ffd188 01B Elements 03
2 XPSS Package 00000000fb1c13d0 01B Elements 03
2 _PSD Package 0000000039eb741a 01B Elements 01
2 PPCV Integer 00000000df6eb317 01B = 0000000000000000
2 _PPC Method 000000000665d99d 01B Args 0 Len 0005 Aml 00000000fdc3368c
1 C002 Processor 00000000bfa4f34a 01A ID 02 Len 06 Addr 0000000000000810
paths
会打印出namespace objects的全路径名
#从左到右依次是层级、Object类型以及全路径名
3 Device _SB.S0D2.D2C0
4 Integer _SB.S0D2.D2C0._ADR
4 Method _SB.S0D2.D2C0._PRW
4 Method _SB.S0D2.D2C0._PRT
4 Region _SB.S0D2.D2C0.K1PC
4 RegionField _SB.S0D2.D2C0.K1SL
4 Device _SB.S0D2.D2C0.BRG1
5 Integer _SB.S0D2.D2C0.BRG1._ADR
5 Device _SB.S0D2.D2C0.BRG1.BRG2
6 Integer _SB.S0D2.D2C0.BRG1.BRG2._ADR
6 Device _SB.S0D2.D2C0.BRG1.BRG2.BRG3
7 Integer _SB.S0D2.D2C0.BRG1.BRG2.BRG3._ADR
7 Region _SB.S0D2.D2C0.BRG1.BRG2.BRG3.PP8C
7 RegionField _SB.S0D2.D2C0.BRG1.BRG2.BRG3.VEND
7 RegionField _SB.S0D2.D2C0.BRG1.BRG2.BRG3.DEVI
7 Method _SB.S0D2.D2C0.BRG1.BRG2.BRG3._SUN
3 Device _SB.S0D2.D2C1
4 Integer _SB.S0D2.D2C1._ADR
- 如果我们需要查看一个Field、Integer或者Method的执行结果,可以使用
execute
命令。
#查看_ADR的值
- execute _SB.S0D2.D2C0._ADR
Evaluating \_SB.S0D2.D2C0._ADR
Evaluation of \_SB.S0D2.D2C0._ADR returned object 000000003e779e35, external buffer length 18
[Integer] = 0000000000030001
#查看_SUN method的执行结果
- execute _SB.S0D2.D2C0.BRG1.BRG2.BRG3._SUN
Evaluating \_SB.S0D2.D2C0.BRG1.BRG2.BRG3._SUN
ACPI Debug: "VendorId = 0000000000001D94, DeviceId = 0000000000006210, Slot = 0000000000000002"
Evaluation of \_SB.S0D2.D2C0.BRG1.BRG2.BRG3._SUN returned object 000000003e779e35, external buffer length 18
[Integer] = 0000000000000002
#查看RegionField的值
- execute _SB.S0D2.D2C0.BRG1.BRG2.BRG3.VEND
Evaluating \_SB.S0D2.D2C0.BRG1.BRG2.BRG3.VEND
Evaluation of \_SB.S0D2.D2C0.BRG1.BRG2.BRG3.VEND returned object 0000000066540b46, external buffer length 18
[Integer] = 0000000000001D94
其他
#
- 如果需要在dmesg中查看ASL中增加的打印,可以修改
/etc/default/grub
文件,加入如下配置信息(具体含义可以参考
ACPI Debug Output),并执行update-grub
让其生效,随后重启就能在dmesg中看到ASL中的打印信息了。
GRUB_CMDLINE_LINUX_DEFAULT="acpi.debug_layer=0xFFFFFFFF acpi.debug_level=0x2"
2024-01-12
1. HEST表、GHES、CPER
#
- APEI(ACPI Platform Error Interfaces):提供了一种将错误信息传递给OS的机制。
- APEI包含了四张ACPI表:
- ERST(Error Record Serialization Table)
- BERT(Boot Error Record Table)
- HEST(Hardware Error Source Table)
- EINJ (Error Injection Table)
- HEST用来将系统硬件错误传递给OSPM,HEST表的结构如下:
-

- GHES(Generic Hardware Error Source)通用硬件错误源,GHES是Error Source Structure的一种。除了GHES外,还有其他的一些错误源(IA-32 Architecture Machine Check Exception、PCI Express Root Port AER、PCI Express Device AER Structure)。
- GHES的结构如下图,其中比较重要的字段为Error Status Address和Notify。
- Error Status Address是一个地址,指向了一块大小固定的地址空间,该空间存放上报给OS的错误信息,结构如下图所示。
- Notify表示当错误发生时如何上报给OS。(支持的方式包括SCI、Polled等)
-

- CPER(Common Platform Error Record):上图中的Error Section可以用来存放CPER(详见UEFI_SPEC的 N)。CPER的结构如下图:
-

- 可以通过如下流程,将硬件错误上报给OS,流程如下:
- 新增一条GHES,并将此GHES插入到HEST中。保存GHES中申请的地址基址。
- 要求此GHES的Notify Type为Polled。使用Polled后OS会定期去轮询。
- 创建CPER,将存在Error的物理地址写入到CPER中。
- 将CPER插入到GHES指向的物理地址中。(此步骤比较难,需要同步修改Generic Error Status Block和Generic Error Data Entry)。
- 整个流程用到的数据结构的关系如下:
-

2. Linux串口配置
#
- 编译grub配置文件,在文件末尾添加如下的配置
vim /etc/default/grub
GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=ttyS0,115200n8"
GRUB_TERMINAL=serial
GRUB_SERIAL_COMMAND="serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1"
- 执行如下命令生成grub配置文件。
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
3. 编译Linux内核
#
- 在使用CPER上报OS的过程中,发现dmesg一直报错
GHES:Failed to read error status block!
,后续重新编译Linux内核,才定位到错误。
- 编译Linux内核流程如下:
- 在Github下载最新的Linux内核代码(下载最新的即可,老版本内核代码可能缺少后续更新的某些patch):
- 将内核代码传到Linux服务器上,并进行解压。
- 将/boot路径下的config-xxx文件拷贝到内核代码的路径,并重命名为.config。
cp /boot/config-4.18.0-193.el8.x86_64 .config
- 进入Linux内核目录,执行
make menuconfig
,之后找找有没有自己感兴趣的配置,把它选上后,保存退出。
- 开始编译Linux内核,执行
make -j128
,其中-j表示并行编译,可以加快编译速度,可以根据自己CPU的核心数来调整该值的大小。
- 如果出现报错,是因为缺少某些库,可以自己按照报错去搜一下需要安装的软件或者需要注释掉的选项。
- 当编译完成后,执行
make modules_install
来安装内核模块。
- 执行
make install
安装Linux内核。
- 更新grub配置,让其重新扫描内核,并从新的Linux内核启动
grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
- 如果以上步骤执行完后,重新启动可以看到grub中新增加了一个刚编译的内核的启动项。
- 内核中使用printk来添加打印信息,使用方式如下:
//KERN_ERR是打印级别
printk(KERN_ERR "%s: kpf acpi_hest_get_size(gdata) = 0x%x, data_len = 0x%x\n", __func__, acpi_hest_get_size(gdata), data_len);
- 重新编译并安装内核后,使用
dmesg -w
即可看到添加的打印信息。
4. 内存
#
- DIMM(Double In-line Memory Module)双列内存模组,双列指的是电路板两侧有两列金手指。
- SDRAM(Synchronous Dynamic Random Access Memory):同步动态随机存储器。同步指的是其时钟频率与CPU前端总线的系统时钟频率相同;动态指的是存储阵列需要不断的刷新来保证数据不丢失;随机指的是可以自由指定地址进行数据的读写。
- RANK:也叫P-Bank(Physical Bank),P-Bank是一组内存芯片的集合,这个集合的总位宽必须要和CPU的数据位宽相同。
- 每个内存芯片都有自己的位宽((SDRAM)存储单元容量=位宽,(DDR)存储单元容量=2×位宽),位宽就是每个传输周期提供的数据量。一般一个内存芯片的位宽为8Bit。
- 只有知道芯片位宽的情况下,才能确定P-BANK的数量。
- BANK:内存可以看作是一个表格,指定一个行(Row)和一个列(Column)后,就可以找到所需要的单元格了,这个单元格可以称为
存储单元
(一个存储单元可以存nbit的数据(n取决于位宽)),这个表格就叫L-BANK(逻辑Bank,也叫BANK)。
- 一个BANK一般会包含多个表格。所以在进行寻址时,需要先确定是哪个BANK,然后在这个选定的L-BANK中选择相应的行和列进行寻址。
- 内存芯片的容量(存储单元的容量) = 行数 x 列数 x L-BANK的数量。
- DDR SDRAM(Double Data Rate SDRAM):双倍数据流SDRAM。
2023-09-14
-

- USB Driver(USBD): 总线驱动,用来枚举USB设备,给USB设备安装Protocol。
- Host Controller Driver(xHCD): xHC控制器驱动。
- Host Controller (xHC):USB控制器,是一个硬件设备。
- USB Device: 包括HUB和Function,比如鼠标、键盘。
XHCI介绍
#
-
xHCI包含三个空间
-
主机配置空间:一般是PCI配置空间。
-
MMIO空间:主要放一些寄存器(Capability Registers、Operational Registers、Runtime Registers和Doorbell Array)。
-
主机内存:主要放一些数据结构,比如Device Context Base Address Array, Device Contexts, Transfer Ring等。
-

-
xHCI支持的传输类型:Isochronous(等时传输)、Interrupt(中断传输)、Control(控制传输)、Bulk(批量传输)。
-
Capability Register:这些值作为Host Controller Driver的参数。
-
Runtime和Operational Registers指定主机控制器配置和运行变化状态。系统软件通过该寄存器来控制和监控主机控制器的Operational状态。
-
xHCI Extended Capabilities说明了xHC实现的一些可选特性。
-
Doorbell Array:最多支持256个Doorbell寄存器的数组,每个Doorbell寄存器都向系统软件提供了一种机制,用于通知xHC是否有域槽位或者Endpoint相关的工作要执行。Doorbell寄存器的DB Target字段表示按下门铃的原因。Doorbell寄存器0被主机控制器用域Command Ring管理。
-
Device Slot 表示USB设备的多个XHCI数据结构。每个设备由Device Context BaseAddress Array中的一个元素、Doorbell Array register中的一个寄存器和设备的Device Context组成。Slot ID用于标识特定的Device Slot。
-
Command Ring:软件使用Command Ring将Command传递给xHC。
-
Event Ring:xHC使用Event Ring将Command Completion和Asynchronous event传递给软件。
-
Transfer Ring:软件使用Transfer Ring为Endpoint安排工作。Transfer Ring是一个循环队列(队列中每个元素都是是Transfer Descriptor(TD)),每个TD定义了一个或多个数据Buffer。
XHCI数据结构
#
Device Context Base Address Array
#
- Device Context Base Address Array (DCBAA)是一个指针数组,数组的每个元素都指向了一个Device Context数据结构。数组最多255个元素。
- DCBAA的数组下标就是SLOT ID。
- 当检测到插入了一个USB设备后:1. 软件初始化一个Device Context数据结构;2. 从xHC获取一个Slot ID;3. 将此Device Context的指针插入到DCBAA的SLOT ID的位置。
Device Context
#
- Device Context用来记录设备的配置和状态信息。
- Device Context由32个数据结构组成,第一个数据结构是Slot Context,剩余的数据结构是Endpoint Context。
- 在枚举USB设备时,软件创建一个Device Context数据结构并初始化为0,在执行了Address Device命令后将该数据结构的所属权传递给xHC。在执行了Disable Slot命令后,xHC会失去该数据结构的所属权。
typedef struct _DEVICE_CONTEXT {
SLOT_CONTEXT Slot;
ENDPOINT_CONTEXT EP[31];
} DEVICE_CONTEXT;
Slot Context
#
- Slot Context提供了control、state、addressing和电源管理。
typedef struct _SLOT_CONTEXT {
UINT32 RouteString : 20;
UINT32 Speed : 4;
UINT32 RsvdZ1 : 1;
UINT32 MTT : 1;
UINT32 Hub : 1;
UINT32 ContextEntries : 5;
UINT32 MaxExitLatency : 16;
UINT32 RootHubPortNum : 8;
UINT32 PortNum : 8;
UINT32 TTHubSlotId : 8;
UINT32 TTPortNum : 8;
UINT32 TTT : 2;
UINT32 RsvdZ2 : 4;
UINT32 InterTarget : 10;
UINT32 DeviceAddress : 8;
UINT32 RsvdZ3 : 19;
UINT32 SlotState : 5;
UINT32 RsvdZ4;
UINT32 RsvdZ5;
UINT32 RsvdZ6;
UINT32 RsvdZ7;
} SLOT_CONTEXT;
Endpoint Context
#
- Endpoint Context数据结构定义了特定的USB Endpoint的配置和状态。Endpoint Context字段包含了Endpoint相关的type、control、state和带宽信息。这些信息由USB设备提供。Endpoint Context还定义了一个TR Dequeue 指定字段,通常提供了一个指向了与此pipe关联的Transfer Ring。
Rings
#
- Ring是一个循环队列,xHC使用三种类型的Ring:
- Command Ring:(每个XHC一个)软件使用Command Ring将命令发送给xHC。
- Event Ring:(每个中断一个)xHC使用Event Ring将命令状态、结果传递给软件。
- Transfer Ring:(每个Endpoint或Stream一个)Transfer Ring被用来在内存和设备Endpoint之间传输数据。
Command接口
#
- 为了管理xHC和连接到xHC的设备,xHC提供了一个Command Ring接口,一个Command Ring上的项目被称为CD(Command Descriptor)。
- 所有命令都会在Event Ring上生成一个命令完成事件,该事件用于报告命令完成状态。
- xHCI 命令集合
命令 |
描述 |
No Op |
测试TRB Ring机制 |
Enable Slot |
返回设备的Slot ID并将设备Slot状态从Disabled改为Default |
Disable Slot |
将Device Slot从其他任何状态改为Disabled状态 |
Address Device |
启用Default Control Endpoint,(可选)向USB设备发出SET_ADDRESS命令并将Device Slot设置为Addressed状态 |
Configure Endpoint |
启用或者禁用设备的Enpoint |
Evaluate Context |
告知xHC软件已经修改了选定的Context参数 |
Reset Endpoint |
复位Endpoint,该命令用于将一个halted endpoint恢复 |
Stop Endpoint |
停止Endpoint |
Set TR Dequeue Pointer |
更新一个启用的endpoint的Transfer Ring Dequeue |
Reset Device |
复位Device Slot,此命令用于在复位一个USB设备时同步Device Slot的状态 |
Endpoint
#
USB设备初始化
#
- 下面是一个连到ROOT HUB的USB设备初始化的流程:
- 当检测到一个USB设备连接后,xHC会将CCS和CSC置为1,并生成一个端口变更事件。
- 收到端口状态变更事件后,软件根据Port ID字段来确认是哪个Port生成的事件。
- 软件读取PORTSC寄存器。 USB3协议的Port尝试进入Enabled状态,连接的USB设备进入为Default状态。
- 软件通过Enable Slot命令来从xHC获取设备的slot,XHC会返回一个SLOT ID。Enable Slot执行成功后,Device Slot会进入Enabled状态。
- 获取到设备的slot后,软件初始化此slot关联的数据结构。
- 分配Input Context数据结构。
- 将Input Context中的Input Control Context的A0和A1标志位置为1。
- 初始化Input Slot Context数据结构,主要是设置Root Hub Port Number、Route String和Context Entries。
- 为Default Control Endpoint初始化Transfer Ring。
- 初始化Input Default Control Endpoint 0 Context,主要是设置EP type = Control、Max Packet Size等信息。
- 分配Output Device Context 数据结构,从Device Context Base Address Array中选择一个下标(Device Slot Id)用来指向Output Device Context数据结构。
- 软件使用Address Device命令来给设备分配地址,并启用其Default Control Endpoint。此命令会将Device Slot从Enabled状态置为Addressed状态,将USB设备从Default状态置为Address状态。
- 对于LS,HS, 和SS设备,其Default Control Endpoint允许的包大小是固定的,分别为8、64、512字节。对于FS设备,系统软件需要做一些操作来决定最大包大小(此处省略)。
- Default Control Endpoint配置完成后,系统软件可以获取到完整的Device Descriptor和Configuration Descriptor,以便将其交给适合的Class Drivers。(软件通过Endpoint 0的GET_DESCRIPTOR请求获取USB描述符)
- 软件会发出将Contxt Bit 0置为1的Evaluate Context命令,用来告知xHC最大退出延迟的值。此命令同样会修改Output Slot Context Interrupter部分字段的值。
- Class Driver会使用Configure EndPoint命令来配置Device Slot,并通过Default Control Endpoint发出USB SET_CONFIGURATION请求来设置USB设备。需要成功设置完这两项操作,才能将USB设备的状态从Address到Configured,并将Device SLot从Addressed变更为Configured。
- 如果需要,系统软件可能会配置Alternate Interface。
Resetting a Root Hub Port
#
- 复位Root HUb port和连接上的USB设备。如果成功了,就会将PORT的状态设置为Enabled,并且可以获取到设备的Speed(位于PORTSC 的Port Speed)。
- 无论RESET是否执行成功,Port Reset Change(PRC)标志位都会置为1。如果PRC是从0变为1,则还会生成一个端口变更事件。
Device Slot Assignment
#
- 当执行完RESET PORT后,软件会向XHC发出一个Enable Slot命令(通过Command Ring),
Device Slot Initialization
#
- 一旦USB设备获得了Slot ID,软件会初始化SLOT对应的数据结构,流程如下:
- 初始化Input Context Data 数据结构,将所有字段初始化为0。
描述符
#
设备描述符
#
- 设备描述符用于表示USB设备的一般信息,如制造商ID、产品序列号等。
- 设备上电时,主机USB系统软件读取设备描述符的前8字节,得到endpoint所支持的最大数据包长度,后续控制传输就使用此值进行工作。
typedef struct {
UINT8 Length; //描述符字节长度0X12
UINT8 DescriptorType; //描述符的类型
UINT16 BcdUSB; //USB设备支持的协议版本号
UINT8 DeviceClass; //设备类代码
UINT8 DeviceSubClass; //子类代码
UINT8 DeviceProtocol; //协议码
UINT8 MaxPacketSize0; //断点0的最大包长度
UINT16 IdVendor; //厂商ID
UINT16 IdProduct; //产品ID
UINT16 BcdDevice; //设备发行号
UINT8 StrManufacturer; //厂商信息的字符串描述符索引值
UINT8 StrProduct; //产品信息的字符串描述符索引值
UINT8 StrSerialNumber; //设备序列号信息的字符串描述符索引值
UINT8 NumConfigurations; //配置描述符数目
} USB_DEVICE_DESCRIPTOR;
/*
案例
------------------------------------------
Device Descriptor
bLength : 0x0012
bDescriptorType : 0x0001
bcdUSB : 0x0320 - Spec# = 03.20
bDeviceClass : 0x00 - Defined at Interface level
bDeviceSubClass : 0x00
bDeviceProtocol : 0x00
bMaxEP0Size : 0x09 - 9 bytes
idVendor : 0x0B95 - "ASIX Electronics Corp."
idProduct : 0x1790 - "AX88179 Gigabit Ethernet"
bcdDevice : 0x0200 - Device# = 02.00
iManufacturer : 0x01 - "ASIX"
iProduct : 0x02 - "AX88179A"
iSerialNumber : 0x03 - "00F30573"
bNumConfigurations : 0x03
------------------------------------------
*/
2023-07-25
事件函数
#
WaitForEvent 等待事件发生
#
/**
@retval EFI_SUCCESS The event indicated by Index was signaled.
@retval EFI_INVALID_PARAMETER 1. NumberOfEvents is 0; 2. The event indicated by Index is of type EVT_NOTIFY_SIGNAL.
@retval EFI_UNSUPPORTED The current TPL is not TPL_APPLICATION.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_WAIT_FOR_EVENT)(
IN UINTN NumberOfEvents, //event数组的长度
IN EFI_EVENT *Event, //event数组
OUT UINTN *Index //返回触发事件的下标
);
- WaitForEvent是阻塞操作,直到Event数组内任一事件被触发或者事件导致错误出现时,WaitForEvent才返回。
- 事件触发后返回index,并将事件重置为非触发状态。
- EVT_NOTIFY_SIGNAL类型的事件似乎不能用WaitForEvent。
CreateEvent 创建事件
#
/**
@retval EFI_SUCCESS The event structure was created.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_OUT_OF_RESOURCES The event could not be allocated.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_CREATE_EVENT)(
IN UINT32 Type, //事件类型
IN EFI_TPL NotifyTpl, //Notification函数的优先级
IN EFI_EVENT_NOTIFY NotifyFunction, //Notification函数
IN VOID *NotifyContext, //传给Notification函数的参数
OUT EFI_EVENT *Event //生成的事件
);
事件类型
#
- 事件类型可以是一种或多种基本类型的组合。常用的事件类型如下:
类型 |
特征 |
EVT_TIMER |
定时器事件,没有Notification函数,生成事件后需要调用setTimer服务设置时钟属性。事件可以通过SetTimer设置等待事件、到期后通过SignalEvent触发、通过WaitForEvent等待事件触发、通过CheckEvent检查事件 |
EVT_NOTIFY_WAIT |
有一个Notification函数,当调用CheckEvent或WaitForEvent时,Notifyication函数会被放到待执行队列 |
EVT_NOTIFY_SIGNAL |
有一个Notification函数,当前事件通过SignalEvent被触发时,这个Notification函数会被放到待执行队列 |
0X00000000 |
没有Notification函数,事件可以通过signalevent触发、waitforevent等待事件被触发、checkevent检查状态 |
- 还有两种特殊的事件
- EVT_SIGNAL_EXIT_BOOT_SERVICES:当ExitBootServices被执行时,事件被触发。
- EVT_SIGNAL_VIRTUAL_ADDRESS_CHANGE:当SetVirtualAddressMap被调用时触发此类型的事件。
优先级
#
优先级 |
用法 |
函数 |
TPL_APPLICATION |
优先级最低,当程序运行在此级别时,任务队列中没有任何处于就绪状态的Notification函数 |
下列安徽念书运行在此级别ExitBootServices()、WaitForEvent()等 |
TPL_CALLBACK |
比较耗时的操作通常在这个优先级 |
Serial I/O Protocol、UnloadImage |
TPL_NOTIFY |
运行在这个级别的程序不允许阻塞,大部分Event的Notification函数允许在这个级别 |
Memory Allocation Services、HII Protocols |
TPL_HIGH_LEVEL |
UEFI内核全局变量的修改允许在这个级别 |
SignalEvent、stall |
Notification函数
#
typedef
VOID
(EFIAPI *EFI_EVENT_NOTIFY)(
IN EFI_EVENT Event, //拥有此函数的事件
IN VOID *Context //上下文指针,在CreateEvent设置
);
- 根据上面的事件类型可知,EVT_NOTIFY_WAIT的函数会在等待事件的过程中调用,而EVT_NOTIFY_SIGNAL的Notification函数会在SignalEvent调用。
CreateEventEx
#
/**
@retval EFI_SUCCESS The event structure was created.
@retval EFI_INVALID_PARAMETER One or more parameters are invalid.
@retval EFI_OUT_OF_RESOURCES The event could not be allocated.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_CREATE_EVENT_EX)(
IN UINT32 Type,
IN EFI_TPL NotifyTpl,
IN EFI_EVENT_NOTIFY NotifyFunction OPTIONAL,
IN CONST VOID *NotifyContext OPTIONAL,
IN CONST EFI_GUID *EventGroup OPTIONAL, //事件组
OUT EFI_EVENT *Event
);
- CreateEventEx用于生成事件并将事件加入事件组。当事件组中的任意事件被触发后,组中的所有事件都会被触发,进而组内所有的Notification函数都会被加入待执行队列,组内优先级最高的Notification函数会被先执行。
- 存在四个预定义的Event组:
- EFI_EVENT_GROUP_EXIT_BOOT_SERVICES:当执行ExitBootServices触发组内所有的事件。
- EFI_EVENT_GROUP_VIRTUAL_ADDRESS_CHANGE:当执行SetVirtualAddressMap触发组内所有的Event。
- EFI_EVENT_GROUP_MEMORY_MAP_CHANGE:Memory Map改变时触发组内所有的Event。
- EFI_EVENT_GROUP_READY_TO_BOOT:Boot Manager加载并且执行一个启动项时触发组内所有的Event。
CheckEvent 检查事件状态
#
/**
@retval EFI_SUCCESS 事件是触发态
@retval EFI_NOT_READY 事件是非触发态
@retval EFI_INVALID_PARAMETER 事件类型是EVT_NOTIFY_SIGNAL
**/
typedef
EFI_STATUS
(EFIAPI *EFI_CHECK_EVENT)(
IN EFI_EVENT Event
);
SignalEvent 触发事件
#
typedef
EFI_STATUS
(EFIAPI *EFI_SIGNAL_EVENT)(
IN EFI_EVENT Event
);
- 将事件设置为触发态。如果该事件在一个组中,则将族中所有的事件设置为触发态。
CloseEvent 关闭事件
#
typedef
EFI_STATUS
(EFIAPI *EFI_CLOSE_EVENT)(
IN EFI_EVENT Event
);
SetTimer
#
- EVT_TIMER是一类特殊的事件,可以通过SetTimer服务设置定时器属性。
/**
@retval EFI_SUCCESS The event has been set to be signaled at the requested time.
@retval EFI_INVALID_PARAMETER Event or Type is not valid.
**/
typedef
EFI_STATUS
(EFIAPI *EFI_SET_TIMER)(
IN EFI_EVENT Event,
IN EFI_TIMER_DELAY Type, //定时器类型
IN UINT64 TriggerTime //过期事件,100ns为一个单位
);
类型 |
|
TimerCancel |
取消定时器触发 |
TimerPeriodic |
重复型定时器 |
TimerRelative |
一次性定时器 |
- 如果Type为TimerPeriodic并且TriggerTIme是0,则定时器每个时钟滴答触发一次。
- 生成定时器事件包含两步:
- 通过CreateEvent生成一个EVT_TIMER事件
- 通过SetTimer设置这个定时器事件的属性。