2

我一直在尝试使以下简单的示例代码在我的模块中工作(内核版本 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,它告诉我在函数内部的某处尝试取消引用空指针。我认为这是由于结构设备初始化不足引起的,但我没有找到有关正确结构设备设置(对于 DMA)的解释。device_register() 返回成功。

任何有关如何解决问题的提示将不胜感激。

4

3 回答 3

2

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

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

如果struct device提供的 todma_map_single()没有dma_mask设置,则底层代码将假定您的内核缓冲区已映射到的总线地址不是 DMA'ble(它调用 dma_capable() 将最高映射地址与掩码进行比较)。如果映射的地址范围不支持 DMA,则尝试使用设备可能可访问的反弹缓冲区,但由于未设置掩码,函数得出结论,反弹缓冲区也不支持 DMA和恐慌。

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

我还注意到特定于 x86 的代码对某些请求使用“后备”设备结构。有关详细信息,请参见 arch/x86/kernel/pci-dma.c。本质上,该结构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);
    }

当然,这个技巧struct device可能无法移植,但在我的 x86_64 和 2.6.32/35 内核上确实有效,所以如果其他人想尝试映射 API,他们可能会发现它很有用。如果没有物理设备,传输是不可能的,但我能够检查 dma_map_single() 生成的总线地址并在调用后访问缓冲区dma_sync_single_for_cpu(),所以我认为值得研究。

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

于 2013-11-14T11:08:55.970 回答
0

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

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

于 2013-11-14T08:53:24.550 回答
0

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

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

dev_set_name(&dev, "mydev");

Linux 设备驱动程序手册 p382 指出以下内容:

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

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

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

于 2013-11-13T12:16:01.797 回答