Linux kernel dma_map_single():对结构设备的最低要求

Linux kernel dma_map_single():对结构设备的最低要求,linux-kernel,linux-device-driver,dma,Linux Kernel,Linux Device Driver,Dma,我一直在尝试在我的模块(内核版本2.6.32、2.6.35)中使用以下简单的示例代码: 问题是dma_map_single()会导致oops,这告诉我有人试图在函数内部的某个地方取消对空指针的引用。我认为这是由于结构设备初始化不足造成的,但我没有找到关于正确的结构设备设置(用于DMA)的解释。device_register()返回成功 对于如何解决此问题的任何提示,我们将不胜感激。您应该使用结构设备的指定初始化[1]。这将保证所有未显式设置的成员都将被清除为零 struct device dev

我一直在尝试在我的模块(内核版本2.6.32、2.6.35)中使用以下简单的示例代码:

问题是dma_map_single()会导致oops,这告诉我有人试图在函数内部的某个地方取消对空指针的引用。我认为这是由于结构设备初始化不足造成的,但我没有找到关于正确的结构设备设置(用于DMA)的解释。device_register()返回成功


对于如何解决此问题的任何提示,我们将不胜感激。

您应该使用
结构设备的指定初始化[1]。这将保证所有未显式设置的成员都将被清除为零

struct device dev = {
    .parent = aaa,
    .bus_id = bbb,
    .bus = ccc,
    .release = ddd
};

dev_set_name(&dev, "mydev");
本书第382页指出了以下内容:

至少必须设置父项、总线id、总线和释放字段 在设备结构可以注册之前

阅读现有驱动程序的代码以确定新驱动程序如何适合现有系统和设备结构是最容易的


[1] ,第6.7.8.21节

dma\u map\u single
返回dma地址,该地址是设备连接到的总线上的地址。 换句话说,DMA地址是相对于总线的,没有总线没有任何意义


如果没有一些真正的设备(已由相应的总线代码初始化),您就无法进行DMA。

如果我对该代码的用途不够清楚,我深表歉意。我只是想尝试流式DMA API,所以我需要能够简单地映射/取消映射内核内存缓冲区(并尝试从CPU访问它)

我做了一些进一步的测试,试图以
dma\u map\u single()
可以接受的方式设置
struct设备。。。这导致了内核恐慌。日志显示恐慌是由lib/swiotlb_map_page.c引起的(我还忘了提到我的硬件平台是x86_64)。我研究了源代码,发现了以下内容

如果提供给
dma_map_single()
struct设备未设置其
dma_掩码
,则底层代码将假定内核缓冲区映射到的总线地址是“不可dma”的(它调用dma_-capable(),将最高映射地址与掩码进行比较)。如果映射的地址范围不支持DMA,则会尝试使用设备可以访问的反弹缓冲区,但由于未设置掩码,因此函数会得出结论,反弹缓冲区也不可DMA,并且会崩溃

请注意,
dma_mask
是指向u64的指针,因此为了使用有意义的值,您应该为它设置一个存储空间。还要注意的是,虽然dma_set_mask设置了掩码值,但它没有为其分配存储。如果
dma_mask
为空,则相当于将掩码设置为零(相关代码在取消引用指针之前检查dma_mask是否为空)

我还注意到,特定于x86的代码对某些请求使用“回退”设备结构。有关详细信息,请参阅arch/x86/kernel/pci dma.c。本质上,该结构将其
coherent\u dma\u mask
设置为某个值,而
dma\u mask
仅设置为指向
coherent\u dma\u mask

在这个回退结构之后,我对我的设备结构进行了建模,并最终使
dma\u map\u single()
正常工作。更新后的代码如下所示:

    static struct device dev = {
        .init_name = "mydmadev",
        .coherent_dma_mask = ~0,             // dma_alloc_coherent(): allow any address
        .dma_mask = &dev.coherent_dma_mask,  // other APIs: use the same mask as coherent
    };

    static void map_single(void) {
        char *kbuf = kmalloc(size, GFP_KERNEL | GFP_DMA);
        dma_addr_t dma_addr = dma_map_single(&dev, kbuf, size, direction);

        if (dma_mapping_error(&dev, dma_addr)) {
           pr_info("dma_map_single() failed\n");
           kfree(kbuf);
           goto fail;
        } else {
            pr_info("dma_map_single() succeeded");
        }

        // the device can be told to access the buffer at dma_addr ...

        // get hold of the buffer temporarily to do some reads/writes
        dma_sync_single_for_cpu(&dev, dma_addr, size, direction);

        // release the buffer to the device again
        dma_sync_single_for_device(&dev, dma_addr, size, direction);

        // some further device I/O...

        // done with the buffer, unmap and free
        dma_unmap_single(&dev, dma_addr, size, direction);

        // check/store buffer contents...

        // free the buffer
        kfree(kbuf);
    }
当然,
struct device
的技巧可能不是可移植的,但在我的x86_64和2.6.32/35内核上确实有效,因此其他人可能会发现它很有用,如果他们想尝试映射API的话。如果没有物理设备,传输是不可能的,但是我能够检查dma\U map\u single()生成的总线地址,并在调用
dma\u sync\u single\u for\u cpu()
后访问缓冲区,因此我认为这是值得研究的


非常感谢你的回答。欢迎对上述代码提出任何进一步的建议/改进。

我忘了提到我已经将.release成员设置为我的release函数。自LDD编写以来,总线id已被删除。我的问题是,我没有物理设备,现在只需要创建一个存根来尝试此映射方法。我还担心问题与默认映射属性(由dma_map_single()隐式使用)或虚拟地址未在缓存线边界上对齐(我请求2*页大小字节)有关。我知道,没有设备,我无法进行任何真正的dma传输(也不能进行与进程相关的中断等)我只是想试验一下映射API,也就是说,获得一个“支持DMA”的缓冲区及其总线地址。真正的设备驱动程序包含许多其他样板代码,这些代码与映射调用混合在一起(当然,这是不可避免的)。我所要做的就是获得一个精简的代码片段来说明正确的映射。
    static struct device dev = {
        .init_name = "mydmadev",
        .coherent_dma_mask = ~0,             // dma_alloc_coherent(): allow any address
        .dma_mask = &dev.coherent_dma_mask,  // other APIs: use the same mask as coherent
    };

    static void map_single(void) {
        char *kbuf = kmalloc(size, GFP_KERNEL | GFP_DMA);
        dma_addr_t dma_addr = dma_map_single(&dev, kbuf, size, direction);

        if (dma_mapping_error(&dev, dma_addr)) {
           pr_info("dma_map_single() failed\n");
           kfree(kbuf);
           goto fail;
        } else {
            pr_info("dma_map_single() succeeded");
        }

        // the device can be told to access the buffer at dma_addr ...

        // get hold of the buffer temporarily to do some reads/writes
        dma_sync_single_for_cpu(&dev, dma_addr, size, direction);

        // release the buffer to the device again
        dma_sync_single_for_device(&dev, dma_addr, size, direction);

        // some further device I/O...

        // done with the buffer, unmap and free
        dma_unmap_single(&dev, dma_addr, size, direction);

        // check/store buffer contents...

        // free the buffer
        kfree(kbuf);
    }