一、问题背景
在基于 PCIe / NVMe SSD 启动 Linux 的系统中,常会遇到这样一个疑问:Linux 内核存放在 SSD 上,而 PCIe RC 驱动又属于 Linux 内核的一部分。那么,在内核尚未运行之前,系统是如何访问 SSD 并加载内核的?
三、NVMe 启动链路中的两次 PCIe 初始化
在 Linux 启动之前,系统仍运行在 Bootloader 阶段,此时 Bootloader 已完成最小化的 PCIe RC 初始化,并实现了仅用于启动的 NVMe 访问逻辑,其目标只是将内核镜像从 SSD 读入内存。
Linux 启动后会重新初始化 PCIe 并重新枚举设备,Bootloader 阶段的 PCIe 配置不会被继承,这种“重复初始化”并不是设计缺陷,而是 系统架构刻意如此设计的结果
四、qcom U-Boot 与 Linux PCIe 初始化关键代码分析
1. 初始化入口
U-Boot (pcie_dw_qcom.c)
staticintqcom_pcie_init_port(struct udevice *dev){ struct qcom_pcie *priv = dev_get_priv(dev); dm_gpio_set_value(&priv->rst_gpio, 1); udelay(PERST_DELAY_US); ret = generic_phy_init(&priv->phy); /* 启用电源、时钟、复位,配置 RC 模式 */ writel(DEVICE_TYPE_RC, priv->parf + PARF_DEVICE_TYPE); ... /* 启动链路训练 */ val = readl(priv->parf + PARF_LTSSM); val |= LTSSM_EN; writel(val, priv->parf + PARF_LTSSM); return 0;}
Linux (pcie-qcom.c)
staticintqcom_pcie_host_init(struct dw_pcie_rp *pp){ struct qcom_pcie *pcie = to_qcom_pcie(to_dw_pcie_from_pp(pp)); qcom_ep_reset_assert(pcie); ret = pcie->cfg->ops->init(pcie); ret = qcom_pcie_phy_power_on(pcie); if (pcie->cfg->ops->post_init) ret = pcie->cfg->ops->post_init(pcie); qcom_ep_reset_deassert(pcie); if (pcie->cfg->ops->config_sid) ret = pcie->cfg->ops->config_sid(pcie); return 0;}
2. 硬件初始化
Linux (qcom_pcie_init_2_1_0)
ret = reset_control_bulk_assert(res->num_resets, res->resets);ret = regulator_bulk_enable(ARRAY_SIZE(res->supplies), res->supplies);ret = reset_control_bulk_deassert(res->num_resets, res->resets);
目的:将 Bootloader 可能留下的 PCIe 状态复位,确保内核正确初始化。
3. 链路训练启动
U-Boot
val = readl(priv->parf + PARF_LTSSM);val |= LTSSM_EN;writel(val, priv->parf + PARF_LTSSM);
Linux
if (pcie->cfg->ops->ltssm_enable) pcie->cfg->ops->ltssm_enable(pcie);
Linux 支持更多高级特性,如 Gen4 均衡和 lane margining。
4. 主要区别总结
代码结构
错误处理
U-Boot:简单 goto 标签
Linux:多级错误恢复,支持安全卸载和资源回收
功能复杂度
链路训练
U-Boot:直接写寄存器启动 LTSSM
Linux:通过 ops 调用,支持高级特性
五、结论
从 SSD 启动 Linux 并不是 Linux 在启动 SSD,而是 Bootloader 先完成了 PCIe 和 NVMe 的最小化初始化,将内核加载到内存中。
Linux 启动后再重新初始化 PCIe 子系统,正式接管 SSD 的管理。