700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > linux设备模型:固件设备及efi固件(平台)设备节点创建过程分析

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

时间:2020-12-07 23:43:25

相关推荐

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/acpicp 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_initrdcat /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 -ewhile [ -n "$1" ]; docase "$1" in"-f") filename="$2"; shift;;"-g") guid="$2"; shift;;*) name="$1";;esacshiftdoneusage(){echo "Syntax: ${0##*/} -f filename [ -g guid ] name"exit 1}[ -n "$name" -a -f "$filename" ] || usageEFIVARFS="/sys/firmware/efi/efivars"[ -d "$EFIVARFS" ] || exit 2if stat -tf $EFIVARFS | grep -q -v de5e81e4; thenmount -t efivarfs none $EFIVARFSfi# 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 writetmp=$(mktemp)/bin/echo -ne "\007\000\000\000" | cat - $filename > $tmpdd 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/tablemkdir my_ssdtcat ~/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;/** 进入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();}

1.3 efi_enter_virtual_mode

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加载执行模式...}

kexec_enter_virtual_mode

1.4 efisubsys_init

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_SERVICES0x000fplatform_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变量的工具和库(内核部分) efivarif (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;}

2. 源码结构

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,#endifINIT_MM_CONTEXT(init_mm) // 初始化内存上下文};

3. 部分结构定义

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 intruntime_version;/* 运行时服务版本 */unsigned intruntime_supported_mask; /* 支持的运行时掩码 */unsigned longacpi;/* ACPI表(IA64 ext 0.71) */unsigned longacpi20;/* ACPI表格(ACPI 2.0) */unsigned longsmbios;/* SMBIOS表(32位入口点) */unsigned longsmbios3;/* SMBIOS表(64位入口点) */unsigned longesrt;/* ESRT表格 */unsigned longtpm_log;/* TPM2事件日志表 */unsigned longtpm_final_log;/* TPM2最终事件日志表 */unsigned longmokvar_table;/* MOK变量配置表 */unsigned longcoco_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_mapmemmap;unsigned longflags;} efi;

efivars 操作EFI变量的工具和库(内核部分)

struct efivars {struct kset *kset; // kobj容器struct kobject *kobject; // 根对象const struct efivar_operations *ops; // 操作};

4. 扩展函数/变量

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; // 固件厂商IDefi_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位架构,地址不能超出4GBpr_err("EFI data located above 4GB, disabling EFI.\n");return -EINVAL;}return 0;}

early_memremap_ro

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架构类型中,目前支持英特尔或AMDreturn (__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和tableif (!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事件日志配置表关联的内存

tpm2_calc_event_log_size

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;}

tcg_pcr_event2_head

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的私有指针}

efi

efivars

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);elsecontinue;} 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_COREefi_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; // 转换到页IDif (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_native_runtime_setup

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)的cpuinit_new_context(NULL, &efi_mm); // 初始化efi_mm(上下文),用于新的使用return 0;...}

init_mm

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_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_statusacpi_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);

目录预览

<<linux设备模型:sysfs(kobject)解析>>

<<linux设备模型:kset及设备驱动抽象类(class)分析>>

<<linux设备模型:bus概念及pci_bus分析>>

<<linux设备模型:devtmpfs虚拟文件系统分析>>

<<linux设备模型:设备及设备节点创建过程分析>>

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。