linux设备模型:固件设备及efi固件(平台)设备节点创建过程分析

上一篇<<linux设备模型:设备及设备节点创建过程分析>>中分析了设备的初始化及注册过程,包括设备与驱动绑定,设备与电源管理之间的联系、中断域的储存及物理设备之间的关系等等。

本篇分析固件系列(以efi为例),固件可以分为(系统)引导阶段(efi stub启动模式)、固件内存映射(物理地址映射到虚拟地址等等)、固件注册到平台设备、平台设备运行等等。固件设备具有更广泛的意义,当然复杂度也更高一些。

固件系列更偏向于开发板,通常由内核模块(服务)与应用程序完成一些硬件级上电/下电及设备形式控制(如更新固件)等等。参考Documentation中一些文章内容:

   为了支持ACPI开放式硬件配置(例如开发板),我们需要一种方法来增强由固件映像提供的ACPI配置。一个常见的例子是连接开发板上I2C / SPI总线上的传感器。

   尽管这可以通过创建内核平台驱动程序或使用更新的ACPI表重新编译固件映像来实现,但这两种方法都不实际:前者增加了特定于主板的内核代码,而后者需要访问固件工具,而这些工具通常不公开可用。

   因为ACPI在AML代码中支持外部引用,所以增强固件ACPI配置的一种更实用的方法是动态加载用户定义的SSDT表,其中包含特定于单板的信息。然后,生成的AML代码可以由内核使用其中一种方法加载在下面。

从initrd加载ACPI ssdt

   此选项允许从 initrd 加载用户定义的 SSDT,当系统不支持 EFI 或没有足够的 EFI 存储时,此选项非常有用。

   它的工作方式与基于 initrd 的 ACPI 表覆盖/升级类似:SSDT AML 代码必须放置在“内核/固件/ACPI”路径下的第一个未压缩的 initrd 中。可以使用多个文件,这将转换为加载多个表。仅允许 SSDT 和 OEM 表。

   下面是一个示例:

# Add the raw ACPI tables to an uncompressed cpio archive.
# They must be put into a /kernel/firmware/acpi directory inside the
# cpio archive.
# The uncompressed cpio archive must be the first.
# Other, typically compressed cpio archives, must be
# concatenated on top of the uncompressed one.
mkdir -p kernel/firmware/acpi
cp ssdt.aml kernel/firmware/acpi

# Create the uncompressed cpio archive and concatenate the original initrd
# on top:
find kernel | cpio -H newc --create > /boot/instrumented_initrd
cat /boot/initrd >>/boot/instrumented_initrd

从EFI变量加载ACPI ssdt

   当平台上支持 EFI 时,这是首选方法,因为它允许以独立于操作系统的持久方式存储用户定义的 SSDT。此外,还正在努力实现对加载用户定义的 SSDT 的 EFI 支持,使用此方法将使在 EFI 加载机制到来时更容易转换为 EFI 加载机制。要启用它,CONFIG_EFI_CUSTOM_SSDT_OVERLAYS shoyld 被选为 y。

   为了从 EFI 变量加载 SSDT,可以使用内核命令行参数(名称限制为 16 个字符)。选项的参数是要使用的变量名称。如果有多个变量具有相同的名称,但具有不同的供应商 GUID,则将加载所有这些变量。“efivar_ssdt=…”

   为了将 AML 代码存储在 EFI 变量中,可以使用 efivarfs 文件系统。默认情况下,它在所有最近的发行版中启用并挂载在 /sys/firmware/efi/efivars 中。

   在 /sys/firmware/efi/efivars 中创建新文件将自动创建一个新的 EFI 变量。更新 /sys/firmware/efi/efivars 中的文件将更新 EFI 变量。请注意,文件名需要特殊格式化为“Name-GUID”,文件中的前 4 个字节(小端格式)表示 EFI 变量的属性(请参阅 include/linux/efi.h 中的EFI_VARIABLE_MASK)。写入文件也必须通过一个写入操作完成。

   例如,您可以使用以下 bash 脚本使用给定文件中的内容创建/更新 EFI 变量:

#!/bin/sh -e

while [ -n "$1" ]; do
        case "$1" in
        "-f") filename="$2"; shift;;
        "-g") guid="$2"; shift;;
        *) name="$1";;
        esac
        shift
done

usage()
{
        echo "Syntax: ${0##*/} -f filename [ -g guid ] name"
        exit 1
}

[ -n "$name" -a -f "$filename" ] || usage

EFIVARFS="/sys/firmware/efi/efivars"

[ -d "$EFIVARFS" ] || exit 2

if stat -tf $EFIVARFS | grep -q -v de5e81e4; then
        mount -t efivarfs none $EFIVARFS
fi

# try to pick up an existing GUID
[ -n "$guid" ] || guid=$(find "$EFIVARFS" -name "$name-*" | head -n1 | cut -f2- -d-)

# use a randomly generated GUID
[ -n "$guid" ] || guid="$(cat /proc/sys/kernel/random/uuid)"

# efivarfs expects all of the data in one write
tmp=$(mktemp)
/bin/echo -ne "\007\000\000\000" | cat - $filename > $tmp
dd if=$tmp of="$EFIVARFS/$name-$guid" bs=$(stat -c %s $tmp)
rm $tmp

从配置加载ACPI SSDTs

   此选项允许通过 configfs 接口从用户空间加载用户定义的 SSDT。必须选择CONFIG_ACPI_CONFIGFS选项,并且必须挂载配置。在下面的例子中,我们假设configfs已经挂载在/sys/kernel/config中。

   可以通过在 /sys/kernel/config/acpi/table 中创建新目录并在 aml 属性中写入 SSDT AML 代码来加载新表:

cd /sys/kernel/config/acpi/table
mkdir my_ssdt
cat ~/ssdt.aml > my_ssdt/aml

本篇文章主要分析从固件设备创建到(efi)平台设备的运行过程等等,(系统)引导阶段之前的文章有过记录,这里不再分析。

固件设备与块设备、字符设备类似,首先创建根kobj(firmware_kobj),各类固件设备将以它为基础。
efi平台设备运行过程如下:

   efi系统初始化
      映射efi系统表物理地址,地址有效的情况下输出EFI版本等信息,进入EFI虚拟模式后,许多配置表条目被重新映射到虚拟地址,并将这些条目更正为它们各自的物理地址,目前只处理一些固件实现所必需的smbios,检查配置表条目是否有效,解析/赋值固件表信息,分析EFI属性表,设置内存属性特征(运行时数据区域映射为不可执行),可以使用运行时服务,在运行时打印额外的调试信息等等。

   efi加载执行模式
      kexec_enter_virtual_mode

   efi子系统初始化
      分配efi订单(order)工作队列 efi_rts_wq,添加平台级设备(“rtc-efi”)及其资源,分配efi kobj,指定固件kobj为父级,注册操作EFI变量的工具和库(内核部分) efivar,执行efivar内核(服务),用于动态加载用户定义的ssdt表,添加平台级设备efivars,efi_kobj创建属性组,efi运行时映射初始化,并创建挂载点等等。


目录


1. 函数分析

1.1 firmware_init

1.2 efi_init

1.3 efi_enter_virtual_mode

1.4 efisubsys_init

2. 源码结构

3. 部分结构定义

4. 扩展函数/变量

目录预览


1. 函数分析

1.1 firmware_init

  分配固件根对象firmware_kobj,各类固件设备将以它为基础

int __init firmware_init(void)
{
        firmware_kobj = kobject_create_and_add("firmware", NULL); // // 分配并初始化kobj对象firmware_kobj
        // 固件根对象
        
        if (!firmware_kobj)
                return -ENOMEM;
        return 0;
} 

1.2 efi_init

  efi系统初始化

  映射efi系统表物理地址,地址有效的情况下输出EFI版本等信息

  进入EFI虚拟模式后,许多配置表条目被重新映射到虚拟地址
  然而,kexec内核需要它们的物理地址,因此我们通过setup_data传递它们,
  并将这些条目更正为它们各自的物理地址,目前只处理一些固件实现所必需的smbios

  检查配置表条目是否有效,解析/赋值固件表信息

  分析EFI属性表,设置内存属性特征(运行时数据区域映射为不可执行)
  可以使用运行时服务,在运行时打印额外的调试信息

void __init efi_init(void)
{
	...
	efi_systab_phys = boot_params.efi_info.efi_systab |
			  ((__u64)boot_params.efi_info.efi_systab_hi << 32); // efi系统表物理地址

	if (efi_systab_init(efi_systab_phys)) // 映射efi系统表物理地址,地址有效的情况下输出EFI版本等信息
		return;

	/*
	 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坤昱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值