文章目录
- 前言
- 一、Why ACPI on ARM?
- 二、Kernel Compatibility
- 三、Relationship with Device Tree
- 四、Booting using ACPI tables
- 五、ACPI Detection
- 六、Device Enumeration
- 七、Driver Recommendations
- 参考资料
前言
ARM64处理器除了可以用设备树(DT)描述系统中设备的硬件,提供硬件抽象表述,还可以用ACPI(Advanced Configuration and Power Interface),比如:
# ls -l /sys/firmware/
acpi/ devicetree/ dmi/ efi/ fdt
# ls -l /sys/firmware/acpi/tables/
APIC BGRT data/ DSDT dynamic/ FACP GTDT IORT MCFG PPTT SPCR SSDT
ARM64处理器兼容设备树和ACPI两者模式,但在内核启动时ACPI和DT只能选择一种启动方式。
ARM在移动端、嵌入式领域通常都是uboot+DT,ARM在服务器和桌面领域时,可以选择UEFI+ACPI,ACPI相比于DT,ACPI不但实现了硬件设备的静态抽象,还实现了硬件行为的动态抽象,同时ACPI基本占据了服务器生态。
比如我手中的飞腾处理器+麒麟桌面操作系统,就是采用和UEFI+ACPI:
[ 0.000000] efi: Getting EFI parameters from FDT:
[ 0.000000] efi: EFI v2.70 by EDK II
[ 0.000000] efi: SMBIOS=0xeb790000 SMBIOS 3.0=0xeb770000 ACPI 2.0=0xec000000 MEMATTR=0xdeaefa98 MEMRESERVE=0xd5909e98
[ 0.000000] secureboot: Secure boot disabled
[ 0.000000] cma: Reserved 32 MiB at 0x00000000fa000000
[ 0.000000] ACPI: Early table checksum verification disabled
[ 0.000000] ACPI: RSDP 0x00000000EC000000 000024 (v02 PHYLTD)
[ 0.000000] ACPI: XSDT 0x00000000EBFF0000 00006C (v01 PHYLTD PHYTIUM. 00000001 GGCL 00010000)
[ 0.000000] ACPI: FACP 0x00000000EBFE0000 000114 (v06 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: DSDT 0x00000000EBF70000 0014AC (v02 PHYLTD PHYTIUM. 00000001 INTL 20190509)
[ 0.000000] ACPI: GTDT 0x00000000EBFD0000 000098 (v02 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: IORT 0x00000000EBFC0000 000080 (v00 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: APIC 0x00000000EBFB0000 0002D8 (v04 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: MCFG 0x00000000EBFA0000 00003C (v01 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: SPCR 0x00000000EBF90000 000050 (v02 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: SSDT 0x00000000EBF80000 000515 (v02 PHYLTD PHYTIUM. 00000001 INTL 20190509)
[ 0.000000] ACPI: PPTT 0x00000000EBF60000 000396 (v01 PHYLTD PHYTIUM. 00000001 PHYT 00000001)
[ 0.000000] ACPI: BGRT 0x00000000EBF50000 000038 (v01 GWTLTD GWTLBGRT 00010000 GGCL 00010000)
ACPI可用于ARMv8通用服务器,其设计符合ARM SBSA(服务器基础系统架构)和SBBR(服务器基础引导要求)规范。
ARMv8内核实现了ACPI 5.1版或更高版本的精简硬件模式。在该模式中,没有使用硬件实现 ACPI 固定硬件接口,而是使用对应的软件替代方案。并且设置HW_REDUCED_ACPI 标志位
规范及其引用的所有外部文档的链接由UEFI论坛管理。请参考:https://uefi.org/specifications
根据 Linux 文档说明,对于 ARM64 上的 ACPI,表格分为如下类型:
Required: DSDT, FADT, GTDT, MADT, MCFG, RSDP, SPCR, XSDT
Recommended: BERT, EINJ, ERST, HEST, PCCT, SSDT
Optional: BGRT, CPEP, CSRT, DBG2, DRTM, ECDT, FACS, FPDT, IBFT, IORT, MCHI, MPST, MSCT, NFIT, PMTT, RASF, SBST, SLIT, SPMI, SRAT, STAO, TCPA, TPM2, UEFI, XENV
Not supported: BOOT, DBGP, DMAR, ETDT, HPET, IVRS, LPIT, MSDM, OEMx, PSDT, RSDT, SLIC, WAET, WDAT, WDRT, WPBT
ARM64服务器处理器通常要求实现上述 Required 表格这一项内容,其它表格项可由固件和OS根据需要实现。
如果ARMv8系统不符合SBSA和SBBR的要求,或者不能使用所需的ACPI规范中定义的机制进行描述,则ACPI可能不太适合该硬件。
接下来描述ARMv8系统上ACPI与Linux之间的交互。
一、Why ACPI on ARM?
在检查ACPI和Linux之间的接口细节之前,了解ACPI被使用的原因是很有用的。毕竟,Linux中已经存在一些描述不可枚举硬件的技术(比如设备树)。
这篇博文:https://www.secretlab.ca/archives/151,概述了ARMv8服务器上ACPI背后的原因。总结如下所示:
(1)ACPI的字节码(AML)允许平台对硬件行为进行编码,而DT明确不支持这一点。对于硬件供应商来说,能够编码行为是支持新硬件上的操作系统版本的关键工具。
(2)ACPI的OSPM定义了一个电源管理模型,该模型将平台允许做的事情限制在一个特定的模型中,同时还提供了硬件设计的灵活性。
(3)在企业服务器环境中,ACPI已经建立了当前在生产系统中使用的绑定(例如RAS)。DT没有。这样的绑定可以在DT中定义,但这样做意味着ARM和x86最终将在固件和内核中使用完全不同的代码路径。
(4)选择单一接口来描述平台和操作系统之间的抽象非常重要。如果硬件供应商想要支持多个操作系统,则不需要同时实现DT和ACPI。而且,在单个接口上达成一致,而不是将其分散到每个操作系统的接口中,有助于实现更好的整体互操作性。
(5)新的ACPI管理过程运行良好,Linux现在与硬件供应商和其他操作系统供应商处于同一位置。事实上,再也没有任何理由认为ACPI只属于Windows,或者在这一领域Linux在任何方面都是微软的次要产品。ACPI管理向UEFI论坛的转移极大地开放了规范开发过程,目前,ACPI的大部分更改都是由Linux驱动的。
ACPI使用的关键是 the support model 。对于一般的服务器,硬件行为的责任不能仅仅是内核的领域,而是必须在平台和内核之间划分,以允许随着时间的推移进行有序的更改。ACPI使操作系统无需了解硬件的所有细节,从而无需将操作系统单独移植到每个设备。它允许硬件供应商对电源管理行为负责,而不依赖于不受其控制的OS发布周期。
ACPI重要的一个原因是硬件和操作系统供应商已经制定出支持通用计算生态系统的机制(The infrastructure、 the bindings 、 the processes等都是现成的)。当使用纵向集成设备时,虽然完全符合Linux的要求,但没有很好的流程来支持服务器供应商的需要。Linux有可能通过DT实现这一目标,但这样做实际上只是复制了一些已经有效的东西。ACPI已经满足了硬件供应商的需求,微软不会在DT上合作,硬件供应商最终仍然会提供两个完全独立的固件接口:一个用于Linux,一个用于Windows。
对于ARM64,ACPI相比于DT来说跟适合用于服务器领域。
二、Kernel Compatibility
ACPI的主要动机之一是标准化,并利用它为Linux内核提供向后兼容性。在服务器市场上,软件和硬件经常被长期使用。ACPI允许内核和固件在一个一致的抽象层上达成一致,即使在硬件或软件发生变化时,该抽象层也可以随时间保持。只要支持抽象层,就可以更新系统,而不必更换内核。
ACPI充当操作系统和系统固件之间的接口层:
当Linux驱动程序或子系统第一次使用ACPI实现时,根据定义,它最终需要特定版本的ACPI规范–它是基准。ACPI固件必须继续工作,即使它可能不是最优的,因为最早的内核版本首先提供对ACPI基准版本的支持。可能需要额外的驱动程序,但添加新功能(例如,CPU电源管理)不应破坏较旧的内核版本。此外,ACPI固件还必须与最新版本的内核一起工作。
三、Relationship with Device Tree
ARMv8驱动程序和子系统中的ACPI支持在编译时不应与DT互斥,即两者编译兼容,两者可同时存在,再启动阶段选择一种。
在引导时,内核将仅使用一种描述方法,这取决于从引导加载器传递的参数(包括内核引导参数)。
无论使用DT还是ACPI,内核必须始终能够使用任一方案启动(在编译时启用两种方案的内核中)。
因此驱动可以 DT 和 ACPI 两种启动方式,如下所示:
static const struct of_device_id static_replicator_match[] = {
{.compatible = "arm,coresight-replicator"},
{.compatible = "arm,coresight-static-replicator"},
{}
};
MODULE_DEVICE_TABLE(of, static_replicator_match);
#ifdef CONFIG_ACPI
static const struct acpi_device_id static_replicator_acpi_ids[] = {
{"ARMHC985", 0}, /* ARM CoreSight Static Replicator */
{}
};
MODULE_DEVICE_TABLE(acpi, static_replicator_acpi_ids);
#endif
static struct platform_driver static_replicator_driver = {
.probe = static_replicator_probe,
.remove = static_replicator_remove,
.driver = {
.name = "coresight-static-replicator",
/* THIS_MODULE is taken care of by platform_driver_register() */
.of_match_table = of_match_ptr(static_replicator_match),
.acpi_match_table = ACPI_PTR(static_replicator_acpi_ids),
.pm = &replicator_dev_pm_ops,
.suppress_bind_attrs = true,
},
};
of_match_table 用于从DT启动匹配硬件设备。
acpi_match_table 用于从ACPI启动匹配硬件设备。
驱动设备有套方案,一套用于解析DT,一套用于解析ACPI,从解析的数据中获取相应的硬件属性。
ARM在移动端和嵌入式领域通常都是uboot+DT。
ARM在服务器和桌面领域时,可以选择UEFI+ACPI。
四、Booting using ACPI tables
在ARMv8上将ACPI表传递给内核的唯一定义方法是通过UEFI系统配置表。正因为如此,这意味着ACPI仅在通过UEFI引导的平台上受支持。
当ARMv8系统启动时,它可以有DT信息、ACPI表,或者在一些非常不寻常的情况下,两者都有。如果没有使用命令行参数,内核将尝试使用DT进行设备枚举;如果不存在DT,内核将尝试使用ACPI表,但前提是它们存在。如果两者都不可用,内核将无法启动。如果在命令行上使用acpi=force,内核将尝试首先使用acpi表,但如果没有acpi表则返回DT。
通过在内核命令行上传递ACPI=off,可以 disable ACPI表的处理;这是默认行为。
五、ACPI Detection
驱动程序应该通过检查ACPI_HANDLE的空值、检查.of_node或设备结构中的其他信息来确定其probe()类型。
在非驱动程序代码中,如果需要在运行时检测ACPI的存在,请检查acpi_disabled的值。如果未设置CONFIG_ACPI,acpi_disabled将始终为1。
六、Device Enumeration
ACPI中的设备描述应使用标准认可的ACPI接口。这些可能包含比通常通过设备树描述为同一设备提供的信息更少的信息,这也是ACPI有用的原因之一:驱动程序考虑到它可能没有关于设备的详细信息,而是使用合理的默认值。如果在驱动程序中做得适当,硬件可以随着时间的推移而改变和改进,而不必改变驱动程序。
时钟就是一个很好的例子。在DT中,需要指定时钟,驱动程序需要将其考虑在内。在ACPI中,假设UEFI将使设备处于合理的默认状态,包括任何时钟设置。如果出于某种原因,驱动程序需要更改时钟值,这可以通过ACPI方法完成;驱动程序需要做的只是调用方法,而不关心方法需要做什么来改变时钟。随着时间的推移,可以通过改变ACPI方法而不是驱动程序来改变硬件。
在DT中,驱动程序设置时钟所需的参数称为“bindings”;在ACPI中,这些参数称为“Device Properties”,并通过_DSD对象提供给驱动程序。
七、Driver Recommendations
为驱动程序添加ACPI支持时,请勿删除任何DT处理。同一设备可用于许多不同的系统。请尝试构造驱动程序,使其成为数据驱动的。也就是说,根据默认值设置一个包含每个设备内部状态的结构。驱动程序 probe 函数必须发现任何其他内容。然后,让驱动程序的其余部分对该结构的内容进行操作。这样做应该允许ACPI和DT功能之间的大部分差异保持在 probe 函数的本地,而不是分散在整个驱动器中。例如:
static int device_probe_dt(struct platform_device *pdev)
{
/* DT specific functionality */
...
}
static int device_probe_acpi(struct platform_device *pdev)
{
/* ACPI specific functionality */
...
}
static int device_probe(struct platform_device *pdev)
{
...
struct device_node node = pdev->dev.of_node;
...
if (node)
ret = device_probe_dt(pdev);
else if (ACPI_HANDLE(&pdev->dev))
ret = device_probe_acpi(pdev);
else
/* other initialization */
...
/* Continue with any generic probe operations */
...
}
一定要在驱动程序中将MODULE_DEVICE_TABLE条目放在一起,以明确从DT和ACPI探测驱动程序的不同名称:
static struct of_device_id virtio_mmio_match[] = {
{ .compatible = "virtio,mmio", },
{ }
};
MODULE_DEVICE_TABLE(of, virtio_mmio_match);
static const struct acpi_device_id virtio_mmio_acpi_match[] = {
{ "LNRO0005", },
{ }
};
MODULE_DEVICE_TABLE(acpi, virtio_mmio_acpi_match);
参考资料
https://static.lwn.net/kerneldoc/arm64/acpi_object_usage.html
https://static.lwn.net/kerneldoc/arm64/arm-acpi.html
https://www.linaro.org/blog/when-will-uefi-and-acpi-be-ready-on-arm/