WMI 驱动开发指南¶
WMI 子系统提供了丰富的驱动程序 API,用于实现 WMI 驱动程序,相关文档位于WMI 驱动 API。本文档将作为使用此 API 的 WMI 驱动程序开发人员的入门指南。它旨在取代原始的 LWN 文章[1],后者涉及使用已弃用的基于 GUID 的 WMI 接口的 WMI 驱动程序。
获取 WMI 设备信息¶
在开发 WMI 驱动程序之前,必须获取有关目标 WMI 设备的信息。lswmi 工具可用于使用以下命令提取详细的 WMI 设备信息
lswmi -V
结果输出将包含给定机器上所有可用 WMI 设备的信息,以及一些额外信息。
为了进一步了解与 WMI 设备通信所使用的接口,可以使用 bmfdec 工具来解码用于描述 WMI 设备的二进制 MOF(Managed Object Format,托管对象格式)信息。wmi-bmof
驱动程序将此信息暴露给用户空间,请参阅WMI 嵌入式二进制 MOF 驱动程序。
要检索已解码的二进制 MOF 信息,请使用以下命令(需要 root 权限)
./bmf2mof /sys/bus/wmi/devices/05901221-D566-11D1-B2F0-00A0C9062910[-X]/bmof
有时,查看用于描述 WMI 设备的已反汇编 ACPI 表有助于理解 WMI 设备的工作原理。与给定 WMI 设备关联的 ACPI 方法的路径可以使用上述 lswmi
工具检索。
如果您正在尝试将驱动程序移植到 Linux 并在 Windows 系统上工作,WMIExplorer 可用于检查可用的 WMI 方法并直接调用它们。
基本 WMI 驱动程序结构¶
基本的 WMI 驱动程序围绕 struct wmi_driver
构建,然后使用 struct wmi_device_id
表绑定到匹配的 WMI 设备
static const struct wmi_device_id foo_id_table[] = {
{ "936DA01F-9ABD-4D9D-80C7-02AF85C822A8", NULL },
{ }
};
MODULE_DEVICE_TABLE(wmi, foo_id_table);
static struct wmi_driver foo_driver = {
.driver = {
.name = "foo",
.probe_type = PROBE_PREFER_ASYNCHRONOUS, /* recommended */
.pm = pm_sleep_ptr(&foo_dev_pm_ops), /* optional */
},
.id_table = foo_id_table,
.probe = foo_probe,
.remove = foo_remove, /* optional, devres is preferred */
.shutdown = foo_shutdown, /* optional, called during shutdown */
.notify = foo_notify, /* optional, for event handling */
.no_notify_data = true, /* optional, enables events containing no additional data */
.no_singleton = true, /* required for new WMI drivers */
};
module_wmi_driver(foo_driver);
当 WMI 驱动程序绑定到匹配的 WMI 设备时,会调用 probe() 回调。分配驱动程序特定的数据结构以及初始化与其他内核子系统的接口通常应在此函数中完成。
当 WMI 驱动程序从 WMI 设备解绑时,会调用 remove() 回调。为了注销与其他内核子系统的接口并释放资源,应使用 devres。这简化了探测期间的错误处理,并且通常允许完全省略此回调,详情请参阅Devres - 管理设备资源。
shutdown() 回调在关机、重启或 kexec 期间被调用。其唯一目的是禁用 WMI 设备,并将其置于 WMI 驱动程序在重启或 kexec 后可以稍后拾取(即恢复)的已知状态。大多数 WMI 驱动程序不需要特殊的关机处理,因此可以省略此回调。
请注意,新的 WMI 驱动程序必须能够多次实例化,并且禁止使用任何已弃用的基于 GUID 的 WMI 函数。这意味着 WMI 驱动程序应该为给定机器上存在多个匹配 WMI 设备的情况做好准备。
因此,WMI 驱动程序应使用设备驱动程序设计模式中描述的状态容器设计模式。
警告
在同一设备上同时使用基于 GUID 和非基于 GUID 的函数来查询 WMI 数据块和处理 WMI 事件,必然会损坏 WMI 设备状态,并可能导致异常行为。
WMI 方法驱动程序¶
WMI 驱动程序可以使用 wmidev_evaluate_method()
调用 WMI 设备方法,传递给此函数的 ACPI 缓冲区结构是设备特定的,通常需要一些调试才能正确。查看包含 WMI 设备的 ACPI 表通常会有帮助。传递给此方法的方法 ID 和实例号也是设备特定的,查看已解码的二进制 MOF 通常足以找到正确的值。
最大实例数可以在运行时使用 wmidev_instance_count()
检索。
请查看 drivers/platform/x86/inspur_platform_profile.c 以获取 WMI 方法驱动程序的示例。
WMI 数据块驱动程序¶
WMI 驱动程序可以使用 wmidev_block_query()
查询 WMI 设备数据块,返回的 ACPI 对象结构再次是设备特定的。某些 WMI 设备也允许使用 wmidev_block_set()
设置数据块。
最大实例数也可以使用 wmidev_instance_count()
检索。
请查看 drivers/platform/x86/intel/wmi/sbl-fw-update.c 以获取 WMI 数据块驱动程序的示例。
WMI 事件驱动程序¶
WMI 驱动程序可以通过 struct wmi_driver
中的 notify() 回调接收 WMI 事件。WMI 子系统将负责相应地设置 WMI 事件。请注意,传递给此回调的 ACPI 对象的结构是设备特定的,并且 ACPI 对象的释放由 WMI 子系统而不是驱动程序完成。
WMI 驱动程序核心将确保 notify() 回调仅在 probe() 回调之后被调用,并且在调用 remove() 或 shutdown() 回调之前和之后,驱动程序不会接收到任何事件。
然而,WMI 驱动程序开发人员应该注意,可以并发接收多个 WMI 事件,因此任何锁定(如有必要)都需要由 WMI 驱动程序自身提供。
为了能够接收不包含额外事件数据的 WMI 事件,应将 struct wmi_driver
中的 no_notify_data
标志设置为 true
。
请查看 drivers/platform/x86/xiaomi-wmi.c 以获取 WMI 事件驱动程序的示例。
同时处理多个 WMI 设备¶
有许多固件供应商使用多个 WMI 设备来控制单个物理设备的不同方面。这会使 WMI 驱动程序的开发变得复杂,因为这些驱动程序可能需要相互通信,以便向用户空间呈现统一的接口。
其中一种情况涉及一个 WMI 事件设备,它在接收到 WMI 事件时需要与 WMI 数据块设备或 WMI 方法设备通信。在这种情况下,应该开发两个 WMI 驱动程序,一个用于 WMI 事件设备,另一个用于其他 WMI 设备。
WMI 事件设备驱动程序只有一个目的:接收 WMI 事件,验证任何额外的事件数据并调用通知链。另一个 WMI 驱动程序在探测期间将自身添加到此通知链中,从而在每次收到 WMI 事件时得到通知。然后,此 WMI 驱动程序可以通过使用输入设备等方式进一步处理事件。
对于其他 WMI 设备组合,可以使用类似的机制。
应避免的事项¶
在开发 WMI 驱动程序时,有几点应该避免
使用已弃用的基于 GUID 的 WMI 接口,该接口使用 GUID 而不是 WMI 设备结构体
与 WMI 设备通信时绕过 WMI 子系统
无法多次实例化的 WMI 驱动程序。
许多旧的 WMI 驱动程序违反了此列表中的一点或多点。原因是 WMI 子系统在过去二十年中发展迅速,因此旧的 WMI 驱动程序中存在许多遗留代码。
新的 WMI 驱动程序还必须符合Linux 内核编码风格中指定的 Linux 内核编码风格。checkpatch 工具可以捕获许多常见的编码风格违规,您可以使用以下命令调用它
./scripts/checkpatch.pl --strict <path to driver file>