3. 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 缓冲区之前完成,否则后续映射可能失败。