上一篇<
> 中分析了设备的初始化及注册过程,包括设备与驱动绑定,设备与电源管理之间的联系、中断域的储存及物理设备之间的关系等等。
本篇分析固件系列(以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运行时映射初始化,并创建挂载点等等。
分配固件根对象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;
}
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;
/*
* 进入EFI虚拟模式后,许多配置表条目被重新映射到虚拟地址
* 然而,kexec内核需要它们的物理地址,因此我们通过setup_data传递它们,
* 并将这些条目更正为它们各自的物理地址
*
* 目前只处理一些固件实现所必需的smbios
* /
if (efi_reuse_config(efi_config_table, efi_nr_tables)) // 检查配置表条目是否有效
return;
if (efi_config_init(arch_tables)) // efi_config_parse_tables efi解析/赋值固件表信息
return;
efi_systab_init
arch_tables
efi_config_parse_tables
/* 分析EFI属性表 */
if (prop_phys != EFI_INVALID_TABLE_ADDR) {
...
if (tbl->memory_protection_attribute &
EFI_PROPERTIES_RUNTIME_MEMORY_PROTECTION_NON_EXECUTABLE_PE_DATA)
set_bit(EFI_NX_PE_DATA, &efi.flags); // 内存属性特征
// 运行时数据区域是否可以映射为不可执行?
...
}
set_bit(EFI_RUNTIME_SERVICES, &efi.flags); // 可以使用运行时服务
efi_clean_memmap();
if (efi_enabled(EFI_DBG)) // 在运行时打印额外的调试信息
efi_print_memmap();
}
efi加载执行模式
void __init efi_enter_virtual_mode(void)
{
efi.runtime = (efi_runtime_services_t *)efi_runtime; // efi解析/赋值固件表信息中已经解析
// efi_tables[]
if (efi_setup)
kexec_enter_virtual_mode(); // efi加载执行模式
...
}
efi子系统初始化
分配efi订单(order)工作队列 efi_rts_wq
添加平台级设备(“rtc-efi”)及其资源
分配efi kobj,指定固件kobj为父级
注册操作EFI变量的工具和库(内核部分) efivar
执行efivar内核(服务),用于动态加载用户定义的ssdt表
添加平台级设备efivars
efi_kobj创建属性组
efi运行时映射初始化,并创建挂载点
static int __init efisubsys_init(void)
{
int error;
if (efi.runtime_supported_mask) { // 支持运行时服务
/*
* 因为我们一次只处理一个efi_runtime_service(),
* 所以一个有序的工作队列(只创建一个执行上下文)应该足以满足我们的所有需求
*/
efi_rts_wq = alloc_ordered_workqueue("efi_rts_wq", 0);
// struct workqueue_struct *efi_rts_wq; efi运行时服务的有序工作队列
// 有序工作队列在排队顺序中的任何给定时间最多执行一个工作项
}
if (efi_rt_services_supported(EFI_RT_SUPPORTED_TIME_SERVICES))
// #define EFI_RT_SUPPORTED_TIME_SERVICES 0x000f
platform_device_register_simple("rtc-efi", 0, NULL, 0); // 添加平台级设备及其资源
// 设备名称"rtc-efi"
/* 我们在/sys/firmware/efi 注册efi目录 */
efi_kobj = kobject_create_and_add("efi", firmware_kobj); // 分配efi kobj,指定固件kobj为父级
if (efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE |
EFI_RT_SUPPORTED_GET_NEXT_VARIABLE_NAME)) {
error = generic_ops_register(); // 注册操作EFI变量的工具和库(内核部分) efivar
if (error)
goto err_put;
efivar_ssdt_load(); // 执行efivar内核(服务),用于动态加载用户定义的ssdt表
platform_device_register_simple("efivars", 0, NULL, 0); // 添加平台级设备efivars
// 创建平台设备,提交设备节点添加请求
}
generic_ops_register
efivar_ssdt_load
platform_device_register_full
error = sysfs_create_group(efi_kobj, &efi_subsys_attr_group); // efi_kobj创建属性组
// efi_subsys_attr_group efi子系统属性组
// 获取kobj对象的sysfs所有权数据,创建kernfs_node节点及命名空间 (父级kn,如目录)
// 然后为属性组->属性列表中的属性分配kernfs_node节点及初始化(子级kn,如目录/文件)
// 包括关联kernfs_ops对象,将kernfs_node链接到同级rbtree,更新哈希值及时间戳
// 并激活这个节点(属性列表中的节点)
error = efi_runtime_map_init(efi_kobj); // efi运行时映射初始化
// 创建kset容器map_kset,容器名称"runtime-map"
// 关联kobj_type结构对象map_ktype
/* efivarfs的标准安装点 */
error = sysfs_create_mount_point(efi_kobj, "efivars"); // 创建挂载点
...
return 0;
}
arch_tables efi配置表类型
guid 全局唯一标识符
ptr 表地址
name[16] 表名称
static const efi_config_table_type_t arch_tables[] __initconst = {
{EFI_PROPERTIES_TABLE_GUID, &prop_phys, "PROP" },
{UGA_IO_PROTOCOL_GUID, &uga_phys, "UGA" },
#ifdef CONFIG_X86_UV
{UV_SYSTEM_TABLE_GUID, &uv_systab_phys, "UVsystab" },
#endif
{},
};
init_mm 初始内存结构对象
/*
* 对于动态分配的mm_structs,在结构的末尾有一个动态大小的cpumask,
* 其大小取决于系统可以看到的最大CPU数
* 这样,我们只为mm_cpumask()分配系统通常运行的数百或数千个进程所需的内存
*
* 由于整个系统中只有一个init_mm,请保持简单,并将cpu_bitmask设置为NR_CPUS
* /
struct mm_struct init_mm = {
.mm_mt = MTREE_INIT_EXT(mm_mt, MM_MT_FLAGS, init_mm.mmap_lock), // 使用外部锁初始化标志树
// 这些标志既用于存储关于该树的一些不可变信息(在树创建时设置),
// 也用于存储自旋锁下设置的动态信息
.pgd = swapper_pg_dir, // 顶部页全局表
// #define swapper_pg_dir init_top_pgt
.mm_users = ATOMIC_INIT(2), // 包括用户空间在内的用户数
.mm_count = ATOMIC_INIT(1), // 对init_mm(mm_struct 对象)的引用数
.write_protect_seq = SEQCNT_ZERO(init_mm.write_protect_seq), // 序列计数器
// 当任何线程正在对此mm映射的页面进行写保护时
// 例如在fork()的页面表复制过程中,会被锁定,以强制执行稍后的COW
// 写端关键部分必须序列化且不可抢占
// 如果可以从hardirq或softirq上下文调用读取器,
// 则在进入写入部分之前,也必须分别禁用中断或下半部分
MMAP_LOCK_INITIALIZER(init_mm) // 初始化读写信号量(init_mm.mmap_lock)
.page_table_lock = __SPIN_LOCK_UNLOCKED(init_mm.page_table_lock), // 初始化页表锁(spinlock_t)
.arg_lock = __SPIN_LOCK_UNLOCKED(init_mm.arg_lock), // 初始化参数锁
.mmlist = LIST_HEAD_INIT(init_mm.mmlist), // 初始化内存链表
.user_ns = &init_user_ns, // 用户命名空间
.cpu_bitmap = CPU_BITS_NONE,
#ifdef CONFIG_IOMMU_SVA
.pasid = INVALID_IOASID,
#endif
INIT_MM_CONTEXT(init_mm) // 初始化内存上下文
};
tcg_pcr_event2_head pcr区域事件头
tpm 可信平台模块(Trusted Platform Module)
struct tcg_pcr_event2_head {
u32 pcr_idx; // pcr区域索引
u32 event_type; // 事件类型
u32 count; // 计数
struct tpm_digest digests[]; // 记录列表
} __packed;
efi 对EFI的所有运行时访问都通过此结构
SMBIOS (System Management BIOS)
extern struct efi {
const efi_runtime_services_t *runtime; /* EFI运行时服务表 */
unsigned int runtime_version; /* 运行时服务版本 */
unsigned int runtime_supported_mask; /* 支持的运行时掩码 */
unsigned long acpi; /* ACPI表(IA64 ext 0.71) */
unsigned long acpi20; /* ACPI表格(ACPI 2.0) */
unsigned long smbios; /* SMBIOS表(32位入口点) */
unsigned long smbios3; /* SMBIOS表(64位入口点) */
unsigned long esrt; /* ESRT表格 */
unsigned long tpm_log; /* TPM2事件日志表 */
unsigned long tpm_final_log; /* TPM2最终事件日志表 */
unsigned long mokvar_table; /* MOK变量配置表 */
unsigned long coco_secret; /* 机密计算秘密表 */
efi_get_time_t *get_time; /* 获取运行时间 */
efi_set_time_t *set_time; /* 设置运行时间 */
efi_get_wakeup_time_t *get_wakeup_time; /* 获取唤醒时间 */
efi_set_wakeup_time_t *set_wakeup_time; /* 设置唤醒时间 */
efi_get_variable_t *get_variable; /* 获取运行时变量 */
efi_get_next_variable_t *get_next_variable; /* 获取运行时下一个变量 */
efi_set_variable_t *set_variable; /* 设置运行时变量 */
efi_set_variable_t *set_variable_nonblocking; /* 设置运行时非阻塞性变量 */
efi_query_variable_info_t *query_variable_info; /* 查询运行时变量信息 */
efi_query_variable_info_t *query_variable_info_nonblocking; /* 查询运行时非阻塞性变量信息 */
efi_update_capsule_t *update_capsule;
efi_query_capsule_caps_t *query_capsule_caps;
efi_get_next_high_mono_count_t *get_next_high_mono_count;
efi_reset_system_t *reset_system;
struct efi_memory_map memmap;
unsigned long flags;
} efi;
efivars 操作EFI变量的工具和库(内核部分)
struct efivars {
struct kset *kset; // kobj容器
struct kobject *kobject; // 根对象
const struct efivar_operations *ops; // 操作
};
efi_systab_init 映射efi系统表物理地址,地址有效的情况下输出EFI版本等信息
static int __init efi_systab_init(unsigned long phys)
{
int size = efi_enabled(EFI_64BIT) ? sizeof(efi_system_table_64_t)
: sizeof(efi_system_table_32_t); // 64位
const efi_table_hdr_t *hdr;
bool over4g = false;
void *p;
int ret;
hdr = p = early_memremap_ro(phys, size); // 物理地址映射到虚拟地址
ret = efi_systab_check_header(hdr, 1); // 检查系统表签名
// #define EFI_SYSTEM_TABLE_SIGNATURE ((u64)0x5453595320494249ULL)
if (efi_enabled(EFI_64BIT)) {
const efi_system_table_64_t *systab64 = p;
efi_runtime = systab64->runtime;
over4g = systab64->runtime > U32_MAX;
if (efi_setup) { // parse_setup_data函数中解析得到
struct efi_setup_data *data;
data = early_memremap_ro(efi_setup, sizeof(*data)); // 物理地址映射到虚拟地址
efi_fw_vendor = (unsigned long)data->fw_vendor; // 固件厂商ID
efi_config_table = (unsigned long)data->tables; // 配置表
over4g |= data->fw_vendor > U32_MAX ||
data->tables > U32_MAX;
early_memunmap(data, sizeof(*data)); // 清空映射的地址区域及标志等
}
efi_nr_tables = systab64->nr_tables;
}
efi.runtime_version = hdr->revision; // 通用EFI表版本号
efi_systab_report_header(hdr, efi_fw_vendor); // 输出EFI版本信息
early_memunmap(p, size); // 清空映射的地址区域及标志等
if (IS_ENABLED(CONFIG_X86_32) && over4g) { // 32位架构,地址不能超出4GB
pr_err("EFI data located above 4GB, disabling EFI.\n");
return -EINVAL;
}
return 0;
}
early_memremap_ro 物理地址映射到虚拟地址
void __init *
early_memremap_ro(resource_size_t phys_addr, unsigned long size)
{
/*
* __weak函数的体系结构重写,以调整重新映射内存时使用的保护属性
* 默认情况下,early_memremap()将数据映射为加密数据
* 确定是否不应进行加密映射,并设置适当的保护属性
* /
pgprot_t prot = early_memremap_pgprot_adjust(phys_addr, size,
FIXMAP_PAGE_RO); // 加密或解密FIXMAP_PAGE_RO,如820、efi等已知固定地址加密,其它类型解密
// 加密或解密在x86架构类型中,目前支持英特尔或AMD
return (__force void *)__early_ioremap(phys_addr, size, prot); // 映射后的地址记录到prev_map[slot]
}
efi_config_parse_tables efi解析/赋值固件表信息
获取efi配置表类型中,所有表的guid和table
从efi配置表类型中找到相同的guid,然后向对应配置表中传入固件表地址
处理引导加载程序传递的随机种子
保留与内存属性配置表关联的内存
保留与TPM事件日志配置表关联的内存
保留内存区域,检查支持的运行时服务
记录initrd设备路径(起始地址和尾部地址)
int __init efi_config_parse_tables(const efi_config_table_t *config_tables,
int count,
const efi_config_table_type_t *arch_tables)
{
const efi_config_table_64_t *tbl64 = (void *)config_tables;
const efi_config_table_32_t *tbl32 = (void *)config_tables;
const efi_guid_t *guid;
unsigned long table;
int i;
for (i = 0; i < count; i++) {
...
// 获取efi配置表类型中,所有表的guid和table
if (!match_config_table(guid, table, common_tables) && arch_tables) // 如果没有找到对应的guid
// match_config_table函数找从efi配置表类型中找到相同的guid,然后向对应配置表中传入固件表地址
match_config_table(guid, table, arch_tables);
}
if (efi_rng_seed != EFI_INVALID_TABLE_ADDR) { // 随机数种子
// setup_boot_parameters -> setup_rng_seed
// efi_tables数组中包含了较多重要性质变量
...
add_bootloader_randomness(seed->bits, size); // 处理引导加载程序传递的随机种子
}
if (!IS_ENABLED(CONFIG_X86_32) && efi_enabled(EFI_MEMMAP))
efi_memattr_init(); // 保留与内存属性配置表关联的内存
efi_tpm_eventlog_init(); // 保留与TPM事件日志配置表关联的内存
if (mem_reserve != EFI_INVALID_TABLE_ADDR) { // 保留内存区域
...
/* 保留条目本身 */
memblock_reserve(prsv,
struct_size(rsv, entry, rsv->size));
for (i = 0; i < atomic_read(&rsv->count); i++) {
memblock_reserve(rsv->entry[i].base,
rsv->entry[i].size);
}
...
}
if (rt_prop != EFI_INVALID_TABLE_ADDR) { // 支持运行时服务
...
efi.runtime_supported_mask &= tbl->runtime_services_supported;
...
}
if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) &&
initrd != EFI_INVALID_TABLE_ADDR && phys_initrd_size == 0) { // initrd设备路径
struct linux_efi_initrd *tbl;
tbl = early_memremap(initrd, sizeof(*tbl));
if (tbl) {
phys_initrd_start = tbl->base;
phys_initrd_size = tbl->size;
early_memunmap(tbl, sizeof(*tbl));
}
}
return 0;
}
tpm2_calc_event_log_size 计算TPM2事件日志条目的大小
static int __init tpm2_calc_event_log_size(void *data, int count, void *size_info)
{
struct tcg_pcr_event2_head *header;
int event_size, size = 0;
while (count > 0) {
header = data + size;
event_size = __calc_tpm2_event_size(header, size_info, true); // 计算TPM2事件日志条目的大小
if (event_size == 0)
return -1;
size += event_size;
count--;
}
return size;
}
generic_ops_register 注册操作EFI变量的工具和库(内核部分) efivar
static int generic_ops_register(void)
{
generic_ops.get_variable = efi.get_variable; // 获取运行时变量
generic_ops.get_next_variable = efi.get_next_variable; // 获取运行时下一个变量
generic_ops.query_variable_store = efi_query_variable_store; // 查询运行时变量存储空间
// 如果变量存储空间不足,某些固件实现将拒绝启动。确保我们的使用量永远不会超过安全限制
if (efi_rt_services_supported(EFI_RT_SUPPORTED_SET_VARIABLE)) {
generic_ops.set_variable = efi.set_variable; // 设置运行时变量
generic_ops.set_variable_nonblocking = efi.set_variable_nonblocking; // 设置运行时非堵塞性变量
}
return efivars_register(&generic_efivars, &generic_ops, efi_kobj); // 注册efivar
// efi_kobj 根kobj
// generic_ops 操作结构变量 不同的固件驱动程序可以使用这个结构对象公开其EFI类变量
// 操作EFI变量的工具和库(内核部分)
// static struct efivars *__efivars; 指向已注册efvar的私有指针
}
efivar_ssdt_load efivar内核(服务),用于动态加载用户定义的ssdt表
所有的ssdt表加载完成后,退出循环
static __init int efivar_ssdt_load(void)
{
unsigned long name_size = 256;
efi_char16_t *name = NULL;
efi_status_t status;
efi_guid_t guid;
if (!efivar_ssdt[0])
return 0;
name = kzalloc(name_size, GFP_KERNEL); // 分配256字节的内存,存储名称
for (;;) { // 此时,efivar内核模块已经加载完成,等待/处理与用户空间的通信内容
char utf8_name[EFIVAR_SSDT_NAME_MAX];
unsigned long data_size = 0;
void *data;
int limit;
status = efi.get_next_variable(&name_size, name, &guid); // efi获取下一个运行时变量
if (status == EFI_NOT_FOUND) { // 所有的ssdt表加载完成后,退出循环
break;
} else if (status == EFI_BUFFER_TOO_SMALL) {
name = krealloc(name, name_size, GFP_KERNEL);
if (!name)
return -ENOMEM;
continue;
}
limit = min(EFIVAR_SSDT_NAME_MAX, name_size);
ucs2_as_utf8(utf8_name, name, limit - 1);
if (strncmp(utf8_name, efivar_ssdt, limit) != 0) // utf8格式名称与传入efivar_ssdt_setup函数传入的名称相同
continue;
pr_info("loading SSDT from variable %s-%pUl\n", efivar_ssdt, &guid);
status = efi.get_variable(name, &guid, NULL, &data_size, NULL); // 获取数据长度
data = kmalloc(data_size, GFP_KERNEL); // 分配数据内存
if (!data)
return -ENOMEM;
status = efi.get_variable(name, &guid, NULL, &data_size, data); // 获取数据
if (status == EFI_SUCCESS) {
acpi_status ret = acpi_load_table(data, NULL); // 加载并安装电源管理/配置表
if (ret)
pr_err("failed to load table: %u\n", ret);
else
continue;
} else {
pr_err("failed to get var data: 0x%lx\n", status);
}
kfree(data);
}
return 0;
}
efi.get_next_variable
acpi_load_table
kexec_enter_virtual_mode efi加载执行模式
为efi分配新的内存结构使用,内存使用前,重新映射物理地址到虚拟地址
efi设置页表,为向EFI函数传递参数添加低内核映射
efi活跃的运行时设置(关联相关函数)
static void __init kexec_enter_virtual_mode(void)
{
#ifdef CONFIG_KEXEC_CORE
efi_memory_desc_t *md;
unsigned int num_pages;
/*
* 我们不做虚拟模式,因为我们不在非本地EFI上做运行时服务
*/
if (efi_is_mixed()) { // 64位不执行这里
efi_memmap_unmap();
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
if (efi_alloc_page_tables()) { // 为efi分配新的内存结构使用
pr_err("Failed to allocate EFI page tables\n");
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
/*
* 映射通过setup_data传递的efi区域
* virt_addr是在kexec引导的第一个内核中使用的固定地址
* /
for_each_efi_memory_desc(md)
efi_map_region_fixed(md); /* FIXME:添加错误处理 */
/*
* 从EFI_init()中注销早期EFI内存映射
* 并安装新的EFI内存映像
* /
efi_memmap_unmap();
if (efi_memmap_init_late(efi.memmap.phys_map,
efi.memmap.desc_size * efi.memmap.nr_map)) { // 内存使用前,重新映射物理地址到虚拟地址
pr_err("Failed to remap late EFI memory map\n");
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
num_pages = ALIGN(efi.memmap.nr_map * efi.memmap.desc_size, PAGE_SIZE); // 按页对齐
num_pages >>= PAGE_SHIFT; // 转换到页ID
if (efi_setup_page_tables(efi.memmap.phys_map, num_pages)) { // efi设置页表
clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);
return;
}
efi_sync_low_kernel_mappings(); // 为向EFI函数传递参数添加低内核映射
efi_native_runtime_setup(); // efi活跃的运行时设置(关联相关函数)
#endif
}
efi_alloc_page_tables 为efi分配新的内存结构使用
分配页(在相应的页目录可以找到(或符合)的情况下,可以使用),返回映射的虚拟地址
清除efi_mm结构关联的cpumask中所有(< nr_cpu_ids)的cpu
初始化efi_mm(上下文),用于新的使用
/*
* 我们需要更高级别的页表的副本,
* 因为我们希望避免将EFI区域映射 (EFI_VA_END 到 EFI_WA_START) 插入到标准内核页表中
* 其他一切都可以共享,请参见efi_sync_low_kernel_mapping()
*
* 我们不需要pgd_list上的pgd,也不能使用pgd_alloc()进行分配
* /
int __init efi_alloc_page_tables(void)
{
pgd_t *pgd, *efi_pgd;
p4d_t *p4d;
pud_t *pud;
gfp_t gfp_mask;
gfp_mask = GFP_KERNEL | __GFP_ZERO;
// GFP_KERNEL通常用于内核内部分配
// 调用方需要 %ZONE_NORMAL或更低的区域进行直接访问,但可以直接回收
// __GFP_ZERO成功时返回零页面
efi_pgd = (pgd_t *)__get_free_pages(gfp_mask, PGD_ALLOCATION_ORDER); // 分配页,返回映射的虚拟地址
// PGD_ALLOCATION_ORDER 我们获得了两个PGD,而不是一个PGD
// 作为1号订单(order),它的大小为8k,排列为8k
// 这让我们只需翻转指针中的第12位,即可在两个4k半部之间进行交换
pgd = efi_pgd + pgd_index(EFI_VA_END); // efi_pgd + 511
// #define EFI_VA_START ( -4 * (_AC(1, UL) << 30))
// #define EFI_VA_END (-68 * (_AC(1, UL) << 30))
p4d = p4d_alloc(&init_mm, pgd, EFI_VA_END); // 4级页目录
pud = pud_alloc(&init_mm, p4d, EFI_VA_END); // pud页目录(page upper directory)
efi_mm.pgd = efi_pgd; // 分配的页在相应的页目录可以找到的情况下,使用
// efi_mm efi内存结构对象
mm_init_cpumask(&efi_mm); // 清除cpumask中所有(< nr_cpu_ids)的cpu
init_new_context(NULL, &efi_mm); // 初始化efi_mm(上下文),用于新的使用
return 0;
...
}
efi_native_runtime_setup efi活跃的运行时设置(关联相关函数)
void efi_native_runtime_setup(void)
{
efi.get_time = virt_efi_get_time; // 关联函数
efi.set_time = virt_efi_set_time;
efi.get_wakeup_time = virt_efi_get_wakeup_time;
efi.set_wakeup_time = virt_efi_set_wakeup_time;
efi.get_variable = virt_efi_get_variable;
efi.get_next_variable = virt_efi_get_next_variable;
efi.set_variable = virt_efi_set_variable;
efi.set_variable_nonblocking = virt_efi_set_variable_nonblocking;
efi.get_next_high_mono_count = virt_efi_get_next_high_mono_count;
efi.reset_system = virt_efi_reset_system;
efi.query_variable_info = virt_efi_query_variable_info;
efi.query_variable_info_nonblocking = virt_efi_query_variable_info_nonblocking;
efi.update_capsule = virt_efi_update_capsule;
efi.query_capsule_caps = virt_efi_query_capsule_caps;
}
efi.get_next_variable efi获取下一个运行时变量
等待用户空间API传入相关变量
static efi_status_t virt_efi_get_next_variable(unsigned long *name_size,
efi_char16_t *name,
efi_guid_t *vendor)
{
efi_status_t status;
if (down_interruptible(&efi_runtime_lock))
return EFI_ABORTED;
status = efi_queue_work(EFI_GET_NEXT_VARIABLE, name_size, name, vendor,
NULL, NULL); // efi提交任务到工作队列,等待任务执行完成
up(&efi_runtime_lock);
return status;
}
efi_queue_work efi提交任务到工作队列,等待任务执行完成
/*
* 对efi_runtime_services()的访问由二进制信号量(efi_untime_lock)序列化
* 调用方等待直到工作完成,因此一次只排队一个工作,调用方线程等待完成
* /
#define efi_queue_work(_rts, _arg1, _arg2, _arg3, _arg4, _arg5) \
({ \
efi_rts_work.status = EFI_ABORTED; \
\
if (!efi_enabled(EFI_RUNTIME_SERVICES)) { \
pr_warn_once("EFI Runtime Services are disabled!\n"); \
goto exit; \
} \
\
init_completion(&efi_rts_work.efi_rts_comp); \
INIT_WORK(&efi_rts_work.work, efi_call_rts); // 初始化任务
// efi_call_rts 任务执行函数
efi_rts_work.arg1 = _arg1; \
efi_rts_work.arg2 = _arg2; \
efi_rts_work.arg3 = _arg3; \
efi_rts_work.arg4 = _arg4; \
efi_rts_work.arg5 = _arg5; \
efi_rts_work.efi_rts_id = _rts; \
\
/* \
* 如果工作已经在队列中,queue_work()返回0,
* 通常不会发生这种情况
*/ \
if (queue_work(efi_rts_wq, &efi_rts_work.work)) // 任务提交到工作队列
wait_for_completion(&efi_rts_work.efi_rts_comp); // 等待任务完成
else \
pr_err("Failed to queue work to efi_rts_wq.\n"); \
\
exit: \
efi_rts_work.efi_rts_id = EFI_NONE; \
efi_rts_work.status; \
})
acpi_load_table 加载并安装电源管理/配置表
安装电源管理/配置表并将其加载到命名空间中,之后加载电源管理/配置表
并完成新对象的初始化/解析
acpi_status acpi_load_table(struct acpi_table_header *table, u32 *table_idx)
{
acpi_status status;
u32 table_index;
ACPI_FUNCTION_TRACE(acpi_load_table); // 函数条目跟踪
// acpi_ut_trace 仅当TRACE_FUNCTIONS位设置为debug_level时打印
status = acpi_tb_install_and_load_table(ACPI_PTR_TO_PHYSADDR(table),
ACPI_TABLE_ORIGIN_EXTERNAL_VIRTUAL,
table, FALSE, &table_index); // 安装电源管理/配置表并将其加载到命名空间中,之后加载电源管理/配置表
if (table_idx) {
*table_idx = table_index;
}
acpi_tb_install_and_load_table
if (ACPI_SUCCESS(status)) {
/* 完成新对象的初始化/解析 */
acpi_ns_initialize_objects();
}
return_ACPI_STATUS(status);
}
ACPI_EXPORT_SYMBOL(acpi_load_table)
acpi_tb_install_and_load_table 安装电源管理/配置表并将其加载到命名空间中,之后加载电源管理/配置表
acpi_status
acpi_tb_install_and_load_table(acpi_physical_address address,
u8 flags,
struct acpi_table_header *table,
u8 override, u32 *table_index)
{
acpi_status status;
u32 i;
ACPI_FUNCTION_TRACE(tb_install_and_load_table);
/* 安装表并将其加载到命名空间中 */
status = acpi_tb_install_standard_table(address, flags, table, TRUE,
override, &i);
// ACPI_GLOBAL(struct acpi_table_list, acpi_gbl_root_table_list);
// RSDT/XSDT中找到的所有ACPI表的主列表
status = acpi_tb_load_table(i, acpi_gbl_root_node); // 加载电源配置表
// 为任何新的_Lxx/Exx方法更新GPE
// 忽略错误。主机负责通过运行此表可能已加载的_PRW方法来发现任何新的唤醒GPE
// ACPI_GLOBAL(struct acpi_namespace_node *, acpi_gbl_root_node);
exit:
*table_index = i;
return_ACPI_STATUS(status);
}
ACPI_EXPORT_SYMBOL(acpi_tb_install_and_load_table)
platform_device_register_full 创建平台设备,提交设备节点添加请求
struct platform_device *platform_device_register_full(
const struct platform_device_info *pdevinfo)
{
int ret;
struct platform_device *pdev;
pdev = platform_device_alloc(pdevinfo->name, pdevinfo->id); // 创建平台设备
pdev->dev.parent = pdevinfo->parent; // 设备父级
pdev->dev.fwnode = pdevinfo->fwnode; // 设备固件节点
pdev->dev.of_node = of_node_get(to_of_node(pdev->dev.fwnode));
pdev->dev.of_node_reused = pdevinfo->of_node_reused;
ret = platform_device_add_resources(pdev,
pdevinfo->res, pdevinfo->num_res); // 向平台设备添加资源
ret = platform_device_add_data(pdev,
pdevinfo->data, pdevinfo->size_data); // 向平台设备添加特定于平台的数据
if (pdevinfo->properties) { // 设备属性
ret = device_create_managed_software_node(&pdev->dev,
pdevinfo->properties, NULL); // 为设备创建软件节点
// struct swnode *swnode;
// dev->kobj 链接到 swnode->kobj 链接名称("software_node")
// swnode->kobj 链接到 dev->kobj 链接名称(设备名称)
}
ret = platform_device_add(pdev); // 提交设备节点添加请求
...
return pdev;
}
EXPORT_SYMBOL_GPL(platform_device_register_full);