3. DMA掩码设置与一致性检查

DMA掩码设置与一致性检查 学习卡片

任务说明

在 PCIe 设备驱动的 probe 阶段,用 dma_set_mask_and_coherent() 一次性设置流式 DMA 掩码与一致性 DMA 掩码,并通过返回值判断设备是否支持该寻址宽度。本案例给出一个最小可加载的 PCI 驱动,probe 时依次尝试 64 位、32 位掩码,成功后打印实际生效的掩码宽度。

关键点

  • dma_set_mask_and_coherent(dev, mask) 内部等价于先 dma_set_mask() 再 dma_set_coherent_mask(),任一失败即返回错误。
  • 掩码是"地址位宽"的位图:DMA_BIT_MASK(64) 表示支持 64 位寻址,DMA_BIT_MASK(32) 表示 32 位。
  • 返回 0 表示设置成功,负值表示设备不支持该宽度,需回退到更小掩码。
  • 设置成功后可用 dma_get_mask(dev) 读回当前生效掩码,用于一致性检查。

驱动代码

// dma_mask_demo.c
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/dma-mapping.h>

#define DRV_NAME "dma_mask_demo"

static int dma_mask_demo_probe(struct pci_dev *pdev,
                               const struct pci_device_id *id)
{
    int ret;

    ret = pcim_enable_device(pdev);
    if (ret)
        return ret;

    /* 优先尝试 64 位寻址 */
    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
    if (ret) {
        dev_info(&pdev->dev, "64-bit DMA not supported, fallback to 32-bit\n");
        ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
        if (ret) {
            dev_err(&pdev->dev, "no usable DMA mask\n");
            return ret;
        }
    }

    /* 一致性检查:读回生效掩码 */
    dev_info(&pdev->dev, "DMA mask set, coherent mask = 0x%llx\n",
             (unsigned long long)dma_get_mask(&pdev->dev));

    return 0;
}

static void dma_mask_demo_remove(struct pci_dev *pdev)
{
    dev_info(&pdev->dev, "dma_mask_demo removed\n");
}

static const struct pci_device_id dma_mask_demo_ids[] = {
    { PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x100e) }, /* 示例:e1000 */
    { }
};
MODULE_DEVICE_TABLE(pci, dma_mask_demo_ids);

static struct pci_driver dma_mask_demo_driver = {
    .name     = DRV_NAME,
    .id_table = dma_mask_demo_ids,
    .probe    = dma_mask_demo_probe,
    .remove   = dma_mask_demo_remove,
};
module_pci_driver(dma_mask_demo_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PCIe DMA mask set and consistency check demo");

构建与运行

# Makefile
obj-m += dma_mask_demo.o
KDIR ?= /lib/modules/$(shell uname -r)/build

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
make
sudo insmod dma_mask_demo.ko
dmesg | tail -n 20
sudo rmmod dma_mask_demo

验证方法

  • 加载后 dmesg 应出现 DMA mask set, coherent mask = 0x...,说明掩码设置成功且读回一致。
  • 若设备仅支持 32 位,会先打印 64-bit DMA not supported, fallback to 32-bit,随后仍打印成功行。
  • 若两种掩码都失败,会打印 no usable DMA mask,probe 返回错误,模块加载失败。
  • 通过条件:dma_set_mask_and_coherent 返回 0,且 dma_get_mask 读回值与设置值一致。

常见问题

  • 设备 ID 需替换为实际硬件:用 lspci -nn 查看厂商/设备号,修改 PCI_DEVICE() 参数。
  • 若驱动已被内核自带模块占用,需先 rmmod 原驱动或使用 driver_override 绑定。
  • 掩码设置必须在申请 DMA 缓冲区之前完成,否则后续映射可能失败。