主页

ACPI调试

2024-12-23
bios

  • 参考 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地址。
  • NAT
  • 仅主机
  • 随后进入到虚拟机的/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常用的命令如namespacepathsexecute
  • 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"

PCIe层次结构

2024-10-08
bios

缩写 全称
TLP Transaction Layer Packet
  • PCIe包括事务层、数据链路层和物理层

事务层 #

  • 定义了四种事务类型

    地址空间 事务类型 介绍
    Memory READ/WRITE 从MMIO读取/写入数据
    I/O READ/WRITE 从I/O空间读取/写入数据
    Configuration READ/WRITE 设备Function配置空间
    Message Baseline 用于支持设备之间的事件交流
  • 当访问PCIe设备时,传输的保温会先通过事务层封装成一个或多个TLP。一个TLP包含多个可选TLP Prefixes、一个TLP header、一个数据载荷和一个可选的TLP Digest。数据载荷的最大长度为4096。TLP结构如下:

  • 事务包括Requests和Completions。

TLP通用Header #

  • TLP Header都有如下通用的结构,且头部总共为3DW/4DW。
  • Fmt和Type表示事务类型。
  • Fmt支持如下的值
  • Fmt和Type支持如下组合:

PCIe错误上报

2024-05-16
bios

  • PCIe定义了两种错误报告机制,baseline error reporting capabilities 和advanced error reporting capability(AER)。所有的PCIe设备都要支持baseline error reporting,AER是可选的。
  • PCIe定义了两种错误,UCE和CE,其中UCE可以进一步分为Fatal UCE和Non-Fatal UCE。

错误检测机制 #

  • 包含三种错误检测机制

    • Completion Status(用来向requester上报错误)
    • Error Messages(用来向HOST上报错误)
    • Error Forwarding(data poisoning)
  • 其中BIOS侧用的比较多的是Error Messages,可以用来向主机报告错误。

  • EP上的Error Messages会被发送到相应的Root Ports。

  • Error Message包括ERR_COR(CE)、ERR_NONFATAL(NON-FATAL UCE)、ERR_FATAL(FATAL UCE)

Error Messages #

  • EP上的Error Messages会被发送到相应的Root Ports。
  • Error Message包括ERR_COR(CE)、ERR_NONFATAL(NON-FATAL UCE)、ERR_FATAL(FATAL UCE)

  • Root Port是一个PCI-PCI Bridge结构,是从PCIe Root Complex出来的PCIe Link。

  • PCIe配置空间为0~0xFFF,其中0~0xFF为PCI兼容配置空间(PCI-compatible region),0x40~0xFF会存放Capability结构,PCI Express Capability就位于0x40~0xFF的位置。0x100~0xFFF是PCIe扩展配置空间(PCI Express Extended Configuration Space)。PCI兼容配置空间可以通过传统的IO寄存器和ECAM访问,PCIe扩展配置空间只能通过ECAM访问(PCI Express Enhanced Configuration Access Mechanism)

  • PCI Express Capability的结构如下图所示,所有的PCIe设备都需要支持该Capability,

  • PCI Express Extended Capability寄存器位于PCI配置空间的0xFF之后,在0x100~0xFFF这段空间。AER寄存器就位于这段空间。

  • 往AER的CE MASK寄存器对应BIT写1,可以屏蔽对应CE上报。

  • error messages生成的流程

  • error messages上报流程

  • 某些情况下,NON-FATAL 的错误可能是不需要执行任何的恢复动作的,比如软件尝试从一个不存在的设置执行一个读取操作,Completion中的UR Status将会报告一个错误,此时如果软件为Completer额外产生一个ERR_NONFATAL Message,就可能会导致系统运行异常。

  • Advisory Non-Fatal Error cases可以让在产生NON_FATAL ERROR时发送ERR_COR Message。如果需要让Advisory Non-Fatal Error cases处理NON_FATAL ERROR为更严重的情况,可以将该ERR的严重级别设置为FATAL,这种情况下Agent将会以ERR_FATAL发出该错误。 下图描述了ERROR MESSAGES的生成流程。

HEST上报开发小结

2024-01-12
bios

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,流程如下:
    1. 新增一条GHES,并将此GHES插入到HEST中。保存GHES中申请的地址基址。
      1. 要求此GHES的Notify Type为Polled。使用Polled后OS会定期去轮询。
    2. 创建CPER,将存在Error的物理地址写入到CPER中。
    3. 将CPER插入到GHES指向的物理地址中。(此步骤比较难,需要同步修改Generic Error Status Block和Generic Error Data Entry)。
  • 整个流程用到的数据结构的关系如下:

2. Linux串口配置 #

  • 开启Linux串口可以收集OS的日志,也可以解决OS下BIOS日志显示不全的问题

  • 开启步骤如下:

  1. 编译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"
  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内核流程如下:
  1. 在Github下载最新的Linux内核代码(下载最新的即可,老版本内核代码可能缺少后续更新的某些patch):
  1. 将内核代码传到Linux服务器上,并进行解压。
  2. 将/boot路径下的config-xxx文件拷贝到内核代码的路径,并重命名为.config。cp /boot/config-4.18.0-193.el8.x86_64 .config
  3. 进入Linux内核目录,执行make menuconfig,之后找找有没有自己感兴趣的配置,把它选上后,保存退出。
  4. 开始编译Linux内核,执行make -j128,其中-j表示并行编译,可以加快编译速度,可以根据自己CPU的核心数来调整该值的大小。
    1. 如果出现报错,是因为缺少某些库,可以自己按照报错去搜一下需要安装的软件或者需要注释掉的选项。
  5. 当编译完成后,执行make modules_install来安装内核模块。
  6. 执行make install安装Linux内核。
  7. 更新grub配置,让其重新扫描内核,并从新的Linux内核启动grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg
  8. 如果以上步骤执行完后,重新启动可以看到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-10-09
bios

  • 冯●诺伊曼计算机的基本思想:
    • 采用二进制形式表示数据和指令,指令由操作码和地址码组成。
    • 将程序和数据存放在存储器中,计算机工作时从存储器取出指令来执行。
    • 指令的执行时顺序的,程序分支由转移指令实现。
    • 计算机由存储器、控制器、运算器、输入设备和输出设备组成。
  • 现代计算机讲5大部件成为了3个硬件子系统:处理器、存储系统、输入输出系统。处理器包括运算器和控制器;存储系统由寄存器、高速缓冲存储器和辅助存储器几个层次组成。输入输出设备统称为外部设备,简称外设或I/O设备。
  • 冯●诺伊曼计算机采用二进制表示数据和指令,指令是控制计算机操作的指令,指令的二进制编码规则形成了指令的代码格式,指令由操作码和地址码组成。指令的操作码表示指令的操作,如加法操作、操作数是参与操作的数据,主要以寄存器或者存储器地址形式指明数据的来源,所以也成为地址码。
  • 程序和数据在执行前需要存放在主存储器中,在执行时才从主存储器进入处理器。现代计算机中,主存储器是字节可寻址的,主存储器的每个存储单元都具有一个地址,保存一个字节的信息。只要指定了地址就能进行存取的方式被称为随机存取。
  • 处理器的主要功能是从主存储器取指令,翻译指令代码的功能(译码),然后执行指令规定的操作。当一条指令执行完后,处理器会自动地去取下一条将要执行的指令,重复上述过程直到整个程序执行完毕。
  • 为了简化各个部件的相互连接,现代计算机使用总线结构。微处理器内集成了控制器、运算器和若干高速存储单元(寄存器)。
  • 存储系统由处理器内部的寄存器、高速缓冲存储器(Cache)、主存储器(即内存)和辅助存储器构成(如磁盘)。
  • I/O设备指的是输入设备和输出设备(外设),由于各种外设的工作速度、驱动方法差别很大,需要一个I/O接口充当外设和主机间的桥梁。较复杂的I/O接口电路通常制成独立的电路板。
  • 总线用于多个部件的相互连接。系统总线指的是微机系统中,处理器与存储器和I/O设备进行信息交换的公共通道。总线有几十条到上百条信号线,总线信号一般可以分为3组:
    • 地址总线:在该组信号线上,处理器单向输出将要访问的主存单元或I/O端口的地址信息。地址线的多少决定了系统能够直接寻址存储器的容量大小和外设端口范围。
    • 数据总线:数据总线的多少决定了一次能够传输数据的位数。处理器进行读操作时,主存或者外设的数据通过该组信号线输入处理器,处理器进行写操作时,处理器的数据通过该组信号线输出到主存或者外设。
    • 控制总线:控制总线用于协调系统中各个部件的操作。各类总线的特点主要取决于控制总线。
  • 中断是处理器正常执行程序的流程被某种原因打断并暂时停止,转向执行事先安排好的一段处理程序(中断处理程序),待处理程序结束后仍返回被中断的指令处继续执行的过程。中断来自处理器内部就是内部中断,也称为异常(Exception);中断来自外部就是外部中断。例:指令的调试需要利用中断,PC以中断的方式响应键盘输入。
  • DMA(Direct Memory Access,直接存储器读取)指主存储器和外设间直接的、不通过处理器的高速数据传输方式。
  • 控制芯片组(即多个控制芯片)提供主板上的关键逻辑电路,如主存控制单元、中断控制器、DMA控制器等,控制芯片决定主板的特性,如支持的主存类型和容量、支持的处理器类型。

xHCI

2023-09-14
bios

  • 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设备支持最高31个Endpoints。

USB设备初始化 #

  • 下面是一个连到ROOT HUB的USB设备初始化的流程:
    1. 当检测到一个USB设备连接后,xHC会将CCS和CSC置为1,并生成一个端口变更事件。
    2. 收到端口状态变更事件后,软件根据Port ID字段来确认是哪个Port生成的事件。
    3. 软件读取PORTSC寄存器。 USB3协议的Port尝试进入Enabled状态,连接的USB设备进入为Default状态。
    4. 软件通过Enable Slot命令来从xHC获取设备的slot,XHC会返回一个SLOT ID。Enable Slot执行成功后,Device Slot会进入Enabled状态。
    5. 获取到设备的slot后,软件初始化此slot关联的数据结构。
      1. 分配Input Context数据结构。
      2. 将Input Context中的Input Control Context的A0和A1标志位置为1。
      3. 初始化Input Slot Context数据结构,主要是设置Root Hub Port Number、Route String和Context Entries。
      4. 为Default Control Endpoint初始化Transfer Ring。
      5. 初始化Input Default Control Endpoint 0 Context,主要是设置EP type = Control、Max Packet Size等信息。
      6. 分配Output Device Context 数据结构,从Device Context Base Address Array中选择一个下标(Device Slot Id)用来指向Output Device Context数据结构。
    6. 软件使用Address Device命令来给设备分配地址,并启用其Default Control Endpoint。此命令会将Device Slot从Enabled状态置为Addressed状态,将USB设备从Default状态置为Address状态。
    7. 对于LS,HS, 和SS设备,其Default Control Endpoint允许的包大小是固定的,分别为8、64、512字节。对于FS设备,系统软件需要做一些操作来决定最大包大小(此处省略)。
    8. Default Control Endpoint配置完成后,系统软件可以获取到完整的Device Descriptor和Configuration Descriptor,以便将其交给适合的Class Drivers。(软件通过Endpoint 0的GET_DESCRIPTOR请求获取USB描述符)
    9. 软件会发出将Contxt Bit 0置为1的Evaluate Context命令,用来告知xHC最大退出延迟的值。此命令同样会修改Output Slot Context Interrupter部分字段的值。
    10. Class Driver会使用Configure EndPoint命令来配置Device Slot,并通过Default Control Endpoint发出USB SET_CONFIGURATION请求来设置USB设备。需要成功设置完这两项操作,才能将USB设备的状态从Address到Configured,并将Device SLot从Addressed变更为Configured
    11. 如果需要,系统软件可能会配置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对应的数据结构,流程如下:
    1. 初始化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
bios

  • UEFI的所有异步操作需要通过事件来完成。

事件函数 #

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,则定时器每个时钟滴答触发一次。
  • 生成定时器事件包含两步:
    1. 通过CreateEvent生成一个EVT_TIMER事件
    2. 通过SetTimer设置这个定时器事件的属性。

Vscode

2023-06-14
其他

  • vscode c语言中数组和指针关系紧密,DEBUG一个c程序后,在WATCH那一栏添加下面这行即可看到数组的数据
*(char(*)[4096])bitmap