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

时间:2013-11-13 11:46:53

标签: linux-kernel linux-device-driver dma

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

    int rc;
    struct device dev;

    dev_set_name(&dev, "mydev");
    if ((rc = device_register(&dev)) != 0)
        goto fail;

    char *kbuf = kmalloc(size, GFP_KERNEL);
    dma_addr_t handle = dma_map_single(&dev, kbuf, size, direction);

    // ... further code omitted

问题是dma_map_single()会产生一个oops,它告诉我有一个尝试在函数内部的某处取消引用空指针。我认为这是由于结构设备初始化不充分引起的,但我没有找到关于正确的struct device设置(对于DMA)的解释。 device_register()返回成功。

非常感谢任何有关如何解决问题的提示。

3 个答案:

答案 0 :(得分:2)

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

我做了一些进一步的测试,尝试以struct device接受的方式设置dma_map_single() ...导致内核恐慌。日志显示恐慌是由lib / swiotlb_map_page.c引起的(我也忘了提到我的硬件平台是x86_64)。我研究了源代码并发现了以下内容。

如果提供给struct device的{​​{1}}没有设置dma_map_single(),则底层代码将假定您的内核缓冲区已映射到的总线地址是“不是DMA” '(它调用dma_capable(),它将最高的映射地址与掩码进行比较)。如果映射的地址范围不支持DMA,则尝试使用设备可以访问的反弹缓冲区,但由于未设置掩码,因此该函数认为反弹缓冲区不是DMA可用的和恐慌。

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

我还注意到x86特定代码对某些请求使用“回退”设备结构。有关详细信息,请参见arch / x86 / kernel / pci-dma.c。从本质上讲,结构将dma_mask设置为某个值,而coherent_dma_mask只是设置为指向dma_mask

我在此回退结构之后对我的设备结构进行了建模,最后让coherent_dma_mask工作。更新后的代码如下所示:

dma_map_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); } 的技巧可能不是可移植的,但可以在我的x86_64和2.6.32 / 35内核上运行,因此如果他们想要尝试使用映射API,其他人可能会发现它很有用。没有物理设备就无法进行传输,但是我能够在调用struct device之后检查dma_map_single()生成并访问缓冲区的总线地址,所以我认为值得研究。

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

答案 1 :(得分:0)

您应该使用struct device的指定初始化[1]。这将保证未明确设置的所有成员将被清除为零。

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

dev_set_name(&dev, "mydev");

Linux device drivers本书,p382表示以下内容:

  

至少必须设置parent,bus_id,bus和release字段   在设备结构可以注册之前。

最简单的方法是仔细阅读现有驱动程序的代码,以确定新驱动程序如何适应现有的系统和设备结构。

[1] http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf,第6.7.8.21节

答案 2 :(得分:0)

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

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