一. acpi与platform设备
platform设备就是为了给嵌入式设备用的,而嵌入式arm架构基本都是使用设备树,一般的过程是这样
① 在扫描设备树文件的时候,就会自动调用platform_device_register去添加设备
② 然后再用compatible字段去匹配驱动
但在acpi情况下,acpi也有acpi表,我们拿定时设备来说,一般都是GTDT表,和设备树一样,这里面也是有很多描述硬件资源的表项,但platform总线并不会扫描GTDT表,只会扫描设备树。这就需要ACPI上的platform设备自己手动注册platform设备
理论终究是枯燥的,上实例,用sbsa的watchdog来说明:
dirvers/acpi/gtdt.c,此文件是acpi专门留给定时器的后台注册文件,实现了诸多对GTDT表项的接口。而sbsa在此处完成了自己扫描GTDT,并注册platform设备的工作
如果有兴趣研究sbsa的源码,可以去drivers/watchdog/sbsa_gwdt.c去看
gtdt_sbsa_gwdt_init
static int __init gtdt_sbsa_gwdt_init(void)
{
void *platform_timer;
struct acpi_table_header *table;
int ret, timer_count, gwdt_count = 0;
······
/*
* Note: Even though the global variable acpi_gtdt_desc has been
* initialized by acpi_gtdt_init() while initializing the arch timers,
* when we call this function to get SBSA watchdogs info from GTDT, the
* pointers stashed in it are stale (since they are early temporary
* mappings carried out before acpi_permanent_mmap is set) and we need
* to re-initialize them with permanent mapped pointer values to let the
* GTDT parsing possible.
*/
// 获取gtdt表
ret = acpi_gtdt_init(table, &timer_count);
if (ret || !timer_count)
return ret;
// 遍历GTDT表项,拿到对应数据放到platform_timer中
for_each_platform_timer(platform_timer中) {
if (is_non_secure_watchdog(platform_timer)) {
// 进一步初始化
ret = gtdt_import_sbsa_gwdt(platform_timer, gwdt_count);
if (ret)
break;
gwdt_count++;
}
}
if (gwdt_count)
pr_info("found %d SBSA generic Watchdog(s).\n", gwdt_count);
return ret;
}
device_initcall(gtdt_sbsa_gwdt_init);
gtdt_import_sbsa_gwdt
/*
* Initialize a SBSA generic Watchdog platform device info from GTDT
*/
// 注释也能看出来这个函数的作用
static int __init gtdt_import_sbsa_gwdt(struct acpi_gtdt_watchdog *wd,
int index)
{
struct platform_device *pdev;
// 从GTDT表中拿的数据,来计算中断号,和设备树是一样的逻辑
int irq = map_gt_gsi(wd->timer_interrupt, wd->timer_flags);
/*
* According to SBSA specification the size of refresh and control
* frames of SBSA Generic Watchdog is SZ_4K(Offset 0x000 – 0xFFF).
*/
// 控制寄存器和刷新寄存器的基地址
struct resource res[] = {
DEFINE_RES_MEM(wd->control_frame_address, SZ_4K),
DEFINE_RES_MEM(wd->refresh_frame_address, SZ_4K),
DEFINE_RES_IRQ(irq),
};
// 资源数量
int nr_res = ARRAY_SIZE(res);
······错误校验代码······
/*
* Add a platform device named "sbsa-gwdt" to match the platform driver.
* "sbsa-gwdt": SBSA(Server Base System Architecture) Generic Watchdog
* The platform driver can get device info below by matching this name.
*/
// 注意这里只是添加了一个设备,并不会匹配驱动,驱动的匹配时机在platform总线轮询的时候才会执行
pdev = platform_device_register_simple("sbsa-gwdt", index, res, nr_res);
if (IS_ERR(pdev)) {
acpi_unregister_gsi(wd->timer_interrupt);
return PTR_ERR(pdev);
}
return 0;
}
看完这些代码,才可以真正理解“手动添加“platform设备的含义,为了与设备树相比较,我们可以把GTDT表与设备树相比较看一下
GTDT表与设备树
GTDT表
GTDT表是ACPI表的一种,具体可自行查阅ACPI表的种类,很多,且这部分表由固件厂家(搞SOC板卡的)和硬件厂家共同维护
我们现在电脑看到的BIOS,实际上是ACPI+UEFI来进行的引导,因为UEFI有图形化界面,这个是intel开发的,实际上现在看到所谓的昆仑,鲲鹏固件等BIOS,都是基于这个框架。现在armv8服务器也开始使用ACPI机制,而放弃设备树了。
GTDT表可以使用 acpidump -o acpi_table.dat + acpixtract -s GTDT acpi_table.dat来直接导出,但一般情况下没有这个工具,介绍另一种方法
首先拿到二进制的GTDT表:
cp /sys/firmware/acpi/tables/GTDT ./gtdt.dat
然后用iasl命令直接解析就行了
root@kylin-pc:/home/kylin/桌面# iasl -d gtdt.dat
Intel ACPI Component Architecture
ASL+ Optimizing Compiler/Disassembler version 20190509
Copyright (c) 2000 - 2019 Intel Corporation
File appears to be binary: found 121 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file gtdt.dat, Length 0x98 (152) bytes
ACPI: GTDT 0x0000000000000000 000098 (v02 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
Acpi Data Table [GTDT] decoded
Formatted output: gtdt.dsl - 4125 bytes
我们来看看这个解析出来是什么,后面见注释
/*
* Intel ACPI Component Architecture
* AML/ASL+ Disassembler version 20190509 (64-bit version)
* Copyright (c) 2000 - 2019 Intel Corporation
*
* Disassembly of gtdt.dat, Thu Apr 24 17:02:56 2025
*
* ACPI Data Table [GTDT]
*
* Format: [HexOffset DecimalOffset ByteLength] FieldName : FieldValue
*/
标识表类型为 Generic Timer Description Table,用于描述ARM架构的通用定时器和看门狗资源
[000h 0000 4] Signature : "GTDT" [Generic Timer Description Table]
[004h 0004 4] Table Length : 00000098 表总长度为 152字节(0x98),与末尾的Raw Data长度一致。
[008h 0008 1] Revision : 02 符合 ACPI 6.3及以上版本规范,支持平台定时器和安全扩展功能
[009h 0009 1] Checksum : 82 校验和值,通过所有字节相加取模256等于0来验证完整性
[00Ah 0010 6] Oem ID : "PHYLTD" 原始设备制造商(OEM)标识符,此处为 Phytium(飞腾)公司 的简写
[010h 0016 8] Oem Table ID : "PHYTIUM."厂商自定义表标识符,可能对应特定硬件平台(如Phytium ARM服务器)
[018h 0024 4] Oem Revision : 00000001 厂商自定义版本号,表示首次发布或修订版本
[01Ch 0028 4] Asl Compiler ID : "PHYT" ACPI编译器标识符,此处为Phytium的定制编译器
[020h 0032 4] Asl Compiler Revision : 00000001 编译器版本号,与厂商工具链相关。
[024h 0036 8] Counter Block Address : FFFFFFFFFFFFFFFF 系统计数器(System Counter)的MMIO地址,全F表示 未启用或保留字段
[02Ch 0044 4] Reserved : 00000000 保留字段,通常填充0
[030h 0048 4] Secure EL1 Interrupt : 0000001D 安全态EL1级别的定时器中断号为 29(0x1D)
[034h 0052 4] EL1 Flags (decoded below) : 00000006 中断属性解码:
Trigger Mode : 0 Trigger Mode : 0 → 电平触发(Level-sensitive)
Polarity : 1 Polarity : 1 → 高电平有效(Active High)
Always On : 1 Always On : 1 → 定时器在低功耗状态下保持运行
[038h 0056 4] Non-Secure EL1 Interrupt : 0000001E 非安全态EL1中断号为 30(0x1E),属性与安全态相同
[03Ch 0060 4] NEL1 Flags (decoded below) : 00000006
Trigger Mode : 0
Polarity : 1
Always On : 1
[040h 0064 4] Virtual Timer Interrupt : 0000001B 虚拟定时器中断号为 27(0x1B),属性同上
[044h 0068 4] VT Flags (decoded below) : 00000006
Trigger Mode : 0
Polarity : 1
Always On : 1
[048h 0072 4] Non-Secure EL2 Interrupt : 0000001A 非安全态EL2中断号为 26(0x1A),属性同上
[04Ch 0076 4] NEL2 Flags (decoded below) : 00000006
Trigger Mode : 0
Polarity : 1
Always On : 1
[050h 0080 8] Counter Read Block Address : FFFFFFFFFFFFFFFF 系统计数器读取块地址,未启用。
[058h 0088 4] Platform Timer Count : 00000002 包含 2个平台定时器条目,此处均为看门狗类型
[05Ch 0092 4] Platform Timer Offset : 00000060 平台定时器条目起始偏移为 0x60(即从表头开始的第96字节)
看门狗条目(Watchdog Subtable)
[060h 0096 1] Subtable Type : 01 [Generic Watchdog Timer] 类型为 Generic Watchdog Timer(SBSA兼容看门狗)
[061h 0097 2] Length : 001C
[063h 0099 1] Reserved : 00
[064h 0100 8] Refresh Frame Address : 000000002800A000 看门狗刷新寄存器基地址:0x2800A000
[06Ch 0108 8] Control Frame Address : 000000002800B000 看门狗控制寄存器基地址:0x2800B000。
[074h 0116 4] Timer Interrupt : 00000030 看门狗中断号为 48(0x30)
[078h 0120 4] Timer Flags (decoded below) : 00000000
Trigger Mode : 0
Polarity : 0
Security : 0
结构与条目1类似,地址为 0x28016000(Refresh)和 0x28017000(Control),中断号为 49
[07Ch 0124 1] Subtable Type : 01 [Generic Watchdog Timer]
[07Dh 0125 2] Length : 001C
[07Fh 0127 1] Reserved : 00
[080h 0128 8] Refresh Frame Address : 0000000028016000
[088h 0136 8] Control Frame Address : 0000000028017000
[090h 0144 4] Timer Interrupt : 00000031
[094h 0148 4] Timer Flags (decoded below) : 00000000
Trigger Mode : 0
Polarity : 0
Security : 0
Raw Table Data: Length 152 (0x98)
0000: 47 54 44 54 98 00 00 00 02 82 50 48 59 4C 54 44 // GTDT......PHYLTD
0010: 50 48 59 54 49 55 4D 2E 01 00 00 00 50 48 59 54 // PHYTIUM.....PHYT
0020: 01 00 00 00 FF FF FF FF FF FF FF FF 00 00 00 00 // ................
0030: 1D 00 00 00 06 00 00 00 1E 00 00 00 06 00 00 00 // ................
0040: 1B 00 00 00 06 00 00 00 1A 00 00 00 06 00 00 00 // ................
0050: FF FF FF FF FF FF FF FF 02 00 00 00 60 00 00 00 // ............`...
0060: 01 1C 00 00 00 A0 00 28 00 00 00 00 00 B0 00 28 // .......(.......(
0070: 00 00 00 00 30 00 00 00 00 00 00 00 01 1C 00 00 // ....0...........
0080: 00 60 01 28 00 00 00 00 00 70 01 28 00 00 00 00 // .`.(.....p.(....
0090: 31 00 00 00 00 00 00 00 // 1.......
设备树
在嵌入式的arm中,启动的方式就变为了uboot + 设备树DT的方式
我们的老朋友设备树就不用说了,在这里:
总结
重中之重,现在的主流两种arm架构的启动方式
① UEFI + ACPI表(acpi支持硬件动态编码,这个不太了解是什么机制)
② uboot +设备树