跳转至

第9篇:RDMA 硬件侧 MR 注册:mlx5/mr.c

源码:drivers/infiniband/hw/mlx5/mr.c | 头文件:drivers/infiniband/hw/mlx5/mlx5_ib.h 系列目录:NVIDIA AI Infra 内核源码深度解析

1. MKey 是什么

MKey(Memory Key)是 InfiniBand/RoCE 协议中最核心的内存管理概念。每个远程 DMA 操作(RDMA Read/Write/Atomic)都需要携带一个有效的 MKey,HCA(Host Channel Adapter,如 ConnectX-7)用 MKey 来:

  1. 查 IOMMU 页表:将虚拟地址翻译为物理总线地址
  2. 权限检查:remote read/write/atomic 是否允许
  3. 地址空间隔离:进程 A 的 MKey 无法访问进程 B 的内存

MKey 的创建需要固件命令(CREATE_MKEY),这在 RDMA 中是开销最大的操作之一。mlx5/mr.c(2197 行)是整个 ConnectX 系列内存注册的枢纽。

2. 架构总览

MR 注册的三条路径:

                    mlx5_ib_reg_user_mr (854)
                    ┌───────┼──────────┐
                    │       │          │
            ODP 流程  UMR 快速路径   reg_create 慢路径
            (874)    (755)           (532)
寄存器路径决策:
  ┌─────────────────────────────────────────────────────┐
  │ access_flags & IB_ACCESS_ON_DEMAND?                  │
  │   YES → create_user_odp_mr()  [ODP 按需分页]        │
  │   NO  → 继续                                        │
  │                                                      │
  │ 用户内存: ib_umem_get() 获取 SG table                │
  │   → create_real_mr()                                 │
  │                                                      │
  │ dmabuf 内存: reg_user_mr_dmabuf()                    │
  │   → alloc_cacheable_mr() [UMR 快速路径]              │
  └─────────────────────────────────────────────────────┘

3. 核心函数

3.1 mlx5_ib_reg_user_mr — 公共入口

drivers/infiniband/hw/mlx5/mr.c:854-880

struct ib_mr *mlx5_ib_reg_user_mr(struct ib_pd *pd, u64 start, u64 length,
                                  u64 iova, int access_flags,
                                  struct ib_dmah *dmah,
                                  struct ib_udata *udata)
{
    struct mlx5_ib_dev *dev = to_mdev(pd->device);
    struct ib_umem *umem;
    int err;

    if (!IS_ENABLED(CONFIG_INFINIBAND_USER_MEM) ||
        ((access_flags & IB_ACCESS_ON_DEMAND) && dmah))
        return ERR_PTR(-EOPNOTSUPP);

    mlx5_ib_dbg(dev,
        "start 0x%llx, iova 0x%llx, length 0x%llx, access_flags 0x%x\n",
        start, iova, length, access_flags);

    err = mlx5r_umr_resource_init(dev);  // 初始化 UMR 资源
    if (err)
        return ERR_PTR(err);

    if (access_flags & IB_ACCESS_ON_DEMAND)
        return create_user_odp_mr(pd, start, length, iova, access_flags, udata);

    umem = ib_umem_get(&dev->ib_dev, start, length, access_flags);
    if (IS_ERR(umem))
        return ERR_CAST(umem);

    return create_real_mr(pd, umem, iova, access_flags, dmah);
}

三路分发: - ODP(On-Demand Paging):用户启用了 IB_ACCESS_ON_DEMAND - 非 ODP 用户内存:ib_umem_getcreate_real_mr - dmabuf 内存:走 mlx5_ib_reg_user_mr_dmabuf

3.2 create_real_mr — UMR vs 慢路径决策

drivers/infiniband/hw/mlx5/mr.c:735-769

static struct ib_mr *create_real_mr(struct ib_pd *pd, struct ib_umem *umem,
                                    u64 iova, int access_flags,
                                    struct ib_dmah *dmah)
{
    struct mlx5_ib_dev *dev = to_mdev(pd->device);
    struct mlx5_ib_mr *mr = NULL;
    bool xlt_with_umr;
    u16 st_index = MLX5_MKC_PCIE_TPH_NO_STEERING_TAG_INDEX;
    u8 ph = MLX5_IB_NO_PH;
    int err;

    xlt_with_umr = mlx5r_umr_can_load_pas(dev, umem->length);
    if (xlt_with_umr) {
        mr = alloc_cacheable_mr(pd, umem, iova, access_flags,
                                MLX5_MKC_ACCESS_MODE_MTT,
                                st_index, ph);
    } else {
        unsigned long page_size = mlx5_umem_mkc_find_best_pgsz(
            dev, umem, iova, MLX5_MKC_ACCESS_MODE_MTT);

        mutex_lock(&dev->slow_path_mutex);
        mr = reg_create(pd, umem, iova, access_flags, page_size,
                        true, MLX5_MKC_ACCESS_MODE_MTT,
                        st_index, ph);
        mutex_unlock(&dev->slow_path_mutex);
    }

关键决策(行 754):mlx5r_umr_can_load_pas 判断 PAS(Physical Address Segment)是否能在单个 UMR WQE 中装下。

┌─────────────────────────────────────────────────────────┐
│  UMR (User Mode Registration) 快速路径:                  │
│  条件: PAS entries 数量 ≤ UMR WQE 容量                  │
│  流程: alloc_cacheable_mr → 从 FRMR pool 获取预分配 MKey │
│        → mlx5r_umr_update_mr_pas 修改 MKey 的 PAS       │
│  优点: 无需固件命令,只写 WQE,延迟 ~1μs                │
│                                                         │
│  reg_create 慢路径:                                     │
│  条件: PAS 数量 > UMR WQE 容量,或超大内存              │
│  流程: 构建 CREATE_MKEY 固件命令 → kvzalloc 命令缓冲区   │
│        → 填充 PAS/MKC 字段 → 发送固件命令               │
│  缺点: 固件 RPC 延迟 ~50-100μs                          │
└─────────────────────────────────────────────────────────┘

3.3 reg_create — CREATE_MKEY 固件命令

drivers/infiniband/hw/mlx5/mr.c:532-601

static struct mlx5_ib_mr *reg_create(struct ib_pd *pd, struct ib_umem *umem,
                                     u64 iova, int access_flags,
                                     unsigned long page_size, bool populate,
                                     int access_mode, u16 st_index, u8 ph)
{
    struct mlx5_ib_dev *dev = to_mdev(pd->device);
    struct mlx5_ib_mr *mr;
    __be64 *pas;
    void *mkc;
    int inlen;
    u32 *in;
    int err;
    bool pg_cap = !!(MLX5_CAP_GEN(dev->mdev, pg)) &&
        (access_mode == MLX5_MKC_ACCESS_MODE_MTT) &&
        (ph == MLX5_IB_NO_PH);
    bool ksm_mode = (access_mode == MLX5_MKC_ACCESS_MODE_KSM);

    if (!page_size)
        return ERR_PTR(-EINVAL);
    mr = kzalloc_obj(*mr);
    if (!mr)
        return ERR_PTR(-ENOMEM);

    mr->ibmr.pd = pd;
    mr->access_flags = access_flags;
    mr->page_shift = order_base_2(page_size);

    inlen = MLX5_ST_SZ_BYTES(create_mkey_in);
    if (populate)
        inlen += sizeof(*pas) *
                 roundup(ib_umem_num_dma_blocks(umem, page_size), 2);
    in = kvzalloc(inlen, GFP_KERNEL);
    // ...
    pas = (__be64 *)MLX5_ADDR_OF(create_mkey_in, in, klm_pas_mtt);
    if (populate) {
        if (WARN_ON(access_flags & IB_ACCESS_ON_DEMAND || ksm_mode)) {
            err = -EINVAL;
            goto err_2;
        }
        mlx5_ib_populate_pas(umem, 1UL << mr->page_shift, pas,
                             pg_cap ? MLX5_IB_MTT_PRESENT : 0);
    }

命令布局

┌─────────────────────────────────────────────────────────┐
│              CREATE_MKEY firmware command                │
│                                                         │
│  ┌─────────────────────────────────────────────────────┐│
│  │  MKC (Memory Key Context)                           ││
│  │  - access_mode: MTT/KSM/SW_ICM/INTERLEAVED         ││
│  │  - iova: 起始虚拟地址                               ││
│  │  - len: 内存区域长度                                ││
│  │  - log_page_size: 页大小 (log2)                     ││
│  │  - bsf_octword_size: 0 (不启用 bank separated format)││
│  │  - translations_octword_size: PAS 数组大小          ││
│  │  - access_flags: local/remote read/write/atomic     ││
│  │  - pd: protection domain                           ││
│  │  - free: 0 (MTT 模式不掉就释放)                     ││
│  │  - umr_en: 1 (允许后续 UMR 重编程)                  ││
│  ├─────────────────────────────────────────────────────┤│
│  │  PAS (Physical Address Segments)                    ││
│  │  ┌─────────────────────┬───────────────────────────┐││
│  │  │ PAS[0] = BUS_ADDR_0 │ PAS[1] = BUS_ADDR_1 │ ... │││
│  │  └─────────────────────┴───────────────────────────┘││
│  │  每个 PAS 是一个 64-bit PCIe 总线地址                 ││
│  └─────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────┘

MKC 字段设置(行 583-604):

    mkc = MLX5_ADDR_OF(create_mkey_in, in, memory_key_mkey_entry);
    set_mkc_access_pd_addr_fields(mkc, access_flags, iova, ...);
    MLX5_SET(mkc, mkc, free, !populate);
    MLX5_SET(mkc, mkc, access_mode_1_0, access_mode);
    MLX5_SET(mkc, mkc, umr_en, 1);          // 允许后续 UMR 更新
    MLX5_SET64(mkc, mkc, len, umem->length);
    MLX5_SET(mkc, mkc, bsf_octword_size, 0);
    MLX5_SET(mkc, mkc, log_page_size, mr->page_shift);

3.4 alloc_cacheable_mr — FRMR 池分配(UMR 快速路径)

drivers/infiniband/hw/mlx5/mr.c:440-469

static struct mlx5_ib_mr *alloc_cacheable_mr(struct ib_pd *pd,
                                             struct ib_umem *umem, u64 iova,
                                             int access_flags, int access_mode,
                                             u16 st_index, u8 ph)
{
    struct mlx5_ib_dev *dev = to_mdev(pd->device);
    struct mlx5_ib_mr *mr;
    unsigned long page_size;

    if (umem->is_dmabuf)
        page_size = mlx5_umem_dmabuf_default_pgsz(umem, iova);
    else
        page_size = mlx5_umem_mkc_find_best_pgsz(dev, umem, iova,
                                                 access_mode);
    if (WARN_ON(!page_size))
        return ERR_PTR(-EINVAL);

    mr = _mlx5_frmr_pool_alloc(dev, umem, access_flags, access_mode,
                               page_size, st_index, ph);
    if (IS_ERR(mr))
        return mr;

    mr->mmkey.type = MLX5_MKEY_MR;
    mr->ibmr.pd = pd;
    mr->umem = umem;
    mr->page_shift = order_base_2(page_size);
    set_mr_fields(dev, mr, umem->length, access_flags, iova);

    return mr;
}

FRMR(Fast Register Memory Region)pool 预分配了一组 MKey,可以从池中获取而不需要固件命令。获取后通过 mlx5r_umr_update_mr_pas 用 UMR WQE 设置 PAS。

3.5 mlx5_ib_populate_pas — 构建 PAS 数组

drivers/infiniband/hw/mlx5/mem.c:41-51

void mlx5_ib_populate_pas(struct ib_umem *umem, size_t page_size, __be64 *pas,
                          u64 access_flags)
{
    struct ib_block_iter biter;

    rdma_umem_for_each_dma_block (umem, &biter, page_size) {
        *pas = cpu_to_be64(rdma_block_iter_dma_address(&biter) |
                           access_flags);
        pas++;
    }
}

核心循环:遍历 ib_umem 的 DMA 块(由 dma_buf_map_attachment 返回的 SG table),将每个 DMA 块的物理地址以大端序cpu_to_be64)写入 PAS 数组。

PAS 数组填充示意:
  umem SG table:              生成的 PAS 数组:
  ┌──────────────┐            ┌────────────────────────┐
  │ sg[0]: 0x1A000│           │ PAS[0]: 0x00000001A000 │
  │ len: 4096    │    ──→     │ (大端, 可能带 access)  │
  ├──────────────┤            ├────────────────────────┤
  │ sg[1]: 0x2B000│           │ PAS[1]: 0x00000002B000 │
  │ len: 4096    │            ├────────────────────────┤
  ├──────────────┤            │ PAS[2]: 0x00000003C000 │
  │ sg[2]: 0x3C000│            └────────────────────────┘
  │ len: 4096    │
  └──────────────┘

4. GPUDirect RDMA 路径:dmabuf MR 注册

4.1 mlx5_ib_reg_user_mr_dmabuf — 公共入口

drivers/infiniband/hw/mlx5/mr.c:1030-1066

struct ib_mr *mlx5_ib_reg_user_mr_dmabuf(struct ib_pd *pd, u64 offset,
                                         u64 length, u64 virt_addr,
                                         int fd, int access_flags,
                                         struct ib_dmah *dmah,
                                         struct uverbs_attr_bundle *attrs)
{
    struct mlx5_ib_dev *dev = to_mdev(pd->device);
    int mlx5_access_flags = 0;
    int err;

    if (!IS_ENABLED(CONFIG_INFINIBAND_USER_MEM) ||
        !IS_ENABLED(CONFIG_INFINIBAND_ON_DEMAND_PAGING))
        return ERR_PTR(-EOPNOTSUPP);

    // ...

    /* dmabuf requires xlt update via umr to work. */
    if (!mlx5r_umr_can_load_pas(dev, length))
        return ERR_PTR(-EINVAL);

    if (mlx5_access_flags & MLX5_IB_UAPI_REG_DMABUF_ACCESS_DATA_DIRECT)
        return reg_user_mr_dmabuf_by_data_direct(pd, offset, length,
                                                 virt_addr, fd, access_flags);

    return reg_user_mr_dmabuf(pd, NULL, offset, length, virt_addr, fd,
                              access_flags, MLX5_MKC_ACCESS_MODE_MTT, dmah);
}

约束(行 1057):dmabuf MR 注册必须能用 UMR——这是硬性要求。如果内存区域太大,无法在 UMR WQE 中容纳全部 PAS,直接返回 -EINVAL

4.2 reg_user_mr_dmabuf — 核心转换

drivers/infiniband/hw/mlx5/mr.c:902-974

static struct ib_mr *
reg_user_mr_dmabuf(struct ib_pd *pd, struct device *dma_device,
                   u64 offset, u64 length, u64 virt_addr,
                   int fd, int access_flags, int access_mode,
                   struct ib_dmah *dmah)
{
    bool pinned_mode = (access_mode == MLX5_MKC_ACCESS_MODE_KSM);
    struct mlx5_ib_dev *dev = to_mdev(pd->device);
    struct mlx5_ib_mr *mr = NULL;
    struct ib_umem_dmabuf *umem_dmabuf;
    // ...
    int err;

    err = mlx5r_umr_resource_init(dev);
    if (err)
        return ERR_PTR(err);

    if (!pinned_mode)
        umem_dmabuf = ib_umem_dmabuf_get(&dev->ib_dev,
                                         offset, length, fd, access_flags,
                                         &mlx5_ib_dmabuf_attach_ops);
    else if (dma_device)
        umem_dmabuf = ib_umem_dmabuf_get_pinned_with_dma_device(
            &dev->ib_dev, dma_device, offset, length, fd, access_flags);
    else
        umem_dmabuf = ib_umem_dmabuf_get_pinned(
            &dev->ib_dev, offset, length, fd, access_flags);
    // ...

    mr = alloc_cacheable_mr(pd, &umem_dmabuf->umem, virt_addr,
                            access_flags, access_mode, st_index, ph);
    // ...

    umem_dmabuf->private = mr;
    if (!pinned_mode) {
        err = mlx5r_store_odp_mkey(dev, &mr->mmkey);
        // ...
    } else {
        mr->data_direct = true;          // GPUDirect RDMA!
    }

    err = mlx5_ib_init_dmabuf_mr(mr);  // 初始化 dmabuf MR
    // ...
    return &mr->ibmr;
}

流程图

reg_user_mr_dmabuf()
  ├─ 【步骤 1】mlx5r_umr_resource_init(dev)
  │   初始化 UMR 资源(WQE buffer、固件上下文)
  ├─ 【步骤 2】ib_umem_dmabuf_get_pinned(fd)
  │   → dma_buf_get(fd)
  │   → dma_buf_pin(attach)
  │   → dma_buf_map_attachment() → GPU VRAM bus addresses (SG table)
  ├─ 【步骤 3】alloc_cacheable_mr()
  │   从 FRMR pool 获取预分配的 MKey
  ├─ 【步骤 4】mlx5r_umr_update_mr_pas()
  │   用 UMR WQE 更新 MKey 的 PAS
  │   PAS 条目 = GPU VRAM 的 PCIe 总线地址
  ├─ 【步骤 5】标记 data_direct = true
  │   告诉内核: 这个 MKey 的 PAS 指向 GPU 显存
  └─ 【步骤 6】返回 ib_mr
      用户态拿到 lkey/rkey, 可以做 RDMA

5. dmabuf 撤销回调

drivers/infiniband/hw/mlx5/mr.c:883-900

static void mlx5_ib_dmabuf_invalidate_cb(struct dma_buf_attachment *attach)
{
    struct ib_umem_dmabuf *umem_dmabuf = attach->importer_priv;
    struct mlx5_ib_mr *mr = umem_dmabuf->private;

    dma_resv_assert_held(umem_dmabuf->attach->dmabuf->resv);

    if (!umem_dmabuf->sgt || !mr)
        return;

    mlx5r_umr_update_mr_pas(mr, MLX5_IB_UPD_XLT_ZAP);
    ib_umem_dmabuf_unmap_pages(umem_dmabuf);
}

static struct dma_buf_attach_ops mlx5_ib_dmabuf_attach_ops = {
    .allow_peer2peer = 1,                              // 支持 PCIe P2P
    .invalidate_mappings = mlx5_ib_dmabuf_invalidate_cb, // 撤销回调
};

当 GPU 驱动异步撤销 dmabuf 时(例如 VRAM 被迁移回系统内存),invalidate_cb 通过 MLX5_IB_UPD_XLT_ZAP 使 MKey 的 PAS 失效。后续任何通过该 MKey 的 RDMA 操作都会收到 Access Error

6. ODP(On-Demand Paging)分支

drivers/infiniband/hw/mlx5/mr.c:874

    if (access_flags & IB_ACCESS_ON_DEMAND)
        return create_user_odp_mr(pd, start, length, iova, access_flags, udata);

ODP 模式下,MKey 创建时不填充 PAS——HCA 在 DMA 时自己处理缺页。页故障由 mlx5_ib_page_fault_resume 处理,自动从系统内存或 GPU 显存获取页。

7. 访问模式对比

模式 含义 典型场景
MTT MLX5_MKC_ACCESS_MODE_MTT 标准页表翻译 常规用户内存 MR
KSM MLX5_MKC_ACCESS_MODE_KSM Key Segment Mode NIC 内部内存(如 Striding RQ)
SW ICM MLX5_MKC_ACCESS_MODE_SW_ICM 软件管理 ICM Header modify pattern
INTERLEAVED 交错模式 多通道交错访问 某些 HPC 场景

8. 端到端 GPUDirect RDMA 时序

                           时间轴 →
用户态    ┌─ibv_reg_dmabuf_mr────┐                ┌─ibv_post_send(RDMA_WRITE)─┐
          │                       │                │                          │
内核      │ dma_buf_get(fd)       │                │                          │
          │ dma_buf_pin           │                │                          │
          │ dma_buf_map_attachment│                │                          │
          │ alloc_cacheable_mr    │                │                          │
          │ UMR update PAS ──────→│  MKey created  │                          │
          │     (GPU VRAM bus)    │  PAS = GPU     │                          │
          │                       │  addrs         │                          │
HCA 固件  │                       │  CREATE_MKEY   │                          │
          │                       │  (如果走慢路径) │                          │
          │                       │                │                          │
HCA 硬件  │                       │                │ DMA read GPU VRAM → 网络 │
          │                       │                │  (PCIe P2P, 零 CPU 参与)   │
          └───────────────────────┴────────────────┴──────────────────────────┘
           ~100μs (UMR fast)       MKey ready!       0-copy DMA!
           ~200μs (reg_create)

9. 系列结语

至此,我们已经完成了从 HMM 内存镜像GPUDirect RDMA 硬件 DMA 的完整内核路径:

第1篇 HMM          │ CPU↔GPU 页表镜像基础
第2篇 DRM GPUSVM   │ GPU 共享虚拟内存框架
第3篇 nouveau_svm  │ NVIDIA 的 HMM 调用者
第4篇 nouveau_dmem │ GPU VRAM 作为 DEVICE_PRIVATE 内存
第5篇 TTM          │ 多内存类型管理的 BO 框架
第6篇 GPU Buddy    │ 增广红黑树显存块分配器
第7篇 DRM Scheduler│ 信用制 GPU 命令调度
第8篇 umem_dmabuf  │ GPU dmabuf → RDMA MR 桥梁
第9篇 mlx5/mr      │ HCA 硬件侧 MKey 创建与 IOMMU 编程

这 9 篇文章覆盖了 NVIDIA AI Infra 的三个维度:

  • 统一内存(文章 1→4):CUDA Unified Memory 的内核实现
  • 显存管理(文章 5→6):GPU VRAM 的分配、迁移与回收
  • 零拷贝通信(文章 8→9):GPUDirect RDMA 的全链路

AI 训练中的 ncclAllReduce(data, ...) 一行调用,背后是这数千行内核代码在协作:hmm_range_fault 确保 GPU 能访问 CPU 页 → gpu_buddy_alloc_blocks 在 VRAM 中分配 buffer → drm_sched_entity_push_job 提交计算命令 → ib_umem_dmabuf_get_pinned 将 VRAM 映射为 RDMA 可 DMA 的地址 → mlx5_ib_reg_user_mr_dmabuf 编程 HCA IOMMU → HCA 通过 PCIe P2P 直接读写 GPU VRAM。

0 次 CPU 拷贝,不是魔法——是精心设计的 Linux 内核子系统协同工作的结果。


💬 评论