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


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

    本篇分析固件系列(以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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    从配置加载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
    
    • 1
    • 2
    • 3

    本篇文章主要分析从固件设备创建到(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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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加载执行模式
    
    		...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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_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
    		// 创建平台设备,提交设备节点添加请求
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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
    	{},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

      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) // 初始化内存上下文
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

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

    struct efivars {
    	struct kset *kset; // kobj容器
    	struct kobject *kobject; // 根对象
    	const struct efivar_operations *ops; // 操作
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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; // 固件厂商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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    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架构类型中,目前支持英特尔或AMD
    
    		return (__force void *)__early_ioremap(phys_addr, size, prot); // 映射后的地址记录到prev_map[slot]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

      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事件日志配置表关联的内存
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

      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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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的私有指针
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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);
    			else
    				continue;
    		} else {
    			pr_err("failed to get var data: 0x%lx\n", status);
    		}
    		kfree(data);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    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)的cpu
    	init_new_context(NULL, &efi_mm); // 初始化efi_mm(上下文),用于新的使用
    
    	return 0;
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

      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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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;						\
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

      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;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    acpi_tb_install_and_load_table

    if (ACPI_SUCCESS(status)) {
    
    		/* 完成新对象的初始化/解析 */
    
    		acpi_ns_initialize_objects();
    	}
    
    	return_ACPI_STATUS(status);
    }
    
    ACPI_EXPORT_SYMBOL(acpi_load_table)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

      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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

      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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    目录预览


    <>
    <>
    <>
    <>
    <>

  • 相关阅读:
    Windows Server 2016安装SQLServer2008R2
    数据结构P46(2-1~2-4)
    python中的变量和数组的赋值和地址的关系
    linux 安装部署nginx
    Oracle 账户被锁:the account is locked 解决方法
    Redis与分布式:主从复制
    pytest:如何在测试中编写和报告断言
    独辟蹊径:逆推Krpano切图算法,实现在浏览器切多层级瓦片图
    关于华为的BFD
    2039: [蓝桥杯2022初赛] 李白打酒加强版 (动态规划)
  • 原文地址:https://blog.csdn.net/a29562268/article/details/127844441