Linux设备驱动模型初始化流程分析(以PCI/PCIe模块为例)

Linux设备驱动模型(以PCI/PCIe为例)

本文以PCI/PCIe为切入点分析Linux设备驱动模型,并且以SMMU技术来分析PCI虚拟化的应用

关于UEFI/BIOSBootLoaderPCI/PCIe链路的初始化过程此处不做分析,可自行查看edk2和uboot源码

此处以qcom/sm8250.dtsi为例

// linux-5.14.5/arch/arm64/boot/dts/qcom/sm8250.dtsi

		pcie2: pci@1c10000 {
   
			compatible = "qcom,pcie-sm8250", "snps,dw-pcie";
			reg = <0 0x01c10000 0 0x3000>,
			      <0 0x64000000 0 0xf1d>,
			      <0 0x64000f20 0 0xa8>,
			      <0 0x64001000 0 0x1000>,
			      <0 0x64100000 0 0x100000>;
			reg-names = "parf", "dbi", "elbi", "atu", "config";
			device_type = "pci";
			linux,pci-domain = <2>;
			bus-range = <0x00 0xff>;
			num-lanes = <2>;

			#address-cells = <3>;
			#size-cells = <2>;

			ranges = <0x01000000 0x0 0x64200000 0x0 0x64200000 0x0 0x100000>,
				 <0x02000000 0x0 0x64300000 0x0 0x64300000 0x0 0x3d00000>;

			interrupts = <GIC_SPI 236 IRQ_TYPE_EDGE_RISING>;
			interrupt-names = "msi";
			#interrupt-cells = <1>;
			interrupt-map-mask = <0 0 0 0x7>;
			interrupt-map = <0 0 0 1 &intc 0 290 IRQ_TYPE_LEVEL_HIGH>, /* int_a */
					<0 0 0 2 &intc 0 415 IRQ_TYPE_LEVEL_HIGH>, /* int_b */
					<0 0 0 3 &intc 0 416 IRQ_TYPE_LEVEL_HIGH>, /* int_c */
					<0 0 0 4 &intc 0 417 IRQ_TYPE_LEVEL_HIGH>; /* int_d */

			clocks = <&gcc GCC_PCIE_2_PIPE_CLK>,
				 <&gcc GCC_PCIE_2_AUX_CLK>,
				 <&gcc GCC_PCIE_2_CFG_AHB_CLK>,
				 <&gcc GCC_PCIE_2_MSTR_AXI_CLK>,
				 <&gcc GCC_PCIE_2_SLV_AXI_CLK>,
				 <&gcc GCC_PCIE_2_SLV_Q2A_AXI_CLK>,
				 <&gcc GCC_PCIE_MDM_CLKREF_EN>,
				 <&gcc GCC_AGGRE_NOC_PCIE_TBU_CLK>,
				 <&gcc GCC_DDRSS_PCIE_SF_TBU_CLK>;
			clock-names = "pipe",
				      "aux",
				      "cfg",
				      "bus_master",
				      "bus_slave",
				      "slave_q2a",
				      "ref",
				      "tbu",
				      "ddrss_sf_tbu";

			assigned-clocks = <&gcc GCC_PCIE_2_AUX_CLK>;
			assigned-clock-rates = <19200000>;

			iommus = <&apps_smmu 0x1d00 0x7f>;
			iommu-map = <0x0   &apps_smmu 0x1d00 0x1>,
				    <0x100 &apps_smmu 0x1d01 0x1>;

			resets = <&gcc GCC_PCIE_2_BCR>;
			reset-names = "pci";

			power-domains = <&gcc PCIE_2_GDSC>;

			phys = <&pcie2_lane>;
			phy-names = "pciephy";

			perst-gpio = <&tlmm 85 GPIO_ACTIVE_LOW>;
			enable-gpio = <&tlmm 87 GPIO_ACTIVE_HIGH>;

			pinctrl-names = "default";
			pinctrl-0 = <&pcie2_default_state>;

			status = "disabled";
		};

前期构建准备:setup_machine_fdt

start_kernel过程中分析设备树文件,最终调用unflatten_device_tree

// linux-5.14.5/arch/arm64/kernel/setup.c

void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
   
    	setup_machine_fdt(__fdt_pointer);
    
    	// ...
    
        /* Parse the ACPI tables for possible boot-time configuration */
        acpi_boot_table_init();
    
    	if (acpi_disabled)
			unflatten_device_tree();

setup_machine_fdt

// linux-5.14.5/arch/arm/kernel/devtree.c

const struct machine_desc * __init setup_machine_fdt(void *dt_virt)
{
   
	const struct machine_desc *mdesc, *mdesc_best = NULL;
    
	if (!dt_virt || !early_init_dt_verify(dt_virt))
		return NULL;

    mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

early_init_dt_verify中会检查设备树的一些标识,并赋值全局变量initial_boot_params等于设备树地址

// linux-5.14.5/drivers/of/fdt.c

bool __init early_init_dt_verify(void *params)
{
   
	if (!params)
		return false;

	/* check device tree validity */
	if (fdt_check_header(params))
		return false;

	/* Setup flat device-tree pointer */
	initial_boot_params = params;
	of_fdt_crc32 = crc32_be(~0, initial_boot_params,
				fdt_totalsize(initial_boot_params));
	return true;
}

of_flat_dt_match_machine用于读取compatible属性,并对mdesc变量(用于描述硬件单板信息)进行赋值

// linux-5.14.5/drivers/of/fdt.c

const void * __init of_flat_dt_match_machine(const void *default_match,
		const void * (*get_next_compat)(const char * const**))
{
   
    // ...
    
    dt_root = of_get_flat_dt_root();
	
    // ...
    
    pr_info("Machine model: %s\n", of_flat_dt_get_machine_name());

	return best_data;
}

在内核输出信息如下

[    0.000000] Machine model: Raspberry Pi 3 Model B

early_init_dt_scan_nodes用于扫描设备树的各节点,可见其主要分析三个节点

  • early_init_dt_scan_chosen
  • early_init_dt_scan_root
  • early_init_dt_scan_memory
// linux-5.14.5/drivers/of/fdt.c

void __init early_init_dt_scan_nodes(void)
{
   
	int rc = 0;

	/* Retrieve various information from the /chosen node */
	rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
	if (!rc)
		pr_warn("No chosen node found, continuing without\n");

	/* Initialize {size,address}-cells info */
	of_scan_flat_dt(early_init_dt_scan_root, NULL);

	/* Setup memory, calling early_init_dt_add_memory_arch */
	of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

并将chosen信息存入boot_command_line,作为启动参数,关于这三个节点,设备树中如下

// linux-5.14.5/arch/arm64/boot/dts/qcom/sm8250.dtsi

/
    chosen {
    };

	memory@80000000 {
   
		device_type = "memory";
		/* We expect the bootloader to fill in the size */
		reg = <0x0 0x80000000 0x0 0x0>;
	};

关于chosen的产生,有两种情况

  • uboot基本上可以不通过显式的bootargs=xxx来传递给内核,而是在env拿出,并存放进设备树中的chosen节点中
  • Linux也开始在设备树中的chosen节点中获取出来

而对于memory节点,则是将其地址范围加入memblock中进行管理,调用过程如下

// linux-5.14.5/drivers/of/fdt.c

of_scan_flat_dt(early_init_dt_scan_memory, NULL);
	early_init_dt_add_memory_arch(base, size);
		memblock_add(base, size);

并且在setup_machine_fdt之后就是memblock系统的初始化

// linux-5.14.5/arch/arm64/kernel/setup.c

void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
   
    setup_machine_fdt(__fdt_pointer);
    
    arm64_memblock_init();

设备树解析:unflatten_device_tree

unflatten_device_tree

// linux-5.14.5/arch/arm64/kernel/setup.c

void __init __no_sanitize_address setup_arch(char **cmdline_p)
{
   
	/* Parse the ACPI tables for possible boot-time configuration */
	acpi_boot_table_init();

	if (acpi_disabled)
		unflatten_device_tree();

of_root是一个全局struct device_node根节点,链接了所有的struct device_node

// linux-5.14.5/drivers/of/base.c

struct device_node *of_root;

initial_boot_params前面已经指向了设备树地址

// linux-5.14.5/drivers/of/fdt.c

void __init unflatten_device_tree(void)
{
   
	__unflatten_device_tree(initial_boot_params, NULL, &of_root,
				early_init_dt_alloc_memory_arch, false);

	/* Get pointer to "/chosen" and "/aliases" nodes for use everywhere */
	of_alias_scan(early_init_dt_alloc_memory_arch);

	unittest_unflatten_overlay_base();
}

如下,可见会进行两次扫描

  • 第一次扫描会转换成所有struct device_node需要的空间然后系统会申请空间dt_alloc(所有的节点都会形成struct device_node结构)
  • 第二次扫描进行解析,重点在此
// linux-5.14.5/drivers/of/fdt.c

void *__unflatten_device_tree(const void *
### UEFI概述 UEFI(Unified Extensible Firmware Interface),即统一可扩展固件接口,是一种替代传统BIOS的新一代固件标准。它的设计目标是提供一种更加灵活、高效和安全的方式来管理和控制计算机硬件与操作系统之间的交互过程[^1]。 UEFI的主要功能包括但不限于以下几个方面: - **硬件初始化**:负责在系统启动过程中完成必要的硬件检测和初始化工作。 - **引导加载程序的选择和支持**:通过查找特定路径下的`.efi`文件来决定操作系统的启动顺序。 - **安全性增强**:支持Secure Boot等功能,能够有效防止恶意软件篡改系统核心组件[^2]。 --- ### 配置UEFI的方法 要正确配置UEFI环境以实现最佳性能及兼容性,通常需要进入主板设置界面调整若干参数: #### 进入UEFI设置界面 大多数现代PC允许用户按下指定按键(如F2、Del键或其他组合键)快速访问UEFI Setup Utility,在这里可以修改各种高级选项。 #### 基本配置项说明 - **Boot Mode**: 切换Legacy Support状态至关闭模式仅保留纯正的UEFI boot path;这一步骤对于确保完全利用UEFI特性至关重要[^3]。 - **Secure Boot State**: 开启此功能有助于验证所有执行代码的真实性并阻止未签名镜像运行。 - **File System Compatibility**: 如果计划安装某些特殊版本Linux发行版,则可能还需要确认是否启用额外的支持服务比如XHCI Pre-Enumeration等。 以下是具体的操作指南示代码片段用于展示如何编程化地查询当前平台所处的状态以及切换到推荐的安全引导形式之一——Microsoft Windows认证链路下运作: ```powershell # PowerShell脚本检查当前系统的引导模式 $firmwareType = (Get-CimInstance Win32_ComputerSystem).PCSystemType if ($firmwareType -eq 2){ Write-Host "The system is currently running under UEFI mode." } else { Write-Warning "This script requires the computer to be booted into UEFI mode!" } # 设置 Secure Boot 参数 Set-Variable -Name 'secureboot' -Value $true -Scope Global ``` 上述PowerShell命令可以帮助管理员轻松识别设备目前采用的是哪种类型的引导机制,并且提供了简单的手段去激活或者停用Secure Boot技术。 --- ### UEFI引导原理详解 当一台基于UEFI架构构建起来的个人计算装置通电之后,整个引导流程大致遵循如下几个阶段展开: 1. **POST Phase**: Power-On Self Test完成后立即转入下一环节之前先经历一次初步检验周期用来评估基本外围连接状况良好与否。 2. **Driver Initialization Sequence**: 加载驱动程序以便后续步骤得以顺利实施,期间会涉及到众多芯片组特性的适配处理作业。 3. **Locating OS Loader Image**: 寻找存储介质上的合法EFI应用程序实作为下一步动作依据所在位置通常是专门划分出来的ESP区域内部署好的相应目录结构之下. 4. **Handoff Control To Operating Systems**: 将主导权正式移交给已选定的目标操作系统继续接管剩余部分直至最终呈现完整的图形桌面给终端使用者看到为止. 值得注意的地方在于每一个单独构成要素之间都存在着紧密联系相互依赖关系从而构成了一个完整有序的整体链条效应现象发生作用当中. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

妖怪喜欢风

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

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

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

打赏作者

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

抵扣说明:

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

余额充值