ACPI _OSI 和 _REV 方法¶
ACPI BIOS 可以使用“操作系统接口”方法 (_OSI) 来查找操作系统支持的功能。例如,如果 BIOS AML 代码包含 _OSI("XYZ"),内核的 AML 解释器可以评估该方法,查看是否支持“XYZ”,并向 BIOS 回答“是”或“否”。
ACPI _REV 方法返回“OSPM 支持的 ACPI 规范修订版”。
本文档解释了 BIOS 和 Linux 如何以及为何使用这些方法。它还解释了它们如何以及为何被广泛滥用。
如何使用 _OSI¶
Linux 运行在两类机器上——一类是 OEM 测试兼容 Linux 的机器,另一类是未曾与 Linux 测试过,但 Linux 被安装以取代原始操作系统 (Windows 或 OSX) 的机器。
更大的一类是仅测试运行 Windows 的系统。不仅如此,许多系统仅测试运行一个特定版本的 Windows。因此,即使 BIOS 可能使用 _OSI 查询正在运行的 Windows 版本,但实际上只有一条通过 BIOS 的路径经过测试。经验表明,通过未经测试的 BIOS 路径会让 Linux 面临一整类 BIOS 错误。因此,Linux _OSI 默认必须继续声称与所有版本的 Windows 兼容。
但 Linux 实际上与 Windows 不兼容,当 Linux 将最新版本的 Windows 添加到其 _OSI 字符串列表时,Linux 社区也因回归而受到伤害。因此,未来在发布到上游之前,可能会有额外的字符串被更彻底地审查。但它们最终很可能都会被添加。
如果 OEM 希望使用相同的 BIOS 镜像支持 Linux 和 Windows,他们应该怎么做?通常,他们需要为 Linux 做一些不同的事情,以处理 Linux 与 Windows 的差异。
在这种情况下,OEM 应该创建自定义的 ASL,供 Linux 内核执行,并更改 Linux 内核驱动程序以执行此自定义 ASL。最简单的方法是引入一个由 Linux 内核调用的设备特定方法 (_DSM)。
过去,内核曾支持类似 _OSI("Linux-OEM-my_interface_name") 的字符串,其中“OEM”表示这是一个 OEM 特定的钩子,“my_interface_name”描述了该钩子,它可以是一个 quirk、一个 bug 或一个 bug 修复。
然而,后来发现这被其他 BIOS 供应商滥用,用于更改完全不相关系统上的完全不相关的代码。这促使对所有这些用途进行了评估。结果发现,它们不再适用于任何原始原因。因此,内核默认将不响应任何自定义的 Linux-* 字符串。
这很容易。请继续阅读,了解如何错误地使用它。
在 _OSI 之前,有 _OS¶
ACPI 1.0 将“_OS”指定为“一个评估为标识操作系统的字符串的对象”。
ACPI BIOS 流程会包含对 _OS 的评估,并且内核中的 AML 解释器会返回一个标识操作系统的字符串给它。
Windows 98, SE: “Microsoft Windows” Windows ME: “Microsoft WindowsME:Millennium Edition” Windows NT: “Microsoft Windows NT”
其理念是,在一个需要运行多个操作系统的平台上,BIOS 可以使用 _OS 来启用操作系统可能支持的设备,或者启用必要的 quirks 或 bug 解决方法,以使平台与预先存在的操作系统兼容。
但 _OS 存在根本性问题。首先,BIOS 需要知道将在其上运行的每个可能版本的操作系统的名称,并且需要知道这些操作系统的所有 quirks。当然,BIOS 向操作系统询问具体的事项会更有意义,例如“你是否支持特定的接口”,因此在 ACPI 3.0 中,_OSI 诞生并取代了 _OS。
_OS 被废弃了,尽管直到今天,许多 BIOS 仍然寻找 _OS “Microsoft Windows NT”,但这似乎有些牵强,因为没有人会把那些旧操作系统安装到机器上。
Linux 回答“Microsoft Windows NT”是为了迎合这种 BIOS 惯例。这是唯一可行的策略,因为现代 Windows 就是这样做的,否则可能会将 BIOS 引向未经测试的路径。
_OSI 诞生,并立即被滥用¶
使用 _OSI,BIOS 提供描述接口的字符串,并询问操作系统:“是/否,你是否兼容此接口?”
例如,如果操作系统知道如何处理 ACPI 3.0 规范中添加的散热扩展,_OSI("3.0 Thermal Model") 将返回 TRUE。一个不知道这些扩展的旧操作系统将返回 FALSE,而一个新操作系统可能能够返回 TRUE。
对于操作系统特定的接口,ACPI 规范指出 BIOS 和操作系统应就诸如“Windows-interface_name”之类的字符串达成一致。
但发生了两件不好的事情。首先,Windows 生态系统使用 _OSI 的方式并非按设计,而是直接替代了 _OS——用于标识操作系统版本,而不是操作系统支持的接口。事实上,从一开始,ACPI 3.0 规范本身就在示例代码中通过 _OSI("Windows 2001") 将这种滥用编入了法典。
这种滥用被采纳并延续至今。
Linux 别无选择,只能对 _OSI("Windows 2001") 及其后续版本返回 TRUE。否则,几乎可以肯定会破坏仅在该 _OSI 返回 TRUE 的情况下进行测试的 BIOS。
此策略存在问题,因为 Linux 永远无法与最新版本的 Windows 完全兼容,有时需要一年多的时间才能解决不兼容问题。
更糟糕的是,Linux 社区通过对 _OSI("Linux") 返回 TRUE 使情况变得更糟。这样做比 Windows 滥用 _OSI 更糟糕,因为“Linux”甚至不包含任何版本信息。_OSI("Linux") 导致一些 BIOS 出现故障,原因是 BIOS 编写者在未经测试的 BIOS 流程中使用它。但一些 OEM 在经过测试的流程中使用 _OSI("Linux") 来支持真正的 Linux 功能。2009 年,Linux 删除了 _OSI("Linux"),并添加了一个命令行参数以恢复它,以支持仍然需要它的旧系统。此外,对于所有调用它的 BIOS,都会打印 BIOS_BUG 警告。
任何 BIOS 都不应使用 _OSI("Linux")。
结果是 Linux 采取了一种策略,以最大限度地兼容在 Windows 机器上测试过的 ACPI BIOS。过度声明这种兼容性存在真正的风险;但替代方案往往是灾难性的故障,因为 BIOS 采取了在任何操作系统下都未经验证的路径。
不要使用 _REV¶
由于 _OSI("Linux") 已被移除,一些 BIOS 编写者使用 _REV 来支持同一 BIOS 中的 Linux 和 Windows 差异。
_REV 在 ACPI 1.0 中定义,用于返回操作系统和操作系统 AML 解释器支持的 ACPI 版本。
现代 Windows 返回 _REV = 2。Linux 使用 ACPI_CA_SUPPORT_LEVEL,它会根据所支持的规范版本递增。
不幸的是,_REV 也被滥用了。例如,一些 BIOS 会检查 _REV = 3,并为 Linux 执行某些操作,但当 Linux 返回 _REV = 4 时,该支持就中断了。
为了应对这个问题,从 2015 年年中开始,Linux 始终返回 _REV = 2。ACPI 规范也将更新,以反映 _REV 已弃用,并始终返回 2。
Apple Mac 和 _OSI("Darwin")¶
在 Apple 的 Mac 平台上,ACPI BIOS 调用 _OSI("Darwin") 来确定机器是否正在运行 Apple OSX。
就像 Linux 的 _OSI("Windows") 策略一样,Linux 默认对 _OSI("Darwin") 回答“是”,以完全访问硬件和 OSX 验证过的 BIOS 路径。就像在 Windows 测试平台上一样,此策略也存在风险。
从 Linux-3.18 开始,内核对 _OSI("Darwin") 回答“是”,目的是启用 Mac Thunderbolt 支持。此外,如果内核注意到 _OSI("Darwin") 被调用,它还会禁用所有 _OSI("Windows"),以防止编写不佳的 Mac BIOS 走上未经测试的路径组合。
Linux-3.18 默认设置的改变导致 Mac 笔记本电脑出现电源回归问题,而且 3.18 的实现不允许通过命令行参数“acpi_osi=!Darwin”更改默认设置。Linux-4.7 修复了使用 acpi_osi=!Darwin 作为变通方法的能力,我们希望在 Linux-4.11 中看到 Mac Thunderbolt 电源管理支持。