DMA pools的使用

辛盛
2023-12-01
前面我们讲申请大于一个page size的时候用dma_alloc_coherent,如果小于一个page size,则可以使用DMA-coherent buffers.
在使用DMA-coherent buffers 之前首先要调用来create buffer.
    struct dma_pool *
    dma_pool_create(const char *name, struct device *dev,
            size_t size, size_t align, size_t alloc);
然后就可以调用dma_pool_alloc 从前面已经create的pool中申请buffer
    void *dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags,
            dma_addr_t *dma_handle);

void *dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags,
                  dma_addr_t *handle)
和dma_pool_alloc 类似,会把申请的memory清零.
buffer 使用完后调用dma_pool_free 来释放buffer
最后调用dma_pool_destroy 来销毁之前申请的pool。
在dma_pool_create 中 可以指定align ,如果没有指定align,最小size是1个byte.
    if (align == 0)
        align = 1;
    else if (align & (align - 1))
        return NULL;

申请的size 最小是4个byte
    if (size == 0)
        return NULL;
    else if (size < 4)
        size = 4;
申请的size 要align,也可以看出调用最大只能申请一个page的size.
    if ((size % align) != 0)
        size = ALIGN(size, align);

    allocation = max_t(size_t, size, PAGE_SIZE);
边界检查,边界必须小于size,且必须是2的指数.
    if (!boundary)
        boundary = allocation;
    else if ((boundary < size) || (boundary & (boundary - 1)))
        return NULL;
原来也是通过kmalloc 申请,然后给retval 赋值.
    retval = kmalloc_node(sizeof(*retval), GFP_KERNEL, dev_to_node(dev));
    if (!retval)
        return retval;

    strlcpy(retval->name, name, sizeof(retval->name));

    retval->dev = dev;

    INIT_LIST_HEAD(&retval->page_list);
    spin_lock_init(&retval->lock);
    retval->size = size;
    retval->boundary = boundary;
    retval->allocation = allocation;

    INIT_LIST_HEAD(&retval->pools);
如果dev->dma_pools 为null,则说明是第一次申请
    if (list_empty(&dev->dma_pools))
        empty = true;
    list_add(&retval->pools, &dev->dma_pools);

如果是第一次申请,则创建sysfs attribute

    if (empty) {
        int err;

        err = device_create_file(dev, &dev_attr_pools);
        if (err) {
            mutex_lock(&pools_lock);
            list_del(&retval->pools);
            mutex_unlock(&pools_lock);
            mutex_unlock(&pools_reg_lock);
            kfree(retval);
            return NULL;
        }
    }
创建sysfs attribute,主要是调用下面的函数.
show_pools(struct device *dev, struct device_attribute *attr, char *buf)
{
    unsigned temp;
    unsigned size;
    char *next;
    struct dma_page *page;
    struct dma_pool *pool;

    next = buf;
    size = PAGE_SIZE;

    temp = scnprintf(next, size, "poolinfo - 0.1\n");
    size -= temp;
    next += temp;

    mutex_lock(&pools_lock);
    list_for_each_entry(pool, &dev->dma_pools, pools) {
        unsigned pages = 0;
        unsigned blocks = 0;

        spin_lock_irq(&pool->lock);
        list_for_each_entry(page, &pool->page_list, page_list) {
            pages++;
            blocks += page->in_use;
        }
        spin_unlock_irq(&pool->lock);

        /* per-pool info, no real statistics yet */
        temp = scnprintf(next, size, "%-16s %4u %4Zu %4Zu %2u\n",
                 pool->name, blocks,
                 pages * (pool->allocation / pool->size),
                 pool->size, pages);
        size -= temp;
        next += temp;
    }
    mutex_unlock(&pools_lock);

    return PAGE_SIZE - size;
}

static DEVICE_ATTR(pools, S_IRUGO, show_pools, NULL);


调用dma_pool_alloc 从已经申请好的pool中查找memroy。
首先查找释放有足够的memory 来申请.
    list_for_each_entry(page, &pool->page_list, page_list) {
        if (page->offset < pool->allocation)
            goto ready;
    }
如果没有的话,调用pool_alloc_page 申请buffer,然后添加到原来的pool中
    page = pool_alloc_page(pool, mem_flags & (~__GFP_ZERO));
    if (!page)
        return NULL;

    spin_lock_irqsave(&pool->lock, flags);

    list_add(&page->page_list, &pool->page_list);
所以调用dma_pool_alloc 肯定会找到pool来申请,如果没有就申请memory再添加到原来的pool中,也算是一种lazy allocation。
    page->in_use++;
    offset = page->offset;
    page->offset = *(int *)(page->vaddr + offset);
    retval = offset + page->vaddr;
    *handle = offset + page->dma;
计算后,最后返回retval,这个就是物理地址。
需要注意的是pool_alloc_page 函数中首先通过kmalloc 来申请,如果failed 就调用dma_alloc_coherent 来申请。
static struct dma_page *pool_alloc_page(struct dma_pool *pool, gfp_t mem_flags)
{
    struct dma_page *page;

    page = kmalloc(sizeof(*page), mem_flags);
    if (!page)
        return NULL;
    page->vaddr = dma_alloc_coherent(pool->dev, pool->allocation,
                     &page->dma, mem_flags);
    if (page->vaddr) {
#ifdef    DMAPOOL_DEBUG
        memset(page->vaddr, POOL_POISON_FREED, pool->allocation);
#endif
        pool_initialise_page(pool, page);
        page->in_use = 0;
        page->offset = 0;
    } else {
        kfree(page);
        page = NULL;
    }
    return page;
}
如果怀疑dma memory没人踩坏可以打开DMAPOOL_DEBUG,复制到现象是就会打印下面的log
WARNING: at /data2/repos/linux-2.6-iommu/lib/dma-debug.c:448
    check_unmap+0x203/0x490()
Hardware name:
forcedeth 0000:00:08.0: DMA-API: device driver frees DMA memory with wrong
    function [device address=0x00000000640444be] [size=66 bytes] [mapped as
single] [unmapped as page]
Modules linked in: nfsd exportfs bridge stp llc r8169
Pid: 0, comm: swapper Tainted: G        W  2.6.28-dmatest-09289-g8bb99c0 #1
Call Trace:
 <IRQ>  [<ffffffff80240b22>] warn_slowpath+0xf2/0x130
 [<ffffffff80647b70>] _spin_unlock+0x10/0x30
 [<ffffffff80537e75>] usb_hcd_link_urb_to_ep+0x75/0xc0
 [<ffffffff80647c22>] _spin_unlock_irqrestore+0x12/0x40
 [<ffffffff8055347f>] ohci_urb_enqueue+0x19f/0x7c0
 [<ffffffff80252f96>] queue_work+0x56/0x60
 [<ffffffff80237e10>] enqueue_task_fair+0x20/0x50
 [<ffffffff80539279>] usb_hcd_submit_urb+0x379/0xbc0
 [<ffffffff803b78c3>] cpumask_next_and+0x23/0x40
 [<ffffffff80235177>] find_busiest_group+0x207/0x8a0
 [<ffffffff8064784f>] _spin_lock_irqsave+0x1f/0x50
 [<ffffffff803c7ea3>] check_unmap+0x203/0x490
 [<ffffffff803c8259>] debug_dma_unmap_page+0x49/0x50
 [<ffffffff80485f26>] nv_tx_done_optimized+0xc6/0x2c0
 [<ffffffff80486c13>] nv_nic_irq_optimized+0x73/0x2b0
 [<ffffffff8026df84>] handle_IRQ_event+0x34/0x70
 [<ffffffff8026ffe9>] handle_edge_irq+0xc9/0x150
 [<ffffffff8020e3ab>] do_IRQ+0xcb/0x1c0
 [<ffffffff8020c093>] ret_from_intr+0x0/0xa
 <EOI> <4>---[ end trace f6435a98e2a38c0e ]---
 类似资料: