Linux 6.6内核 IOMMU 深度解析(七):DMA API 与 IOMMU 集成 — 从 dma_map_single 到 iommu_map

〇、全景:驱动"不知不觉"用上了 IOMMU

第 1 篇(dma-remapping)讲了 IOMMU 硬件 怎么翻译地址:设备发出 IOVA,IOMMU 查页表翻译成物理地址(HPA)。但有个问题没回答------这张页表是谁、在什么时候建起来的?

答案是:设备驱动调 DMA API(dma_map_single 之类)时,内核在背后建好的。驱动压根不用关心底层是 IOMMU 还是"直接映射",它只调一个统一接口,剩下的路由、页表建立全由内核代劳。

这一篇就讲这条软件调用链 ------从驱动的一句 dma_map_single,一路走到 IOMMU 页表的 iommu_map
#mermaid-svg-mKQXCE3CfDXSxuvU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mKQXCE3CfDXSxuvU .error-icon{fill:#552222;}#mermaid-svg-mKQXCE3CfDXSxuvU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mKQXCE3CfDXSxuvU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mKQXCE3CfDXSxuvU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mKQXCE3CfDXSxuvU .marker.cross{stroke:#333333;}#mermaid-svg-mKQXCE3CfDXSxuvU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mKQXCE3CfDXSxuvU p{margin:0;}#mermaid-svg-mKQXCE3CfDXSxuvU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU .cluster-label text{fill:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU .cluster-label span{color:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU .cluster-label span p{background-color:transparent;}#mermaid-svg-mKQXCE3CfDXSxuvU .label text,#mermaid-svg-mKQXCE3CfDXSxuvU span{fill:#333;color:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU .node rect,#mermaid-svg-mKQXCE3CfDXSxuvU .node circle,#mermaid-svg-mKQXCE3CfDXSxuvU .node ellipse,#mermaid-svg-mKQXCE3CfDXSxuvU .node polygon,#mermaid-svg-mKQXCE3CfDXSxuvU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mKQXCE3CfDXSxuvU .rough-node .label text,#mermaid-svg-mKQXCE3CfDXSxuvU .node .label text,#mermaid-svg-mKQXCE3CfDXSxuvU .image-shape .label,#mermaid-svg-mKQXCE3CfDXSxuvU .icon-shape .label{text-anchor:middle;}#mermaid-svg-mKQXCE3CfDXSxuvU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mKQXCE3CfDXSxuvU .rough-node .label,#mermaid-svg-mKQXCE3CfDXSxuvU .node .label,#mermaid-svg-mKQXCE3CfDXSxuvU .image-shape .label,#mermaid-svg-mKQXCE3CfDXSxuvU .icon-shape .label{text-align:center;}#mermaid-svg-mKQXCE3CfDXSxuvU .node.clickable{cursor:pointer;}#mermaid-svg-mKQXCE3CfDXSxuvU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mKQXCE3CfDXSxuvU .arrowheadPath{fill:#333333;}#mermaid-svg-mKQXCE3CfDXSxuvU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mKQXCE3CfDXSxuvU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mKQXCE3CfDXSxuvU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mKQXCE3CfDXSxuvU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mKQXCE3CfDXSxuvU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mKQXCE3CfDXSxuvU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mKQXCE3CfDXSxuvU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mKQXCE3CfDXSxuvU .cluster text{fill:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU .cluster span{color:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mKQXCE3CfDXSxuvU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mKQXCE3CfDXSxuvU rect.text{fill:none;stroke-width:0;}#mermaid-svg-mKQXCE3CfDXSxuvU .icon-shape,#mermaid-svg-mKQXCE3CfDXSxuvU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mKQXCE3CfDXSxuvU .icon-shape p,#mermaid-svg-mKQXCE3CfDXSxuvU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mKQXCE3CfDXSxuvU .icon-shape .label rect,#mermaid-svg-mKQXCE3CfDXSxuvU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mKQXCE3CfDXSxuvU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mKQXCE3CfDXSxuvU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mKQXCE3CfDXSxuvU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是(dev->dma_ops = iommu_dma_ops)
否(direct)
驱动

dma_map_single(dev, buf, size, dir)
dma_map_page_attrs

mapping.c:147
get_dma_ops(dev)

设备挂了 IOMMU 吗?
iommu_dma_map_page

dma-iommu.c:1029
dma_direct_map_page

直接返回物理地址
__iommu_dma_map

dma-iommu.c:744
iommu_dma_alloc_iova

分配 IOVA
iommu_map

建 IOMMU 页表
__iommu_map_pages

ops->map_pages(intel/amd)

一句话主线:DMA API 是统一入口(驱动不关心底层);get_dma_ops 决定走 IOMMU 还是 direct;IOMMU 路径的核心是「分配 IOVA + iommu_map 建页表」------这就是第 1 篇讲的硬件翻译的软件前提。


一、为什么需要 DMA API:统一的设备 DMA 接口

设备要做 DMA(直接访问内存),得先解决一个问题:告诉设备一个"它能用的地址"。但这个地址是什么,取决于有没有 IOMMU:

  • 没有 IOMMU :设备的 DMA 地址 = 物理地址,直接把 virt_to_phys(buf) 给设备即可。
  • 有 IOMMU:设备的 DMA 地址是 IOVA(虚拟地址),得先分配 IOVA、再建页表把它映射到物理地址,才能给设备。

如果让每个驱动自己区分这两种情况,那驱动代码会一团糟。所以内核提供统一的 DMA API :驱动只调 dma_map_single() / dma_map_page(),返回一个 dma_addr_t(DMA 地址),把它写进设备寄存器就行。至于这个地址是物理地址还是 IOVA、要不要建页表,内核通过 dma_map_ops 这个多态接口在背后处理

c 复制代码
// 驱动的视角:不管底层,统一这么写
dma_addr_t dma = dma_map_single(dev, buf, size, DMA_TO_DEVICE);
writel(dma, dev->regs + DMA_ADDR_REG);   // 把 dma 地址写给设备
...
dma_unmap_single(dev, dma, size, DMA_TO_DEVICE);

二、入口:dma_map_single → dma_map_page_attrs

dma_map_single 其实是宏(include/linux/dma-mapping.h:412),包装了带 attrs 的版本:

c 复制代码
// include/linux/dma-mapping.h:412
#define dma_map_single(d, a, s, r) dma_map_single_attrs(d, a, s, r, 0)

dma_map_single_attrsdma-mapping.h:328)做两件事:检查内存合法性,然后把"CPU 虚拟地址"转成"page + offset"交给 dma_map_page_attrs

c 复制代码
// include/linux/dma-mapping.h:328
static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,
        size_t size, enum dma_data_direction dir, unsigned long attrs)
{
    if (dev_WARN_ONCE(dev, is_vmalloc_addr(ptr),          // vmalloc 内存不能 DMA
              "rejecting DMA map of vmalloc memory\n"))
        return DMA_MAPPING_ERROR;
    return dma_map_page_attrs(dev, virt_to_page(ptr),     // 虚拟地址 → page
            offset_in_page(ptr), size, dir, attrs);       //        → 页内偏移
}

dma_map_page_attrskernel/dma/mapping.c:147)是真正的分发点:

c 复制代码
// kernel/dma/mapping.c:147
dma_addr_t dma_map_page_attrs(struct device *dev, struct page *page,
        size_t offset, size_t size, enum dma_data_direction dir,
        unsigned long attrs)
{
    const struct dma_map_ops *ops = get_dma_ops(dev);     // ① 取设备的 dma_ops
    dma_addr_t addr;

    BUG_ON(!valid_dma_direction(dir));

    if (dma_map_direct(dev, ops) ||                       // ② 走 direct 吗?
        arch_dma_map_page_direct(dev, page_to_phys(page) + offset + size))
        addr = dma_direct_map_page(dev, page, offset, size, dir, attrs);  // direct:返回物理地址
    else
        addr = ops->map_page(dev, page, offset, size, dir, attrs);        // IOMMU:调 map_page
    ...
    return addr;
}

关键就在这两行:

  1. get_dma_ops(dev) 取设备的 dma_map_ops
  2. dma_map_direct(dev, ops) 判断"要不要绕过 IOMMU"------绕过就 dma_direct_map_page(直接返回物理地址,不建页表);不绕过就 ops->map_page

三、分发:get_dma_ops 的多态

get_dma_opsinclude/linux/dma-map-ops.h:90)极其简单:

c 复制代码
// include/linux/dma-map-ops.h:90
static inline const struct dma_map_ops *get_dma_ops(struct device *dev)
{
    if (dev->dma_ops)
        return dev->dma_ops;          // 设备挂了 IOMMU → 返回 iommu_dma_ops
    return get_arch_dma_ops();        // 没挂 → 返回 arch 默认(direct)
}

dev->dma_ops 是谁设的?在设备挂载 IOMMU 时(第 2 篇 device-attach 的流程里),iommu_setup_dma_opsdma-iommu.c:1630)把它换成了 IOMMU 的实现:

c 复制代码
// drivers/iommu/dma-iommu.c:1630
void iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 dma_limit)
{
    struct iommu_domain *domain = iommu_get_domain_for_dev(dev);
    ...
    if (iommu_is_dma_domain(domain)) {
        iommu_dma_init_domain(domain, dma_base, dma_limit, dev);
        dev->dma_ops = &iommu_dma_ops;    // ← 关键:把 dma_ops 换成 IOMMU 实现
    }
    ...
}

于是整个多态链路就通了:

  • 设备没挂 IOMMU → dev->dma_ops == NULLget_dma_ops 返回 arch 默认的 dma_ops(x86 上通常是 NULL,NULL 就表示 direct)→ dma_map_direct 返回 true → dma_direct_map_page(返回物理地址)。
  • 设备挂了 IOMMU → dev->dma_ops == &iommu_dma_opsdma_map_direct 返回 false → ops->map_page = iommu_dma_map_page

驱动代码一行不用改 ,挂没挂 IOMMU 对驱动完全透明------这就是 dma_map_ops 这个多态接口的价值。


四、iommu_dma_ops:IOMMU 路径的实现

iommu_dma_opsdma-iommu.c:1602)是一整套回调,把 DMA API 的每个操作接到 IOMMU 框架:

c 复制代码
// drivers/iommu/dma-iommu.c:1602
static const struct dma_map_ops iommu_dma_ops = {
    .flags          = DMA_F_PCI_P2PDMA_SUPPORTED,
    .alloc          = iommu_dma_alloc,              // dma_alloc_coherent 走这
    .free           = iommu_dma_free,
    .map_page       = iommu_dma_map_page,           // ← dma_map_single 走这
    .unmap_page     = iommu_dma_unmap_page,
    .map_sg         = iommu_dma_map_sg,             // scatter-gather 走这
    .sync_single_for_cpu    = iommu_dma_sync_single_for_cpu,
    .sync_single_for_device = iommu_dma_sync_single_for_device,
    ...
};

map_page 的实现 iommu_dma_map_pagedma-iommu.c:1029):

c 复制代码
// drivers/iommu/dma-iommu.c:1029
static dma_addr_t iommu_dma_map_page(struct device *dev, struct page *page,
        unsigned long offset, size_t size, enum dma_data_direction dir,
        unsigned long attrs)
{
    phys_addr_t phys = page_to_phys(page) + offset;   // ① 物理地址
    bool coherent = dev_is_dma_coherent(dev);
    int prot = dma_info_to_prot(dir, coherent, attrs);
    struct iommu_domain *domain = iommu_get_dma_domain(dev);
    struct iommu_dma_cookie *cookie = domain->iova_cookie;
    struct iova_domain *iovad = &cookie->iovad;
    dma_addr_t iova, dma_mask = dma_get_mask(dev);
    ...
    if (dev_use_swiotlb(dev, size, dir) &&            // ② 需要 bounce buffer?
        iova_offset(iovad, phys | size)) {
        ...
        phys = swiotlb_tbl_map_single(dev, phys, size, aligned_size, ...);
    }

    if (!coherent && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
        arch_sync_dma_for_device(phys, size, dir);    // ③ 非 coherent:刷缓存

    iova = __iommu_dma_map(dev, phys, size, prot, dma_mask);  // ④ 真正映射
    return iova;
}

它做了四件事,最后落到 __iommu_dma_mapdma-iommu.c:744):

c 复制代码
// drivers/iommu/dma-iommu.c:744
static dma_addr_t __iommu_dma_map(struct device *dev, phys_addr_t phys,
        size_t size, int prot, u64 dma_mask)
{
    struct iommu_domain *domain = iommu_get_dma_domain(dev);
    struct iommu_dma_cookie *cookie = domain->iova_cookie;
    struct iova_domain *iovad = &cookie->iovad;
    size_t iova_off = iova_offset(iovad, phys);   // 物理地址的页内偏移
    dma_addr_t iova;
    ...
    iova = iommu_dma_alloc_iova(domain, size, dma_mask, dev);  // ① 分配 IOVA
    if (!iova)
        return DMA_MAPPING_ERROR;

    if (iommu_map(domain, iova, phys - iova_off, size, prot, GFP_ATOMIC)) {  // ② 建页表
        iommu_dma_free_iova(cookie, iova, size, NULL);
        return DMA_MAPPING_ERROR;
    }
    return iova + iova_off;
}

这就是 IOMMU 路径的核心,两句话:

  1. 分配 IOVAiommu_dma_alloc_iovadma-iommu.c:658)从该 domain 的 iova_domain(一棵红黑树,drivers/iommu/iova.c)里分配一段空闲的 IOVA 区间------这是"设备看到的 DMA 地址"。
  2. 建页表iommu_map(domain, iova, phys, size, ...) 把 IOVA → 物理地址的映射写进 IOMMU 页表------这是"硬件翻译 IOVA→HPA"的依据。

返回给驱动的 dma_addr_t 就是这个 IOVA。驱动把它写进设备寄存器,设备发起 DMA 时用这个 IOVA,IOMMU 查页表翻译成真正的物理地址。


五、iommu_map:建 IOMMU 页表

iommu_mapdrivers/iommu/iommu.c:2505)是 IOMMU 框架的核心 API,往下走到具体驱动的页表操作:

c 复制代码
// drivers/iommu/iommu.c:2505
int iommu_map(struct iommu_domain *domain, unsigned long iova,
              phys_addr_t paddr, size_t size, int prot, gfp_t gfp)
{
    const struct iommu_domain_ops *ops = domain->ops;
    int ret;
    ...
    ret = __iommu_map(domain, iova, paddr, size, prot, gfp);   // 核心映射
    if (ret == 0 && ops->iotlb_sync_map)
        ops->iotlb_sync_map(domain, iova, size);               // 刷 IOTLB
    return ret;
}

__iommu_mapiommu.c:2445)做两件事:对齐检查 + 循环分段映射(因为 iova/paddr/size 可能对不齐,要拆成多段):

c 复制代码
// drivers/iommu/iommu.c:2445(节选)
static int __iommu_map(struct iommu_domain *domain, unsigned long iova,
        phys_addr_t paddr, size_t size, int prot, gfp_t gfp)
{
    ...
    min_pagesz = 1 << __ffs(domain->pgsize_bitmap);   // 最小页大小
    if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) // 三者都要对齐
        return -EINVAL;

    while (size) {                                     // 循环分段
        size_t mapped = 0;
        ret = __iommu_map_pages(domain, iova, paddr, size, prot, gfp, &mapped);
        size -= mapped;
        iova += mapped;
        paddr += mapped;
    }
    ...
}

__iommu_map_pagesiommu.c:2421)是真正碰页表的地方,它会选页大小(这里就是巨页的入口):

c 复制代码
// drivers/iommu/iommu.c:2421
static int __iommu_map_pages(struct iommu_domain *domain, unsigned long iova,
        phys_addr_t paddr, size_t size, int prot, gfp_t gfp, size_t *mapped)
{
    const struct iommu_domain_ops *ops = domain->ops;
    size_t pgsize, count;

    pgsize = iommu_pgsize(domain, iova, paddr, size, &count);  // ① 选页大小

    if (ops->map_pages) {                             // ② 驱动底层实现
        ret = ops->map_pages(domain, iova, paddr, pgsize, count, prot, gfp, mapped);
    } else {
        ret = ops->map(domain, iova, paddr, pgsize, prot, gfp);
        *mapped = ret ? 0 : pgsize;
    }
    return ret;
}

两个关键点:

  1. iommu_pgsize 选页大小 :如果 iova/paddr/size 都对齐到 2MB/1GB,就选大页(pgsize 变大、count 变少),一次映射更大区间------这是 IOMMU 巨页(扩展方向 3)的入口,也是 TLB 效率的关键。
  2. ops->map_pages / ops->map :这是具体 IOMMU 驱动 (Intel VT-d 的 intel_iommu_map_pages、AMD-Vi 的 amd_iommu_map_pages)的实现,真正写硬件页表。

最后 ops->iotlb_sync_mapiommu.c:2519)刷 IOTLB(IOMMU 的 TLB),让新页表生效。


六、与 VFIO 直通的对比

这一篇的 DMA API 路径,和第 6 篇(vfio-passthrough)讲的 VFIO 直通,都最终走到 iommu_map 建页表,但入口完全不同

DMA API 路径 VFIO 直通路径
谁调用 内核驱动(NVMe、网卡等) 用户态(QEMU,通过 ioctl)
入口 dma_map_singleiommu_dma_ops VFIO_IOMMU_MAP_DMA ioctl → vfio_iommu_map
IOVA 分配 iommu_dma_alloc_iova(iova_domain 红黑树) 用户态指定(QEMU 自己管理 guest 地址空间)
物理页来源 page_to_phys(内核分配的页) pin_user_pages(pin 住用户态/guest 内存)
最终 iommu_map iommu_map

一句话:两者殊途同归,都是 iommu_map 建页表;区别在"谁在什么时候、用什么 IOVA、映射什么物理页"。 DMA API 是内核驱动的默认路径(自动、透明),VFIO 是用户态直通的显式路径(QEMU 全权控制)。


七、完整调用链

复制代码
【DMA API → IOMMU 映射(内核驱动路径)】
驱动:dma_map_single(dev, buf, size, dir)
  └─ dma_map_single_attrs(dev, ptr, size, dir, 0)        dma-mapping.h:328
       ├─ 拒绝 vmalloc 地址
       └─ dma_map_page_attrs(dev, page, offset, ...)     dma-mapping.h:336
            ├─ get_dma_ops(dev)                          dma-map-ops.h:90
            │     └─ dev->dma_ops(= iommu_dma_ops,iommu_setup_dma_ops 设)  dma-iommu.c:1644
            ├─ dma_map_direct(dev, ops)                  mapping.c:141
            │     ├─ true  → dma_direct_map_page(返回物理地址,不翻译)
            │     └─ false → ops->map_page = iommu_dma_map_page  dma-iommu.c:1612
            └─ iommu_dma_map_page                        dma-iommu.c:1029
                 ├─ phys = page_to_phys(page) + offset
                 ├─ swiotlb bounce(非对齐/受限 dma_mask)
                 ├─ arch_sync_dma_for_device(非 coherent 刷缓存)
                 └─ __iommu_dma_map                        dma-iommu.c:744
                      ├─ iommu_dma_alloc_iova(分配 IOVA,红黑树)  dma-iommu.c:658
                      └─ iommu_map(domain, iova, phys, size)      iommu.c:2505
                           ├─ __iommu_map(对齐检查 + 循环分段)    iommu.c:2445
                           │     └─ __iommu_map_pages              iommu.c:2421
                           │           ├─ iommu_pgsize(选页大小,巨页入口)
                           │           └─ ops->map_pages(intel/amd 写页表)
                           └─ ops->iotlb_sync_map(刷 IOTLB)        iommu.c:2519

【VFIO 直通路径(对比,第 6 篇)】
QEMU:VFIO_IOMMU_MAP_DMA ioctl
  └─ vfio_iommu_map → pin_user_pages(pin 住内存)→ iommu_map(同上)

八、关键函数 / 文件索引

函数 位置 作用
dma_map_single dma-mapping.h:412 DMA API 入口(宏)
dma_map_single_attrs dma-mapping.h:328 虚拟地址 → page + offset
dma_map_page_attrs mapping.c:147 分发点(direct vs IOMMU)
get_dma_ops dma-map-ops.h:90 取设备的 dma_ops
dma_map_direct mapping.c:141 判断是否绕过 IOMMU
iommu_setup_dma_ops dma-iommu.c:1630 设 dev->dma_ops = iommu_dma_ops
iommu_dma_ops dma-iommu.c:1602 IOMMU 的 dma_map_ops 实现
iommu_dma_map_page dma-iommu.c:1029 map_page 实现
__iommu_dma_map dma-iommu.c:744 分配 IOVA + iommu_map
iommu_dma_alloc_iova dma-iommu.c:658 分配 IOVA
iommu_map iommu.c:2505 建 IOMMU 页表
__iommu_map iommu.c:2445 对齐检查 + 循环分段
__iommu_map_pages iommu.c:2421 选页大小 + 驱动底层映射
文件(v6.6) 关键内容
include/linux/dma-mapping.h DMA API 入口(宏/inline)
include/linux/dma-map-ops.h dma_map_ops 接口、get_dma_ops
kernel/dma/mapping.c DMA API 分发(direct vs ops)
drivers/iommu/dma-iommu.c iommu_dma_ops、IOVA 分配、map_page
drivers/iommu/iommu.c iommu_map、页表操作

九、记住三点

① DMA API 是统一入口,dma_map_ops 是多态关键 :驱动只调 dma_map_single,不知道底层是 IOMMU 还是 direct。get_dma_ops(dma-map-ops.h:90)返回 dev->dma_ops------设备挂 IOMMU 时它被 iommu_setup_dma_ops(dma-iommu.c:1644)设成 iommu_dma_ops,于是走 IOMMU;否则 dma_map_direct 走 direct(返回物理地址)。

② IOMMU 路径的核心 = 「分配 IOVA + iommu_map 建页表」iommu_dma_map_page(dma-iommu.c:1029)先算物理地址、处理 swiotlb/缓存同步,然后 __iommu_dma_map(:744)做两件事------iommu_dma_alloc_iova 分配 IOVA(红黑树)、iommu_map 把 IOVA→物理页写进页表。返回给驱动的 dma_addr_t 就是 IOVA,硬件靠它翻译。

③ 所有建页表都汇到 iommu_map,页大小是巨页入口iommu_map(iommu.c:2505)→ __iommu_map(循环分段)→ __iommu_map_pages(:2421 用 iommu_pgsize 选页大小,对齐 2MB/1GB 则用大页)→ ops->map_pages(intel/amd 写硬件页表)→ iotlb_sync_map 刷 IOTLB。DMA API 和 VFIO 直通殊途同归,都到 iommu_map,区别只在"谁调用、什么 IOVA、什么物理页"。


相关推荐
分支预测失败1 小时前
RISC-V AIA 中断架构实战:从 PLIC 到 APLIC 与 IMSIC 的迁移
linux·后端
GeW1 小时前
RHCE学习拿证计划:锁定核心考点,实现备考效率最大化
linux
snow@li1 小时前
服务器运维:Alibaba Cloud Linux 4 LTS 64位 根目录全景深度解析文章
linux·运维·服务器
实战派K8S&DB1 小时前
《基于 Dify + FastAPI + PyTiDB 搭建大模型驱动的 TiDB 智能运维 Agent》
运维·数据库·分布式·云原生·tidb·fastapi
苏生Susheng2 小时前
【软件实施】Linux企业运维常用命令手册
java·linux·运维·服务器·springboot·springcloud·软件实施
额额额对了2 小时前
Linux 进程间通信(IPC)核心机制:消息队列、共享内存与信号量详解
linux·网络·网络协议
西索斯coding2 小时前
doubao-seed-2.1-turbo 调用一直 401 怎么办?pro 版同样的 Key 却正常——5 分钟排查定位指南
java·服务器·数据库·ai
zx_741484812 小时前
【Linux入门】Linux 安装:VMware Workstation Pro 安装 CentOS 7 保姆级图文教程
linux·centos
myy-learn3 小时前
31-TCP并发
服务器·网络·tcp/ip