Skip to the content.

SWIOTLB 常见疑问整理(结合最新上游代码)

1. SWIOTLB 到底是什么?

SWIOTLB(software I/O TLB)是一个软件 bounce buffer 机制。

当设备因为以下原因无法直接访问某段物理内存时,内核会把数据先复制到一块预留的、设备能够访问的连续低地址内存中,再把这块内存的地址交给设备做 DMA:

1.1 与 IOMMU 的核心区别

机制 做法 对设备地址的作用
IOMMU(翻译模式) 在 IOMMU 页表中把 IOVA 映射到物理页 设备拿到的是 IOVA,可大于 dma_mask;IOMMU 负责翻译和保护
SWIOTLB 把原数据复制到预留的 bounce buffer,再给设备物理地址 设备直接访问 bounce buffer;靠复制保证数据一致性

也就是说:IOMMU 是地址翻译 + 隔离;SWIOTLB 是数据复制 + 兜底。


3. 为什么开了 IOMMU 还是看到 swiotlb_map_*

这是原文档里反复出现的疑问。常见原因有三类。

3.1 iommu=pt / 默认 passthrough

iommu=pt(或 iommu.passthrough=1)只是把 IOMMU 配成 identity(1:1)映射, 并没有给设备提供 IOVA 翻译。此时 DMA 路径退化成 direct mapping:

// kernel/dma/mapping.c
dma_map_direct(dev, ops)        /* true when use_dma_iommu(dev) == false */
  -> dma_direct_map_phys()

如果设备 dma_mask 较小(如 32 位),物理内存又超过 4GB,就会触发 swiotlb。 原文档中 Intel 机器在 iommu=pt intel_iommu=on 下出现 swiotlb_map_sg_attrs,Hygon 机器默认也是同样原因。

3.2 IOMMU 翻译模式下设备 untrusted 或缓冲区未对齐

见 2.2。IOMMU 页表通常按 4KB/64KB 等粒度管理,如果驱动提交的缓冲区起始地址或长度没有对齐到该粒度,内核会先把它 bounce 到对齐的 bounce buffer,再用 IOMMU 映射这个 bounce buffer。

3.3 根本没有 IOMMU

如果 BIOS/UEFI 没有暴露 IOMMU,或者命令行 iommu=offuse_dma_iommu(dev) 为 false,直接走 direct mapping,同样会因为 dma_mask 限制触发 swiotlb。


4. SWIOTLB 的“保护”体现在哪里?

原文档提到 dev_is_untrusted()dev_use_swiotlb(),疑惑 swiotlb 和 IOMMU 的保护关系。这里要区分两层含义:

  1. 防止设备看到不该看的数据
    • 在 IOMMU 翻译模式下,真正隔离由 IOMMU 完成:设备只能访问分配给它的 IOVA。
    • 对于 untrusted 设备,如果直接映射一个未对齐的 buffer,IOMMU 必须映射更多页面,padding 里可能包含内核数据。swiotlb 先把数据复制到独立的 bounce buffer,并把 padding 清零,再让 IOMMU 映射这个干净的 bounce buffer。
    • 因此:swiotlb 不是替代 IOMMU,而是配合 IOMMU 处理未对齐 / untrusted 的情况。
  2. 限制设备只能访问 bounce buffer
    • 在 direct mapping 场景下,设备收到的 DMA 地址是 bounce buffer 的物理地址,而不是原缓冲区的地址。设备在一次 DMA 中只能读到/写到这块 bounce buffer。
    • 但它并不像 IOMMU 那样提供长期的、细粒度的地址空间隔离。

所以:swiotlb 提供的是“受限的、临时的数据隔离 + 地址可访问性兜底”,不是完整的 IOMMU 保护。


5. 为什么 AMD 要“Enable swiotlb in all cases”?

原文档引用的 commit 121660bba631 ("iommu/amd: Enable swiotlb in all cases") 的背景是:

在较新的内核里,这个逻辑已经被泛化到 drivers/iommu/dma-iommu.cdev_use_swiotlb()

因此,不是“AMD 特殊”,而是“任何 untrusted 设备在 IOMMU 翻译模式下做未对齐 DMA 时都会 bounce”。


6. 为什么飞腾(Phytium)默认走 swiotlb_map_sg_attrs

原文档里飞腾的 /sys/kernel/iommu_groups/*/type 全部是 identity,而 Apple/ Kunpeng 是 DMA(翻译)。根本原因是:

在最新上游 ARM64 代码里,arch_setup_dma_ops() 已经大幅简化,不再显式设置 arm64_swiotlb_dma_ops,而是统一走 dma-direct + IOMMU 框架。默认 domain 类型由 iommu_def_domain_type 决定:

// drivers/iommu/iommu.c
static int __init iommu_subsys_init(void)
{
    ...
    if (IS_ENABLED(CONFIG_IOMMU_DEFAULT_PASSTHROUGH))
        iommu_set_default_passthrough(false);
    else
        iommu_set_default_translated(false);
    ...
}

如果内核/命令行把默认 domain 设为 passthrough,行为就和原文档里飞腾的情况一样。


7. 初始化、内存占用与日志

7.1 启动时预留内存

// kernel/dma/swiotlb.c
#define IO_TLB_SHIFT 11              /* 每个 slot 2KB */
#define IO_TLB_SIZE (1 << IO_TLB_SHIFT)
#define IO_TLB_SEGSIZE 128           /* 每段 128 个 slot = 256KB */
#define IO_TLB_DEFAULT_SIZE (64UL<<20)

默认大小 64MB,按 2KB slot 切分,并按 CPU 数划分 area(default_nareas 默认取 num_possible_cpus()),每个 area 有独立自旋锁以减少并发冲突。

初始化入口:

7.2 那条启动日志

[    0.365146] software IO TLB: mapped [mem 0x00000000a9000000-0x00000000ad000000] (64MB)

来自 kernel/dma/swiotlb.c:swiotlb_print_info()

void swiotlb_print_info(void)
{
    ...
    pr_info("mapped [mem %pa-%pa] (%luMB)\n", &mem->start, &mem->end,
           (mem->nslabs << IO_TLB_SHIFT) >> 20);
}

area num 128 那条日志来自 swiotlb_adjust_nareas()

7.3 运行期观察

# 1. 是否分配成功
dmesg | grep "software IO TLB"

# 2. debugfs 统计
cat /sys/kernel/debug/swiotlb/io_tlb_used
cat /sys/kernel/debug/swiotlb/io_tlb_nslabs
cat /sys/kernel/debug/swiotlb/io_tlb_used_hiwater

# 3. 查看设备 dma mask
for dev in /sys/bus/pci/devices/*; do
    [ -f "$dev/dma_mask_bits" ] && \
        echo "$(basename $dev): $(cat $dev/dma_mask_bits) bits"
done

# 4. ftrace 看 swiotlb 路径
sudo perf ftrace -G swiotlb_map -- sleep 1

8. Dynamic SWIOTLB(LWN 940973)

从 6.6 左右开始,上游引入了 CONFIG_SWIOTLB_DYNAMIC,目标是解决“64MB 固定池在嵌入式系统太大、在全量 bounce 场景又太小”的问题。

8.1 动态增长

当默认池找不到 slot 时:

// kernel/dma/swiotlb.c:swiotlb_find_slots()
if (!mem->can_grow)
    return -1;

schedule_work(&mem->dyn_alloc);      /* 异步扩容默认池 */

/* 同时立即分配一个 transient pool 应急 */
pool = swiotlb_alloc_pool(dev, nslabs, nslabs, 1, phys_limit, GFP_NOWAIT);
...
pool->transient = true;
list_add_rcu(&pool->node, &dev->dma_io_tlb_pools);

8.2 数据结构变化

// include/linux/swiotlb.h
struct io_tlb_mem {
    struct io_tlb_pool defpool;
    unsigned long nslabs;
    ...
#ifdef CONFIG_SWIOTLB_DYNAMIC
    bool can_grow;
    u64 phys_limit;
    spinlock_t lock;
    struct list_head pools;          /* 全局动态池 */
    struct work_struct dyn_alloc;
#endif
    ...
};

每个 struct device 也新增了:

struct io_tlb_mem *dma_io_tlb_mem;
#ifdef CONFIG_SWIOTLB_DYNAMIC
struct list_head dma_io_tlb_pools;   /* 该设备的 transient pools */
spinlock_t dma_io_tlb_lock;
bool dma_uses_io_tlb;
#endif

相关代码在 kernel/dma/swiotlb.c:559-813include/linux/swiotlb.h:70-126

更多背景可参考 LWN 文章:More dynamic IOTLB


9. 为什么“不就是多拷贝一次”这个想法不完整?

原文档里有一句:“无非就是多拷贝了一次,岂不是需要 swiotlb 提前分配一个 buffer 过去?”

关键点:

所以 swiotlb 的代价 = 预留内存 + CPU 复制 + 额外的同步(cache flush),是最后的兜底手段。


10. 小结

问题 答案
SWIOTLB 是干什么的? 软件 bounce buffer,把数据复制到设备可访问的低地址连续内存。
什么时候触发? dma_mask 不足、IOMMU passthrough/无 IOMMU、强制 bounce(内存加密/swiotlb=force)、untrusted 设备未对齐 DMA、kmalloc 不对齐。
开了 IOMMU 为什么还有 swiotlb? iommu=pt 没有翻译;翻译模式下 untrusted/未对齐也会 bounce。
SWIOTLB 能替代 IOMMU 保护吗? 不能。它只能限制设备看到 bounce buffer,并配合 IOMMU 清零 padding;完整隔离需要 IOMMU 翻译模式。
AMD 为什么 always swiotlb? 为 untrusted 设备的未对齐 DMA 保留 bounce 能力;现代代码已泛化为 dev_use_swiotlb()
飞腾为什么默认 swiotlb? SMMU 默认 identity domain,没有 IOVA 翻译,设备 mask 小则 bounce。
64MB 日志是哪来的? swiotlb_print_info(),默认 IO_TLB_DEFAULT_SIZE
最新代码有什么变化? dma_direct_map_page 改为 dma_direct_map_phys;引入 CONFIG_SWIOTLB_DYNAMICdev_use_swiotlb 集中到 dma-iommu.c;新增 CONFIG_DMA_BOUNCE_UNALIGNED_KMALLOC

11. 主要参考文件

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。