前言
现在已经很久没有去更新ACPI表,整日忙于工作。工作上大体上是没有任何意义,学习不到底层上的原理只能抽空看看了。越工作越觉得自己知识深度可宽度都不行。
我在前面acpi_early_init 中看到了,OS会开辟一段ACPI表的空间创建,创建表头,创建信号量等一系列上的操作。acpi_init对这些函数进行了进一步的处理。
如果直接去看acpi_init函数实在是太过于宽泛了
init_acpi_device_notify(); 初始化ACPI设备的通知系统。
acpi_bus_init():一个ACPI(高级配置和电源接口)总线初始化函数
pci_mmcfg_late_init():初始化PCI多功能配置空间。
acpi_iort_init():初始化中断对象资源表(IORT)。
acpi_scan_init():扫描ACPI设备。
acpi_ec_init():初始化嵌入式控制器。
acpi_debugfs_init():设置ACPI的调试文件系统条目。
acpi_sleep_proc_init():初始化电源管理的睡眠过程。
acpi_wakeup_device_init():初始化可以唤醒系统的设备。
acpi_debugger_init():初始化ACPI调试器。
acpi_setup_sb_notify_handler():设置系统总线通知的处理程序。
这些函数每一个单独拿出来,那都是一个大模块,所以让我们一点拨开去看吧。
acpi_bus_init
实际上盖模块还是需要继续分解,本文按照一层层的打开去分析。
1. ACPI OS 初始化
acpi_os_initialize1();
- 这个模块负责初始化 ACPI 操作系统服务。
acpi_os_initialize1()
函数执行与操作系统相关的 ACPI 初始化工作。
以下是该函数的详细内容
acpi_status __init acpi_os_initialize1(void)
{
// 分配一个工作队列用于处理 ACPI 相关的任务,队列名称为 "kacpid"
kacpid_wq = alloc_workqueue("kacpid", 0, 1);
// 分配一个工作队列用于处理 ACPI 通知事件,队列名称为 "kacpi_notify"
kacpi_notify_wq = alloc_workqueue("kacpi_notify", 0, 1);
// 分配一个有序工作队列用于处理 ACPI 热插拔事件,队列名称为 "kacpi_hotplug"
kacpi_hotplug_wq = alloc_ordered_workqueue("kacpi_hotplug", 0);
// 检查 kacpid_wq 是否分配成功,如果失败则触发内核错误
BUG_ON(!kacpid_wq);
// 检查 kacpi_notify_wq 是否分配成功,如果失败则触发内核错误
BUG_ON(!kacpi_notify_wq);
// 检查 kacpi_hotplug_wq 是否分配成功,如果失败则触发内核错误
BUG_ON(!kacpi_hotplug_wq);
// 调用函数初始化 ACPI 操作系统兼容性字符串
acpi_osi_init();
// 返回 AE_OK,表示初始化成功
return AE_OK;
}
上述代码中一共提及到了两个比较重要的点 alloc_workqueue,acpi_osi_init。 置于工作队列,这里只做了一个简单的介绍,这不是我目前学习的重点。
workqueue是对内核线程封装的用于处理各种工作项的一种处理方法, 由于处理对象是用链表拼接一个个工作项, 依次取出来处理, 然后从链表删除,就像一个队列排好队依次处理一样, 所以也称工作队列,所谓封装可以简单理解一个中转站, 一边指向“合适”的内核线程, 一边接受你丢过来的工作项, 用结构体 workqueue_srtuct表示, 而所谓工作项也是个结构体 -- work_struct, 里面有个成员指针, 指向你最终要实现的函数。这是博客园Linux-workqueue讲解 - Vedic - 博客园 (cnblogs.com)。
acpi_osi_init
该函数里面包含了acpi_install_interface_handler(acpi_osi_handler)和acpi_osi_setup_late()这两个函数。
acpi_install_interface_handler初始化acpi_gbl_interface_handler = acpi_osi_handler,acpi_install_interface_handler函数的作用是将指定的处理程序安装到 ACPI 接口,以便在 ACPI 事件发生时进行处理。
acpi_osi_setup_late如何通过字符串操作来灵活控制和报告给 BIOS 的操作系统接口信息。通过使用空字符串、以 !
开头的字符串或普通字符串,开发人员可以禁用整个 _OSI
,禁用某个特定接口,或者向接口列表中添加新的支持项。
static void __init acpi_osi_setup_late(void)
{
struct acpi_osi_entry *osi; // 定义指向 ACPI OSI 条目的指针
char *str; // 定义指向字符串的指针,用于存储 OSI 字符串
int i; // 循环计数器
acpi_status status; // 定义状态变量,用于存储返回状态
// 检查是否需要禁用默认的 OSI 设置
if (osi_config.default_disabling) {
// 更新接口以禁用指定的 OSI 设置
status = acpi_update_interfaces(osi_config.default_disabling);
// 如果更新成功,打印相关信息
if (ACPI_SUCCESS(status))
pr_info("Disabled all _OSI OS vendors%s\n",
osi_config.default_disabling ==
ACPI_DISABLE_ALL_STRINGS ?
" and feature groups" : "");
}
// 遍历所有定义的 OSI 字符串条目
for (i = 0; i < OSI_STRING_ENTRIES_MAX; i++) {
osi = &osi_setup_entries[i]; // 获取当前 OSI 条目,这是是个