This patch makes part of the ARM dmabounce code available to other architectures as a generic API. See included kernel-doc annotations for the actual API implemented. An architecture can opt-in for generic dmabounce support by defining HAVE_DMABOUNCE. This support will be used later to address DMA memory access restrictions on the Nintendo Wii video game console. Signed-off-by: Albert Herranz <albert_herranz@xxxxxxxx> --- arch/Kconfig | 3 + include/linux/dmabounce.h | 77 +++++++++ lib/Kconfig | 10 ++ lib/Makefile | 2 + lib/dmabounce.c | 395 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 487 insertions(+), 0 deletions(-) create mode 100644 include/linux/dmabounce.h create mode 100644 lib/dmabounce.c diff --git a/arch/Kconfig b/arch/Kconfig index 9d055b4..98ff26d 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -130,6 +130,9 @@ config HAVE_CLK config HAVE_DMA_API_DEBUG bool +config HAVE_DMABOUNCE + bool + config HAVE_DEFAULT_NO_SPIN_MUTEXES bool diff --git a/include/linux/dmabounce.h b/include/linux/dmabounce.h new file mode 100644 index 0000000..d60dc04 --- /dev/null +++ b/include/linux/dmabounce.h @@ -0,0 +1,77 @@ +#ifndef _LINUX_DMABOUNCE_H +#define _LINUX_DMABOUNCE_H + +/* FIXME remove later when arch/arm/common/dmabounce.c is updated */ +#ifndef CONFIG_ARM + +#ifdef CONFIG_DMABOUNCE + +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> +#include <linux/list.h> +#include <linux/types.h> +#include <linux/device.h> + +struct dmabounce_info; + +#ifdef CONFIG_DMABOUNCE_STATS +struct dmabounce_stats { + unsigned long total_allocs; + unsigned long map_op_count; + unsigned long bounce_count; +}; +extern struct dmabounce_stats *dmabounce_get_stats(struct dmabounce_info *); +#define DMABOUNCE_DO_STATS(i, X) do { dmabounce_get_stats(i)->X ; } while (0) +#else +#define DMABOUNCE_DO_STATS(i, X) do { } while (0) +#endif /* CONFIG_DMABOUNCE_STATS */ + +struct dmabounce_pool { + unsigned long size; + struct dma_pool *pool; +#ifdef CONFIG_DMABOUNCE_STATS + unsigned long allocs; +#endif +}; + +struct dmabounce_buffer { + struct list_head node; + + /* original buffer */ + void *buf; + size_t size; + enum dma_data_direction dir; + + /* bounced buffer */ + void *bounce_buf; + dma_addr_t bounce_buf_dma; + + struct dmabounce_pool *pool; +}; + +extern struct dmabounce_buffer * +dmabounce_alloc_buffer(struct dmabounce_info *info, + void *buf, size_t size, enum dma_data_direction dir, + gfp_t gfp); +extern void dmabounce_free_buffer(struct dmabounce_info *info, + struct dmabounce_buffer *bb); +extern struct dmabounce_buffer * +dmabounce_find_buffer(struct dmabounce_info *info, dma_addr_t bounce_buf_dma, + size_t size, enum dma_data_direction dir); + +extern struct dmabounce_info * +dmabounce_info_alloc(struct device *dev, + size_t small_buffer_size, size_t large_buffer_size, + size_t align, size_t boundary); +extern void dmabounce_info_free(struct dmabounce_info *info); + +extern int dmabounce_info_register(struct device *dev, + struct dmabounce_info *info); +extern void dmabounce_info_unregister(struct device *dev); + +#endif /* CONFIG_DMABOUNCE */ + +/* FIXME remove later when arch/arm/common/dmabounce.c is updated */ +#endif /* !CONFIG_ARM */ + +#endif /* _LINUX_DMABOUNCE_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 97b136f..b53b7dc 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -207,4 +207,14 @@ config GENERIC_ATOMIC64 config LRU_CACHE tristate +config DMABOUNCE + bool + depends on HAVE_DMABOUNCE + select ZONE_DMA if ARM + default y + +config DMABOUNCE_STATS + bool "Track dmabounce statistics" + depends on DMABOUNCE + endmenu diff --git a/lib/Makefile b/lib/Makefile index 3b0b4a6..097c2ed 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -100,6 +100,8 @@ obj-$(CONFIG_GENERIC_CSUM) += checksum.o obj-$(CONFIG_GENERIC_ATOMIC64) += atomic64.o +obj-$(CONFIG_DMABOUNCE) += dmabounce.o + hostprogs-y := gen_crc32table clean-files := crc32table.h diff --git a/lib/dmabounce.c b/lib/dmabounce.c new file mode 100644 index 0000000..620d314 --- /dev/null +++ b/lib/dmabounce.c @@ -0,0 +1,395 @@ +/* + * lib/dmabounce.c + * + * Generic DMA bounce buffer functions. + * Copyright (C) 2010 Albert Herranz <albert_herranz@xxxxxxxx> + * + * Based on arch/arm/common/dmabounce.c + * + * Original version by Brad Parker (brad@xxxxxxxxxxx) + * Re-written by Christopher Hoover <ch@xxxxxxxxxxxxxx> + * Made generic by Deepak Saxena <dsaxena@xxxxxxxxxxx> + * + * Copyright (C) 2002 Hewlett Packard Company. + * Copyright (C) 2004 MontaVista Software, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +/* FIXME remove later when arch/arm/common/dmabounce.c is updated */ +#ifndef CONFIG_ARM + +#define DRV_MODULE_NAME "dmabounce" +#define DRV_DESCRIPTION "Generic DMA bounce buffer functions" +#define DRV_AUTHOR "Christopher Hoover <ch@xxxxxxxxxx>, " \ + "Deepak Saxena <dsaxena@xxxxxxxxxxx>, " \ + "Albert Herranz <albert_herranz@xxxxxxxx>" + +#define pr_fmt(fmt) DRV_MODULE_NAME ": " fmt +#include <linux/kernel.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/dmabounce.h> + + +struct dmabounce_info { + struct device *dev; + struct list_head bounce_buffers; + +#ifdef CONFIG_DMABOUNCE_STATS + struct dmabounce_stats stats; + int attr_res; +#endif + struct dmabounce_pool small; + struct dmabounce_pool large; + + rwlock_t lock; +}; + +#ifdef CONFIG_DMABOUNCE_STATS +struct dmabounce_stats *dmabounce_get_stats(struct dmabounce_info *info) +{ + return &info->stats; +} + +static ssize_t dmabounce_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dmabounce_info *info = dev->archdata.dmabounce; + return sprintf(buf, "%lu %lu %lu %lu %lu %lu\n", + info->small.allocs, + info->large.allocs, + info->stats.total_allocs - + info->small.allocs - info->large.allocs, + info->stats.total_allocs, + info->stats.map_op_count, + info->stats.bounce_count); +} + +static DEVICE_ATTR(dmabounce_stats, 0400, dmabounce_show, NULL); +#endif /* CONFIG_DMABOUNCE_STATS */ + +/** + * dmabounce_alloc_buffer() - try to allocate a coherent DMA bounce buffer + * @info: This dmabounce_info. + * @buf: Original buffer virtual address. + * @size: Original buffer length. + * @dir: Direction of DMA transfer for this buffer. + * @gfp: Flags used for memory allocations. + * + * Use this function to allocate a coherent buffer of size @size associated + * to a dmabounce_info structure @info. + * The allocation will be performed using the @gfp allocation flags. + * + * The coherent buffer can be used later to bounce data from/to the + * corresponding normal buffer @buf with the specified direction @dir. + * + * If the buffer cannot be allocated the function returns NULL. + * Otherwise, the allocated buffer is returned. + */ +struct dmabounce_buffer * +dmabounce_alloc_buffer(struct dmabounce_info *info, + void *buf, size_t size, enum dma_data_direction dir, + gfp_t gfp) +{ + struct device *dev = info->dev; + struct dmabounce_pool *pool = NULL; + struct dmabounce_buffer *bb; + unsigned long flags; + + dev_dbg(dev, "%s(buf=%p, size=%d, dir=%d)\n", __func__, buf, size, dir); + + if (size <= info->small.size) + pool = &info->small; + else if (size <= info->large.size) + pool = &info->large; + + bb = kzalloc(sizeof(*bb), gfp); + if (!bb) { + dev_err(dev, "%s: kmalloc failed\n", __func__); + goto out; + } + + if (pool) { + bb->bounce_buf = dma_pool_alloc(pool->pool, gfp, + &bb->bounce_buf_dma); + } else { + bb->bounce_buf = dma_alloc_coherent(dev, size, + &bb->bounce_buf_dma, gfp); + } + + if (!bb->bounce_buf) { + dev_err(dev, "%s: error allocating DMA memory (size=%d)\n", + __func__, size); + kfree(bb); + bb = NULL; + goto out; + } + +#ifdef CONFIG_DMABOUNCE_STATS + if (pool) + pool->allocs++; +#endif + DMABOUNCE_DO_STATS(info, total_allocs++); + + bb->buf = buf; + bb->size = size; + bb->dir = dir; + bb->pool = pool; + + write_lock_irqsave(&info->lock, flags); + list_add(&bb->node, &info->bounce_buffers); + write_unlock_irqrestore(&info->lock, flags); +out: + return bb; +} +EXPORT_SYMBOL(dmabounce_alloc_buffer); + +/** + * dmabounce_free_buffer() - free a coherent DMA bounce buffer + * @info: This dmabounce_info. + * @bb: The coherent DMA bounce buffer to free. + * + * Free a previously allocated coherent DMA bounce buffer @bb from its + * associated dmabounce_info structure @info. + * + * The coherent DMA bounce buffer @bb must have been previously allocated + * using dmabounce_alloc_buffer(). + */ +void +dmabounce_free_buffer(struct dmabounce_info *info, struct dmabounce_buffer *bb) +{ + unsigned long flags; + + dev_dbg(info->dev, "%s(buf=%p)\n", __func__, bb->buf); + + write_lock_irqsave(&info->lock, flags); + list_del(&bb->node); + write_unlock_irqrestore(&info->lock, flags); + + if (bb->pool) + dma_pool_free(bb->pool->pool, bb->bounce_buf, + bb->bounce_buf_dma); + else { + dma_free_coherent(info->dev, bb->size, bb->bounce_buf, + bb->bounce_buf_dma); + } + + kfree(bb); +} +EXPORT_SYMBOL(dmabounce_free_buffer); + +/** + * dmabounce_find_buffer() - locate an existing coherent DMA bounce buffer + * @info: This dmabounce_info. + * @bounce_buf_dma: DMA handle for the bounce buffer to find. + * @size: Size of the bounce buffer to find. + * @dir: Direction of DMA transfer for the buffer to find. + * + * Finds a previously allocated coherent DMA bounce buffer associated + * to a dmabounce_info structure @info. + * + * The coherent DMA bounce buffer searched must have the given + * @bounce_buf_dma DMA handle, @size size and DMA direction @dir. + * If @size is zero, the searched buffer can have any size. + * + * If no matching coherent DMA bounce buffer is found the function returns + * NULL. Otherwise, the matching buffer is returned. + */ +struct dmabounce_buffer * +dmabounce_find_buffer(struct dmabounce_info *info, dma_addr_t bounce_buf_dma, + size_t size, enum dma_data_direction dir) +{ + struct dmabounce_buffer *bb, *needle = NULL; + unsigned long flags; + + read_lock_irqsave(&info->lock, flags); + + list_for_each_entry(bb, &info->bounce_buffers, node) { + if (bb->bounce_buf_dma == bounce_buf_dma) { + /* we should get a perfect match here */ + BUG_ON((size && bb->size != size) || bb->dir != dir); + + needle = bb; + break; + } + } + + read_unlock_irqrestore(&info->lock, flags); + return needle; +} +EXPORT_SYMBOL(dmabounce_find_buffer); + +static int dmabounce_init_pool(struct dmabounce_pool *pool, + struct device *dev, const char *name, + size_t size, size_t align, size_t boundary) +{ + pool->size = size; +#ifdef CONFIG_DMABOUNCE_STATS + pool->allocs = 0; +#endif + pool->pool = dma_pool_create(name, dev, size, align, boundary); + + return pool->pool ? 0 : -ENOMEM; +} + +/** + * dmabounce_info_alloc() - allocate a dmabounce_info structure + * @dev: Device for which coherent memory allocations are done. + * @small_buffer_size: Buffer size for allocations from the small pool. + * @large_buffer_size: Buffer size for allocations from the large pool. + * @align: Alignment for pool based allocations. + * @boundary: Boundary for pool based allocations. + * + * Use this function to allocate a dmabounce_info structure. + * A dmabounce_info structure can be used to manage a set of related + * coherent DMA bounce buffers. + * + * Memory for the coherent DMA bounce buffers will be allocated for the + * specified device @dev. + * If @small_buffer_size or @large_buffer_size are non-zero, allocations + * will be tried from the closest associated DMA pool which has a size + * greater or equal than the specified @size size. Allocations from DMA + * pools will honor the alignment and boundary crossing restrictions + * specified in @align and @boundary. + * Otherwise, allocations will be performed from non-pool coherent memory. + * + * If the dmabounce_info structure cannot be allocated the function + * returns NULL. Otherwise, the allocated dmabounce_info structure is returned. + */ +struct dmabounce_info * +dmabounce_info_alloc(struct device *dev, + size_t small_buffer_size, size_t large_buffer_size, + size_t align, size_t boundary) +{ + struct dmabounce_info *info; + int error; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(dev, "%s: allocation error\n", __func__); + goto out; + } + + if (small_buffer_size) { + error = dmabounce_init_pool(&info->small, dev, + "small_dmabounce_pool", + small_buffer_size, align, boundary); + if (error) { + dev_err(dev, "error %d allocating DMA pool for %zu" + " byte objects\n", error, small_buffer_size); + goto err_alloc_small; + } + } + + if (large_buffer_size) { + error = dmabounce_init_pool(&info->large, dev, + "large_dmabounce_pool", + large_buffer_size, align, boundary); + if (error) { + dev_err(dev, "error %d allocating DMA pool for %zu" + " byte objects\n", error, large_buffer_size); + goto err_alloc_large; + } + } + + info->dev = dev; + INIT_LIST_HEAD(&info->bounce_buffers); + rwlock_init(&info->lock); + + DMABOUNCE_DO_STATS(info, total_allocs = 0); + DMABOUNCE_DO_STATS(info, map_op_count = 0); + DMABOUNCE_DO_STATS(info, bounce_count = 0); + goto out; + +err_alloc_large: + dma_pool_destroy(info->small.pool); +err_alloc_small: + kfree(info); + info = NULL; +out: + return info; +} +EXPORT_SYMBOL(dmabounce_info_alloc); + +/** + * dmabounce_info_free() - free a dmabounce_info + * @info: This dmabounce_info. + * + * Free a previously allocated dmabounce_info structure @info. + * + * The dmabounce_info structure @info must have been previously allocated + * using dmabounce_info_alloc(). + */ +void dmabounce_info_free(struct dmabounce_info *info) +{ + if (!list_empty(&info->bounce_buffers)) { + dev_err(info->dev, "freeing dmabounce with pending buffers!\n"); + BUG(); + } + + if (info->small.pool) + dma_pool_destroy(info->small.pool); + if (info->large.pool) + dma_pool_destroy(info->large.pool); + + kfree(info); +} +EXPORT_SYMBOL(dmabounce_info_free); + +/** + * dmabounce_info_register() - register a dmabounce_info for a device + * @dev: Device for which the dmabounce_info is registered. + * @info: dmabounce_info to register. + * + * Use this function to register a given dmabounce_info @info into a + * device @dev. + * + * A device can only have one dmabounce_info registered with. + * The same dmabounce_info may be registered for many devices. + */ +int dmabounce_info_register(struct device *dev, struct dmabounce_info *info) +{ + if (dev->archdata.dmabounce) + return -EBUSY; + + dev->archdata.dmabounce = info; + +#ifdef CONFIG_DMABOUNCE_STATS + info->attr_res = device_create_file(dev, &dev_attr_dmabounce_stats); +#endif + + dev_info(dev, pr_fmt("device registered\n")); + return 0; +} +EXPORT_SYMBOL(dmabounce_info_register); + +/** + * dmabounce_info_unregister() - unregister the dmabounce_info from a device + * @dev: Device for which the dmabounce_info is unregistered. + * + * Use this function to unregister a previously registered dmabounce_info + * from a device @dev. + */ +void dmabounce_info_unregister(struct device *dev) +{ +#ifdef CONFIG_DMABOUNCE_STATS + struct dmabounce_info *info = dev->archdata.dmabounce; + + if (info && info->attr_res == 0) + device_remove_file(dev, &dev_attr_dmabounce_stats); +#endif + dev->archdata.dmabounce = NULL; + + dev_info(dev, pr_fmt("device unregistered\n")); +} +EXPORT_SYMBOL(dmabounce_info_unregister); + +MODULE_AUTHOR(DRV_AUTHOR); +MODULE_DESCRIPTION(DRV_DESCRIPTION); +MODULE_LICENSE("GPL"); + +/* FIXME remove later when arch/arm/common/dmabounce.c is updated */ +#endif /* !CONFIG_ARM */ -- 1.6.3.3 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html