AMD KFD的BO设计分析系列10-1:doorbell bo的相关内核实现

前文:AMD rocr-libhsakmt分析系列7-1: doorbell机制概览中,描述了AMD的doorbell的机制。本文重点描述KFD中的相关实现。

1. doorbell 的硬件原理

doorbell(门铃)是 AMD GPU 架构中用于CPU与 GPU 硬件之间高效通信的一种机制。
它本质上是一块映射到用户/内核空间的物理寄存器区间(GPU/GPGPU设备上一个存储空间),进程或驱动通过写入特定的 doorbell 索引(index),即可向 GPU 某个硬件引擎(如调度器、DMA、Compute 队列等)发出“有新任务”或“状态变更”的通知。

doorbell 区域通常位于 PCIe BAR2(Base Address Register 2)空间,大小数 KB 到数 MB 不等。每个 doorbell 索引对应 32 位(dword)或 64 位(qword)寄存器。doorbell BAR 通过 mmap映射到用户/内核空间,支持进程隔离和权限控制。

2. doorbell 在 AMDGPU 驱动中的实现

2.1 doorbell 相关数据结构

在 AMDGPU 驱动中,doorbell 相关结构体为:

struct amdgpu_doorbell {
    resource_size_t base;         // Doorbell BAR 基地址
    resource_size_t size;         // Doorbell BAR 大小
    u32 num_kernel_doorbells;     // 内核可用 doorbell 数量
    struct amdgpu_bo *kernel_doorbells; // 内核 doorbell BO
    u32 *cpu_addr;                // Doorbell 映射到 CPU 的虚拟地址
};
  • base 和 size 由 PCIe BAR2 获取。

  • num_kernel_doorbells 表示内核预留的 doorbell 数量。

  • kernel_doorbells 是内核分配的 doorbell BO(Buffer Object)。

  • cpu_addr 是 doorbell aperture 的虚拟地址,供内核/用户空间直接访问。

2.2 doorbell 的初始化流程

2.2.1 doorbell BAR 的初始化

这个过程在驱动初始化时执行,确定好doorbell的基地址和空间大小。

nt amdgpu_doorbell_init(struct amdgpu_device *adev)
{
    if (adev->asic_type < CHIP_BONAIRE) {
        adev->doorbell.base = 0;
        adev->doorbell.size = 0;
        adev->doorbell.num_kernel_doorbells = 0;
        return 0;
    }

    if (pci_resource_flags(adev->pdev, 2) & IORESOURCE_UNSET)
        return -EINVAL;

    amdgpu_asic_init_doorbell_index(adev);

    adev->doorbell.base = pci_resource_start(adev->pdev, 2);
    adev->doorbell.size = pci_resource_len(adev->pdev, 2);

    adev->doorbell.num_kernel_doorbells =
        min_t(u32, adev->doorbell.size / sizeof(u32),
              adev->doorbell_index.max_assignment + 1);

    if (adev->doorbell.num_kernel_doorbells == 0)
        return -EINVAL;

    if (adev->asic_type >= CHIP_VEGA10)
        adev->doorbell.num_kernel_doorbells += 0x400;

    return 0;
}
  • 通过 PCIe BAR2 获取 doorbell 的物理地址和大小。

  • 计算可用 doorbell 数量,部分为内核预留,部分可分配给用户进程。

  • Vega10 及以后架构会额外预留一页 doorbell 空间。

2.2.2 内核doorbell BO 的分配

在驱动初始化各memory的管理器后,就会调用下面的函数初始化内核用的doorbell。所以这个初始化的时机是比较早的。在不支持user doorbell的需求中,会使用内核doorbell。在AMD的内核代码中,注释是这样的:amdgpu_doorbell_create_kernel_doorbells - Create kernel doorbells for graphics。看起来是专给图形驱动用的,KFD走user doorbell。

//调用链
amdgpu_bo_init
    amdgpu_ttm_init
        amdgpu_doorbell_create_kernel_doorbells

int amdgpu_doorbell_create_kernel_doorbells(struct amdgpu_device *adev)
{
    int size = ALIGN(adev->doorbell.num_kernel_doorbells * sizeof(u32), PAGE_SIZE);

    adev->mes.db_start_dw_offset = size / sizeof(u32);
    size += PAGE_SIZE;

    int r = amdgpu_bo_create_kernel(adev,
                                    size,
                                    PAGE_SIZE,
                                    AMDGPU_GEM_DOMAIN_DOORBELL,
                                    &adev->doorbell.kernel_doorbells,
                                    NULL,
                                    (void **)&adev->doorbell.cpu_addr);
    if (r) {
        DRM_ERROR("Failed to allocate kernel doorbells, err=%d\n", r);
        return r;
    }

    adev->doorbell.num_kernel_doorbells = size / sizeof(u32);
    return 0;
}
  • 分配一个doorbell bo 映射到内核虚拟地址空间。

  • cpu_addr 指向 doorbell aperture 的起始地址,供内核直接读写。

2.2.3 内核doorbell BO的释放

void amdgpu_doorbell_fini(struct amdgpu_device *adev)
{
    amdgpu_bo_free_kernel(&adev->doorbell.kernel_doorbells,
                          NULL,
                          (void **)&adev->doorbell.cpu_addr);
}
  • 释放 doorbell BO,解除映射。

2.3 Doorbell 的读写操作

2.3.1 内核空间读写

u32 amdgpu_mm_rdoorbell(struct amdgpu_device *adev, u32 index)
{
    if (index < adev->doorbell.num_kernel_doorbells)
        return readl(adev->doorbell.cpu_addr + index);
    DRM_ERROR("reading beyond doorbell aperture: 0x%08x!\n", index);
    return 0;
}

void amdgpu_mm_wdoorbell(struct amdgpu_device *adev, u32 index, u32 v)
{
    if (index < adev->doorbell.num_kernel_doorbells)
        writel(v, adev->doorbell.cpu_addr + index);
    else
        DRM_ERROR("writing beyond doorbell aperture: 0x%08x!\n", index);
}
  • 通过 readl/writel 直接访问 doorbell aperture,实现低延迟通知。

  • 64 位 doorbell 用 atomic64_read/atomic64_set 实现。

2.4 doorbell 索引的分配与管理

doorbell 索引由驱动统一分配,分为内核保留和用户进程分配两部分。每个进程/队列分配独立的 doorbell 索引,避免冲突。doorbell 分配表(如 adev->doorbell_index)记录所有已分配的索引。进程退出或队列销毁时,驱动回收对应的 doorbell 索引,供后续分配复用。

3. Doorbell 的典型调用路径

3.1 用户空间提交任务的流程

  1. 用户空间将任务描述符写入共享内存(如 command buffer)。

  2. 用户空间写入自己的 doorbell 索引(通过 mmap 的 doorbell aperture)。

  3. GPU 硬件检测到 doorbell 变化,读取任务描述符并执行。

3.2 内核空间 doorbell 操作

  • 内核驱动在调度、队列管理、测试等场景下,也会直接写 doorbell,通知 GPU 执行特定操作。

3.3 Doorbell 索引的分配与回收

  • 进程创建时,驱动分配 doorbell 索引,并通过 ioctl 或 mmap 传递给用户空间。

  • 进程退出时,驱动回收 doorbell 索引,清理资源。

接下来分析libhskmt里doorbell的应用,详见:AMD rocr-libhsakmt分析系列7-2:doorbell的实现

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

DeeplyMind

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

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

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

打赏作者

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

抵扣说明:

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

余额充值