mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 14:42:24 +00:00
5efd2ea8c9
the following error pops up during "testusb -a -t 10" | musb-hdrc musb-hdrc.1.auto: dma_pool_free buffer-128, f134e000/be842000 (bad dma) hcd_buffer_create() creates a few buffers, the smallest has 32 bytes of size. ARCH_KMALLOC_MINALIGN is set to 64 bytes. This combo results in hcd_buffer_alloc() returning memory which is 32 bytes aligned and it might by identified by buffer_offset() as another buffer. This means the buffer which is on a 32 byte boundary will not get freed, instead it tries to free another buffer with the error message. This patch fixes the issue by creating the smallest DMA buffer with the size of ARCH_KMALLOC_MINALIGN (or 32 in case ARCH_KMALLOC_MINALIGN is smaller). This might be 32, 64 or even 128 bytes. The next three pools will have the size 128, 512 and 2048. In case the smallest pool is 128 bytes then we have only three pools instead of four (and zero the first entry in the array). The last pool size is always 2048 bytes which is the assumed PAGE_SIZE / 2 of 4096. I doubt it makes sense to continue using PAGE_SIZE / 2 where we would end up with 8KiB buffer in case we have 16KiB pages. Instead I think it makes sense to have a common size(s) and extend them if there is need to. There is a BUILD_BUG_ON() now in case someone has a minalign of more than 128 bytes. Cc: stable@vger.kernel.org Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
161 lines
3.5 KiB
C
161 lines
3.5 KiB
C
/*
|
|
* DMA memory management for framework level HCD code (hc_driver)
|
|
*
|
|
* This implementation plugs in through generic "usb_bus" level methods,
|
|
* and should work with all USB controllers, regardless of bus type.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/io.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/dmapool.h>
|
|
#include <linux/usb.h>
|
|
#include <linux/usb/hcd.h>
|
|
|
|
|
|
/*
|
|
* DMA-Coherent Buffers
|
|
*/
|
|
|
|
/* FIXME tune these based on pool statistics ... */
|
|
static size_t pool_max[HCD_BUFFER_POOLS] = {
|
|
32, 128, 512, 2048,
|
|
};
|
|
|
|
void __init usb_init_pool_max(void)
|
|
{
|
|
/*
|
|
* The pool_max values must never be smaller than
|
|
* ARCH_KMALLOC_MINALIGN.
|
|
*/
|
|
if (ARCH_KMALLOC_MINALIGN <= 32)
|
|
; /* Original value is okay */
|
|
else if (ARCH_KMALLOC_MINALIGN <= 64)
|
|
pool_max[0] = 64;
|
|
else if (ARCH_KMALLOC_MINALIGN <= 128)
|
|
pool_max[0] = 0; /* Don't use this pool */
|
|
else
|
|
BUILD_BUG(); /* We don't allow this */
|
|
}
|
|
|
|
/* SETUP primitives */
|
|
|
|
/**
|
|
* hcd_buffer_create - initialize buffer pools
|
|
* @hcd: the bus whose buffer pools are to be initialized
|
|
* Context: !in_interrupt()
|
|
*
|
|
* Call this as part of initializing a host controller that uses the dma
|
|
* memory allocators. It initializes some pools of dma-coherent memory that
|
|
* will be shared by all drivers using that controller.
|
|
*
|
|
* Call hcd_buffer_destroy() to clean up after using those pools.
|
|
*
|
|
* Return: 0 if successful. A negative errno value otherwise.
|
|
*/
|
|
int hcd_buffer_create(struct usb_hcd *hcd)
|
|
{
|
|
char name[16];
|
|
int i, size;
|
|
|
|
if (!hcd->self.controller->dma_mask &&
|
|
!(hcd->driver->flags & HCD_LOCAL_MEM))
|
|
return 0;
|
|
|
|
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
|
size = pool_max[i];
|
|
if (!size)
|
|
continue;
|
|
snprintf(name, sizeof name, "buffer-%d", size);
|
|
hcd->pool[i] = dma_pool_create(name, hcd->self.controller,
|
|
size, size, 0);
|
|
if (!hcd->pool[i]) {
|
|
hcd_buffer_destroy(hcd);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* hcd_buffer_destroy - deallocate buffer pools
|
|
* @hcd: the bus whose buffer pools are to be destroyed
|
|
* Context: !in_interrupt()
|
|
*
|
|
* This frees the buffer pools created by hcd_buffer_create().
|
|
*/
|
|
void hcd_buffer_destroy(struct usb_hcd *hcd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
|
struct dma_pool *pool = hcd->pool[i];
|
|
if (pool) {
|
|
dma_pool_destroy(pool);
|
|
hcd->pool[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* sometimes alloc/free could use kmalloc with GFP_DMA, for
|
|
* better sharing and to leverage mm/slab.c intelligence.
|
|
*/
|
|
|
|
void *hcd_buffer_alloc(
|
|
struct usb_bus *bus,
|
|
size_t size,
|
|
gfp_t mem_flags,
|
|
dma_addr_t *dma
|
|
)
|
|
{
|
|
struct usb_hcd *hcd = bus_to_hcd(bus);
|
|
int i;
|
|
|
|
/* some USB hosts just use PIO */
|
|
if (!bus->controller->dma_mask &&
|
|
!(hcd->driver->flags & HCD_LOCAL_MEM)) {
|
|
*dma = ~(dma_addr_t) 0;
|
|
return kmalloc(size, mem_flags);
|
|
}
|
|
|
|
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
|
if (size <= pool_max[i])
|
|
return dma_pool_alloc(hcd->pool[i], mem_flags, dma);
|
|
}
|
|
return dma_alloc_coherent(hcd->self.controller, size, dma, mem_flags);
|
|
}
|
|
|
|
void hcd_buffer_free(
|
|
struct usb_bus *bus,
|
|
size_t size,
|
|
void *addr,
|
|
dma_addr_t dma
|
|
)
|
|
{
|
|
struct usb_hcd *hcd = bus_to_hcd(bus);
|
|
int i;
|
|
|
|
if (!addr)
|
|
return;
|
|
|
|
if (!bus->controller->dma_mask &&
|
|
!(hcd->driver->flags & HCD_LOCAL_MEM)) {
|
|
kfree(addr);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < HCD_BUFFER_POOLS; i++) {
|
|
if (size <= pool_max[i]) {
|
|
dma_pool_free(hcd->pool[i], addr, dma);
|
|
return;
|
|
}
|
|
}
|
|
dma_free_coherent(hcd->self.controller, size, addr, dma);
|
|
}
|