系统开机设备寻址全流程:从 UEFI 伪代码到内核映射

摘要

在系统开机过程中,设备寻址是一个关键环节,它涉及到 UEFI 固件对硬件设备的初步探测和资源分配,以及 Linux 内核在此基础上进行的二次加工和冲突化解。

本文以 AMD 集成 GPU(ACPI 描述)和 NVIDIA 独立显卡(PCIe 设备)为例,详细介绍这一过程。

UEFI 固件阶段:硬件探测与地址预分配

集成 GPU(通过 ACPI 描述静态分配资源)

在 UEFI 阶段,系统会对硬件设备进行扫描,并为它们分配初始的资源,包括<font style="color:rgba(0, 0, 0, 0.85);">内存地址</font><font style="color:rgba(0, 0, 0, 0.85);">中断号</font>等。

  • 集成 GPU 之前在开发板上的「集成设备」:PCIe or 非 PCIe ] 文章中已经详细分析过了,通常它们都是固定在系统总线上。
  • 这些设备的资源通常是由 ACPI 表或者设备树进行静态描述完成的。
  • 在内核中通过虚拟的 platform 总线完成设备的挂载,并由 platform 驱动完成设备的加载。

以下是 UEFI 阶段处理集成 GPU 和独立显卡的伪代码示例:

// UEFI阶段:处理ACPI描述的集成GPU(如AMD Ryzen 7945HX的Radeon 780M)
if (acpi_table_exists("DSDT")) {
    device = acpi_find_device("GFX0"); // ACPI设备名
    if (device->hid == "AMD0079") {   // 集成GPU的硬件ID
        
        // 读取ACPI资源描述(_CRS)配置空间,_CRS含义见本文附加内容
        resource = acpi_eval("_CRS"); 
        mmio_reg = resource->mem[0];  // 寄存器M
        vram_shared = resource->mem[1];// 共享显存:系统内存

        // 标记为保留内存(避免被OS分配)
        e820_mark(mmio_reg.base, mmio_reg.size, E820_RESERVED);
        e820_mark(vram_shared.base, vram_shared.size, E820_RESERVED);

        // 写入设备树(供Linux内核使用)
        dtb_add_node("/arm-io/gpu", {
            "reg" = [mmio_reg, vram_shared],
            "compatible" = "amd,renoir-igpu"
        });
    }
}

对于集成 GPU,通过解析 ACPI 表获取其所需的资源信息,包括寄存器 MMIO 地址和共享显存地址,并将这些地址标记为保留,同时写入设备树供内核使用。

独立 PCIe 显卡(通过 bar 的定义动态分配资源)

PCIe 显卡通常在上面会通过接出一根 PCIe 总线,用于挂载 GPU、VPU、NPU 等设备,这些设备也都称之为 PCIe 设备。

  • 它们会在自己的寄存器空间有一个或若干个 Bar 空间,用于定义自己需要的资源描述。
  • PCI 总线驱动扫描到这些设备后,会探测它们的 bar 空间,获取对应则资源描述;
  • 然后动态地为这些 PCIe 设备分配内存、寄存器等资源。
// UEFI PCIe枚举:为RTX 4090分配临时BAR(x86平台)
for (bus = 0; bus < MAX_PCIE_BUS; bus++) {
    device = pcie_scan_device(bus, 0, 0); // 假设显卡在0号总线
    if (device->vendor_id == 0x10DE && device->device_id == 0x2644) {
        
        // 读取初始BAR(全1表示未分配)
        bar0 = pcie_read_bar(device, 0); // 寄存器BAR
        bar1 = pcie_read_bar(device, 1); // 显存BAR
        
        // 从PCIe MMIO池(0x80000000-0xFFFF0000)分配地址
        mmio_pool = 0x80000000;
        bar0_addr = mmio_pool; mmio_pool += 0x10000; // 64KB寄存器
        bar1_addr = mmio_pool; mmio_pool += 0x40000000; // 1GB显存
        
        // 写入BAR并标记保留
        pcie_write_bar(device, 0, bar0_addr | PCIE_BAR_MEM);
        pcie_write_bar(device, 1, bar1_addr | PCIE_BAR_MEM);
        e820_reserve(bar0_addr, 0x10000, "GPU-MMIO");
        e820_reserve(bar1_addr, 0x40000000, "GPU-VRAM");

        // 记录到PCI设备树(x86通过ACPI,ARM64通过DTB)
        acpi_add_pci_device(device, bar0_addr, bar1_addr);
    }
}

Linux 内核:将地址映射到虚拟地址空间

集成 GPU 驱动处理

集成 GPU 的驱动需要根据 UEFI 阶段写入设备树的信息,将设备的寄存器和显存映射到内核的虚拟地址空间。

以下是集成 GPU 驱动的伪代码示例:

// 映射寄存器 MMIO 到内核虚拟地址
regs = devm_ioremap_resource(&pdev->dev, res);
if (!regs) {
    dev_err(&pdev->dev, "Failed to map register MMIO\n");
    return -ENOMEM;
}

// 初始化共享显存(系统内存)
pages = vmalloc_to_page((void *)vram->start);
if (!pages) {
    dev_err(&pdev->dev, "Failed to get pages for VRAM\n");
    return -ENOMEM;
}
if (gpu_ttm_bo_create(&pdev->dev, vram->size, pages)) {
    dev_err(&pdev->dev, "Failed to create GPU BO for VRAM\n");
    return -EFAULT;
}

// 验证:dmesg 输出
dev_info(&pdev->dev, "iGPU MMIO: %pa - %pa", &res->start, &res->end);

独立显卡驱动处理

独立显卡的驱动需要检查 UEFI 阶段分配的 BAR 地址是否与其他设备冲突,如果冲突则需要重新分配地址。

以下是独立显卡驱动的伪代码示例:

resource_size_t pci_addr;
void __iomem *mmio;

// 检查 UEFI 分配的 BAR 是否与集成 GPU 冲突(如 0xA0000000 重叠)
if (pci_resource_start(pdev, 1) < 0xB0000000) {
    // 动态调整显存 BAR 到高位
    /*   ...调整代码省略...  */  
    if (pci_request_resource(pdev, &pdev->resource[1], "nvidia - vram")) {
        dev_err(&pdev->dev, "Failed to request new VRAM resource\n");
        return -ENODEV;
    }
    pci_addr = pci_resource_start(pdev, 1);  // 新地址 0xB0000000
} else {
    pci_addr = pci_resource_start(pdev, 1);
}

// 映射寄存器 BAR 到内核虚拟地址
mmio = pci_iomap(pdev, 0, pci_resource_len(pdev, 0));
if (!mmio) {
    dev_err(&pdev->dev, "Failed to map register BAR\n");
    return -ENOMEM;
}

// 初始化独立显存(通过 PCIe BAR 直接访问)
if (nvidia_gpu_init(pdev, pci_addr)) {
    dev_err(&pdev->dev, "Failed to initialize GPU VRAM\n");
    return -EFAULT;
}
// 验证:/proc/iomem 输出
// 0000:01:00.0 BAR1 [mem 0xb0000000 - 0xbfffffff]

资源查看

集成 GPU

# 查看 ACPI 定义的资源
cat /sys/firmware/acpi/tables/DSDT | grep -A 3 "Device (GFX0)"
# 查看内核映射
cat /proc/iomem | grep "amdgpu"

PCIe-GPU

# 查看 PCI 设备 BAR
lspci -vvv -s 01:00.0 | grep BAR
# 验证地址不冲突
cat /proc/iomem | grep -E "a0000000|b0000000"

总结

在系统开机过程中,UEFI 固件负责对硬件设备进行初步的探测和资源预分配,而 Linux 内核则在 UEFI 的基础上进行地址的二次加工和冲突化解。集成 GPU 通过 ACPI 表获取静态资源,而独立显卡则通过 PCIe 枚举进行动态资源分配。通过合理的资源分配和冲突解决机制,系统可以确保各个设备能够正常工作。


附加

ACPI 中 CRS 的描述

假设系统中有一个集成显卡设备,其ACPI描述如下:

// ACPI 
    Scope (_SB)
    {
        Device (DC)
        {
            Name (_HID, "PNP0C0A")  // _HID: Hardware ID
            Name (_UID, Zero)  // _UID: Unique ID
            Name (_CCA, One)  // _CCA: Cache Coherency Attribute
            Name (PIPE, 0x03)
            Name (_CRS, ResourceTemplate ()  // _CRS: Current Resource Settings
            {
                Memory32Fixed (ReadWrite,  // 寄存器空间(必选)
                    0x28000000,         // Address Base
                    0x00008000,         // Address Length
                    )
                Memory32Fixed (ReadWrite,  // 预留内存(可选,全0表示未使用)
                    0x00000000,         // Address Base
                    0x00000000,         // Address Length
                    )
                Interrupt (ResourceConsumer, Level, ActiveHigh, Exclusive, ,, )
                {
                    0x0000006C,  // 中断配置(必选)
                }
                GpioIo (Exclusive, PullUp, 0x0000, 0x0000, IoRestrictionOutputOnly,
                    "\\_SB.GPI3", 0x00, ResourceConsumer, ,
                    )
                    {   // Pin list
                        0x0002  // 输出引脚2(必选)
                    }
                GpioIo (Exclusive, PullUp, 0x0000, 0x0000, IoRestrictionOutputOnly,
                    "\\_SB.GPI3", 0x00, ResourceConsumer, ,
                    )
                    {   // Pin list  
                        0x0003  // 输出引脚3(必选)
                    }
            })
``
  1. 设备对象 (Device)
    这个案例描述了一个 DC 设备,其设备标识符为 PNP0C0A。设备对象中包含了设备的标识符、唯一标识符,以及资源描述。
  2. CRS(当前资源设置)
    CRS (_CRS) 描述了集成显卡在实际运行时当前使用的资源,确保操作系统能够正确配置和管理硬件资源。
    • 在系统启动时,ACPI解析器会读取集成显卡的 _CRS
    • 操作系统的资源管理器会根据 _CRS 确保集成显卡的资源分配不会与其他设备冲突。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值