patch-2.4.22 linux-2.4.22/arch/arm/mach-sa1100/sa1111-pcibuf.c

Next file: linux-2.4.22/arch/arm/mach-sa1100/sa1111.c
Previous file: linux-2.4.22/arch/arm/mach-sa1100/pm.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.21/arch/arm/mach-sa1100/sa1111-pcibuf.c linux-2.4.22/arch/arm/mach-sa1100/sa1111-pcibuf.c
@@ -1,272 +1,400 @@
 /*
  *  linux/arch/arm/mach-sa1100/pci-sa1111.c
  *
- *  Special pci_map/unmap_single routines for SA-1111.   These functions
- *  compensate for a bug in the SA-1111 hardware which don't allow DMA
- *  to/from addresses above 1MB.
+ *  Special pci_{map/unmap/dma_sync}_* routines for SA-1111.
  *
- *  Brad Parker (brad@heeltoe.com)
+ *  These functions utilize bouncer buffers to compensate for a bug in
+ *  the SA-1111 hardware which don't allow DMA to/from addresses
+ *  certain addresses above 1MB.
  *
- *  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.
+ *  Re-written by Christopher Hoover <ch@murgatroid.com>
+ *  Original version by Brad Parker (brad@heeltoe.com)
  *
- *  06/13/2001 - created.
- */
+ *  Copyright (C) 2002 Hewlett Packard Company.
+ *
+ *  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.
+ * */
+
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/slab.h>
 #include <linux/pci.h>
+#include <linux/list.h>
+#include <asm/hardware/sa1111.h>
+//#define DEBUG
+#ifdef DEBUG
+#define DPRINTK(...) do { printk(KERN_DEBUG __VA_ARGS__); } while (0)
+#else
+#define DPRINTK(...) do { } while (0)
+#endif
+
+struct safe_buffer {
+	struct list_head node;
+
+	/* original request */
+	void		*ptr;
+	size_t		size;
+	int		direction;
 
-#include "pcipool.h"
+	/* safe buffer info */
+	struct pci_pool *pool;
+	void		*safe;
+	dma_addr_t	safe_dma_addr;
+};
 
-/*
- * simple buffer allocator for copying of unsafe to safe buffers
- * uses __alloc/__free for actual buffers
- * keeps track of safe buffers we've allocated so we can recover the
- * unsafe buffers.
- */
+LIST_HEAD(safe_buffers);
 
-#define MAX_SAFE	32
 #define SIZE_SMALL	1024
 #define SIZE_LARGE	(16*1024)
 
-static long mapped_alloc_size;
-static char *safe_buffers[MAX_SAFE][2];
-
-
-static struct pci_pool *small_buffer_cache, *large_buffer_cache;
+static struct pci_pool *small_buffer_pool, *large_buffer_pool;
 
-static int
-init_safe_buffers(struct pci_dev *dev)
+static int __init
+create_safe_buffer_pools(void)
 {
-	small_buffer_cache = pci_pool_create("pci_small_buffer",
-					    dev,
+	small_buffer_pool = pci_pool_create("sa1111_small_dma_buffer",
+					    SA1111_FAKE_PCIDEV,
 					    SIZE_SMALL,
 					    0 /* byte alignment */,
 					    0 /* no page-crossing issues */,
-					    GFP_KERNEL | GFP_DMA);
-
-	if (small_buffer_cache == 0)
+					    SLAB_KERNEL);
+	if (0 == small_buffer_pool) {
+		printk(KERN_ERR
+		       "sa1111_pcibuf: could not allocate small pci pool\n");
 		return -1;
+	}
 
-	large_buffer_cache = pci_pool_create("pci_large_buffer",
-					    dev,
+	large_buffer_pool = pci_pool_create("sa1111_large_dma_buffer",
+					    SA1111_FAKE_PCIDEV,
 					    SIZE_LARGE,
 					    0 /* byte alignment */,
 					    0 /* no page-crossing issues */,
-					    GFP_KERNEL | GFP_DMA);
-	if (large_buffer_cache == 0)
+					    SLAB_KERNEL);
+	if (0 == large_buffer_pool) {
+		printk(KERN_ERR
+		       "sa1111_pcibuf: could not allocate large pci pool\n");
+		pci_pool_destroy(small_buffer_pool);
+		small_buffer_pool = 0;
 		return -1;
+	}
 
 	return 0;
 }
 
+static void __exit
+destroy_safe_buffer_pools(void)
+{
+	if (small_buffer_pool)
+		pci_pool_destroy(small_buffer_pool);
+	if (large_buffer_pool)
+		pci_pool_destroy(large_buffer_pool);
+
+	small_buffer_pool = large_buffer_pool = 0;
+}
+
+
 /* allocate a 'safe' buffer and keep track of it */
-static char *
-alloc_safe_buffer(char *unsafe, int size, dma_addr_t *pbus)
+static struct safe_buffer *
+alloc_safe_buffer(void *ptr, size_t size, int direction)
 {
-	char *safe;
-	dma_addr_t busptr;
+	struct safe_buffer *buf;
 	struct pci_pool *pool;
-	int i;
-
-	if (0) printk("alloc_safe_buffer(size=%d)\n", size);
+	void *safe;
+	dma_addr_t safe_dma_addr;
 
-	if (size <= SIZE_SMALL)
-		pool = small_buffer_cache;
-	else
-		if (size < SIZE_LARGE)
-			pool = large_buffer_cache;
-				else
-					return 0;
+	DPRINTK("%s(ptr=%p, size=%d, direction=%d)\n",
+		__func__, ptr, size, direction);
 
-	safe = pci_pool_alloc(pool, SLAB_ATOMIC, &busptr);
-	if (safe == 0)
+	buf = kmalloc(sizeof(struct safe_buffer), GFP_ATOMIC);
+	if (buf == 0) {
+		printk(KERN_WARNING "%s: kmalloc failed\n", __func__);
 		return 0;
+	}
 
-	for (i = 0; i < MAX_SAFE; i++)
-		if (safe_buffers[i][0] == 0) {
-			break;
-		}
-
-	if (i == MAX_SAFE) {
-		panic(__FILE__ ": exceeded MAX_SAFE buffers");
+	if (size <= SIZE_SMALL) {
+		pool = small_buffer_pool;
+		safe = pci_pool_alloc(pool, GFP_ATOMIC, &safe_dma_addr);
+	} else if (size <= SIZE_LARGE) {
+		pool = large_buffer_pool;
+		safe = pci_pool_alloc(pool, GFP_ATOMIC, &safe_dma_addr);
+	} else {
+		printk(KERN_DEBUG
+		       "sa111_pcibuf: resorting to pci_alloc_consistent\n");
+		pool = 0;
+		safe = pci_alloc_consistent(SA1111_FAKE_PCIDEV, size,
+					    &safe_dma_addr);
 	}
 
-	/* place the size index and the old buffer ptr in the first 8 bytes
-	 * and return a ptr + 12 to caller
-	 */
-	((int *)safe)[0] = i;
-	((char **)safe)[1] = (char *)pool;
-	((char **)safe)[2] = unsafe;
+	if (safe == 0) {
+		printk(KERN_WARNING
+		       "%s: could not alloc dma memory (size=%d)\n",
+		       __func__, size);
+		kfree(buf);
+		return 0;
+	}
 
-	busptr += sizeof(int) + sizeof(char *) + sizeof(char *);
+	BUG_ON(sa1111_check_dma_bug(safe_dma_addr));	// paranoia
 
-	safe_buffers[i][0] = (void *)busptr;
-	safe_buffers[i][1] = (void *)safe;
+	buf->ptr = ptr;
+	buf->size = size;
+	buf->direction = direction;
+	buf->pool = pool;
+	buf->safe = safe;
+	buf->safe_dma_addr = safe_dma_addr;
 
-	safe += sizeof(int) + sizeof(char *) + sizeof(char *);
+	MOD_INC_USE_COUNT;
+	list_add(&buf->node, &safe_buffers);
 
-	*pbus = busptr;
-	return safe;
+	return buf;
 }
 
 /* determine if a buffer is from our "safe" pool */
-static char *
-find_safe_buffer(char *busptr, char **unsafe)
+static struct safe_buffer *
+find_safe_buffer(dma_addr_t safe_dma_addr)
 {
-	int i;
-	char *buf;
+	struct list_head *entry;
 
-	for (i = 0; i < MAX_SAFE; i++) {
-		if (safe_buffers[i][0] == busptr) {
-			if (0) printk("find_safe_buffer(%p) found @ %d\n", busptr, i);
-			buf = safe_buffers[i][1];
-			*unsafe = ((char **)buf)[2];
-			return buf + sizeof(int) + sizeof(char *) + sizeof(char *);
+	list_for_each(entry, &safe_buffers) {
+		struct safe_buffer *b =
+			list_entry(entry, struct safe_buffer, node);
+
+		if (b->safe_dma_addr == safe_dma_addr) {
+			return b;
 		}
 	}
 
-	return (char *)0;
+	return 0;
 }
 
 static void
-free_safe_buffer(char *buf)
+free_safe_buffer(struct safe_buffer *buf)
 {
-	int index;
-	struct pci_pool *pool;
-	char *dma;
+	DPRINTK("%s(buf=%p)\n", __func__, buf);
 
-	if (0) printk("free_safe_buffer(buf=%p)\n", buf);
+	list_del(&buf->node);
 
-	/* retrieve the buffer size index */
-	buf -= sizeof(int) + sizeof(char*) + sizeof(char*);
-	index = ((int *)buf)[0];
-	pool = (struct pci_pool *)((char **)buf)[1];
+	if (buf->pool)
+		pci_pool_free(buf->pool, buf->safe, buf->safe_dma_addr);
+	else
+		pci_free_consistent(SA1111_FAKE_PCIDEV, buf->size, buf->safe,
+				    buf->safe_dma_addr);
+	kfree(buf);
 
-	if (0) printk("free_safe_buffer(%p) index %d\n",
-		      buf, index);
+	MOD_DEC_USE_COUNT;
+}
 
-	if (index < 0 || index >= MAX_SAFE) {
-		printk(__FILE__ ": free_safe_buffer() corrupt buffer\n");
-		return;
-	}
+static inline int
+dma_range_is_safe(dma_addr_t addr, size_t size)
+{
+	unsigned int physaddr = SA1111_DMA_ADDR((unsigned int) addr);
 
-	dma = safe_buffers[index][0];
-	safe_buffers[index][0] = 0;
+	/* Any address within one megabyte of the start of the target
+         * bank will be OK.  This is an overly conservative test:
+         * other addresses can be OK depending on the dram
+         * configuration.  (See sa1111.c:sa1111_check_dma_bug() * for
+         * details.)
+	 *
+	 * We take care to ensure the entire dma region is within
+	 * the safe range.
+	 */
 
-	pci_pool_free(pool, buf, (u32)dma);
+	return ((physaddr + size - 1) < (1<<20));
 }
 
 /*
-  NOTE:
-  replace pci_map/unmap_single with local routines which will
-  do buffer copies if buffer is above 1mb...
-*/
-
-/*
  * see if a buffer address is in an 'unsafe' range.  if it is
  * allocate a 'safe' buffer and copy the unsafe buffer into it.
  * substitute the safe buffer for the unsafe one.
  * (basically move the buffer from an unsafe area to a safe one)
- *
- * we assume calls to map_single are symmetric with calls to unmap_single...
  */
 dma_addr_t
-sa1111_map_single(struct pci_dev *hwdev, void *virtptr,
-	       size_t size, int direction)
+sa1111_map_single(void *ptr, size_t size, int direction)
 {
-	dma_addr_t busptr;
+	unsigned long flags;
+	dma_addr_t dma_addr;
 
-	mapped_alloc_size += size;
+	DPRINTK("%s(ptr=%p,size=%d,dir=%x)\n",
+	       __func__, ptr, size, direction);
 
-	if (0) printk("pci_map_single(hwdev=%p,ptr=%p,size=%d,dir=%x) "
-		      "alloced=%ld\n",
-		      hwdev, virtptr, size, direction, mapped_alloc_size);
+	BUG_ON(direction == PCI_DMA_NONE);
 
-	busptr = virt_to_bus(virtptr);
+	local_irq_save(flags);
 
-	/* we assume here that a buffer will never be >=64k */
-	if ( (((unsigned long)busptr) & 0x100000) ||
-	     ((((unsigned long)busptr)+size) & 0x100000) )
-	{
-		char *safe;
+	dma_addr = virt_to_bus(ptr);
 
-		safe = alloc_safe_buffer(virtptr, size, &busptr);
-		if (safe == 0) {
-			printk("unable to map unsafe buffer %p!\n", virtptr);
+	if (!dma_range_is_safe(dma_addr, size)) {
+		struct safe_buffer *buf;
+
+		buf = alloc_safe_buffer(ptr, size, direction);
+		if (buf == 0) {
+			printk(KERN_ERR
+			       "%s: unable to map unsafe buffer %p!\n",
+			       __func__, ptr);
+			local_irq_restore(flags);
 			return 0;
 		}
 
-		if (0) printk("unsafe buffer %p (phy=%p) mapped to %p (phy=%p)\n",
-			      virtptr, (void *)virt_to_bus(virtptr),
-			      safe, (void *)busptr);
-
-		memcpy(safe, virtptr, size);
-		consistent_sync(safe, size, direction);
+		DPRINTK("%s: unsafe buffer %p (phy=%p) mapped to %p (phy=%p)\n",
+			__func__,
+			buf->ptr, (void *) virt_to_bus(buf->ptr),
+			buf->safe, (void *) buf->safe_dma_addr);
+
+		if ((direction == PCI_DMA_TODEVICE) ||
+		    (direction == PCI_DMA_BIDIRECTIONAL)) {
+			DPRINTK("%s: copy out from unsafe %p, to safe %p, size %d\n",
+				__func__, ptr, buf->safe, size);
+			memcpy(buf->safe, ptr, size);
+		}
+		consistent_sync(buf->safe, size, direction);
 
-		return busptr;
+		dma_addr = buf->safe_dma_addr;
+	} else {
+		consistent_sync(ptr, size, direction);
 	}
 
-	consistent_sync(virtptr, size, direction);
-	return busptr;
+	local_irq_restore(flags);
+	return dma_addr;
 }
 
 /*
- * see if a mapped address was really a "safe" buffer and if so,
- * copy the data from the safe buffer back to the unsafe buffer
- * and free up the safe buffer.
- * (basically return things back to the way they should be)
+ * see if a mapped address was really a "safe" buffer and if so, copy
+ * the data from the safe buffer back to the unsafe buffer and free up
+ * the safe buffer.  (basically return things back to the way they
+ * should be)
  */
+
 void
-sa1111_unmap_single(struct pci_dev *hwdev, dma_addr_t dma_addr,
-		 size_t size, int direction)
+sa1111_unmap_single(dma_addr_t dma_addr, size_t size, int direction)
 {
-	char *safe, *unsafe;
-	void *buf;
+	unsigned long flags;
+	struct safe_buffer *buf;
 
-	/* hack; usb-ohci.c never sends hwdev==NULL, all others do */
-	if (hwdev == NULL) {
-		return;
+	DPRINTK("%s(ptr=%p,size=%d,dir=%x)\n",
+		__func__, (void *) dma_addr, size, direction);
+
+	BUG_ON(direction == PCI_DMA_NONE);
+
+	local_irq_save(flags);
+
+	buf = find_safe_buffer(dma_addr);
+	if (buf) {
+		BUG_ON(buf->size != size);
+		BUG_ON(buf->direction != direction);
+
+		DPRINTK("%s: unsafe buffer %p (phy=%p) mapped to %p (phy=%p)\n",
+			__func__,
+			buf->ptr, (void *) virt_to_bus(buf->ptr),
+			buf->safe, (void *) buf->safe_dma_addr);
+
+		if ((direction == PCI_DMA_FROMDEVICE) ||
+		    (direction == PCI_DMA_BIDIRECTIONAL)) {
+			DPRINTK("%s: copy back from safe %p, to unsafe %p size %d\n",
+				__func__, buf->safe, buf->ptr, size);
+			memcpy(buf->ptr, buf->safe, size);
+		}
+		free_safe_buffer(buf);
 	}
 
-	mapped_alloc_size -= size;
+	local_irq_restore(flags);
+}
+
+int
+sa1111_map_sg(struct scatterlist *sg, int nents, int direction)
+{
+	BUG();			/* Not implemented. */
+
+	return -1;
+}
+
+void
+sa1111_unmap_sg(struct scatterlist *sg, int nents, int direction)
+{
+	BUG();			/* Not implemented. */
+}
+
+void
+sa1111_dma_sync_single(dma_addr_t dma_addr, size_t size, int direction)
+{
+	unsigned long flags;
+	struct safe_buffer *buf;
+
+	DPRINTK("%s(ptr=%p,size=%d,dir=%x)\n",
+		__func__, (void *) dma_addr, size, direction);
 
-	if (0) printk("pci_unmap_single(hwdev=%p,ptr=%p,size=%d,dir=%x) "
-		      "alloced=%ld\n",
-		      hwdev, (void *)dma_addr, size, direction,
-		      mapped_alloc_size);
-
-	if ((safe = find_safe_buffer((void *)dma_addr, &unsafe))) {
-		if (0) printk("copyback unsafe %p, safe %p, size %d\n",
-			      unsafe, safe, size);
-
-		consistent_sync(safe, size, PCI_DMA_FROMDEVICE);
-		memcpy(unsafe, safe, size);
-		free_safe_buffer(safe);
+	local_irq_save(flags);
+
+	buf = find_safe_buffer(dma_addr);
+	if (buf) {
+		BUG_ON(buf->size != size);
+		BUG_ON(buf->direction != direction);
+
+		DPRINTK("%s: unsafe buffer %p (phy=%p) mapped to %p (phy=%p)\n",
+			__func__,
+			buf->ptr, (void *) virt_to_bus(buf->ptr),
+			buf->safe, (void *) buf->safe_dma_addr);
+
+		switch (direction) {
+		case PCI_DMA_FROMDEVICE:
+			DPRINTK("%s: copy back from safe %p, to unsafe %p size %d\n",
+				__func__, buf->safe, buf->ptr, size);
+			memcpy(buf->ptr, buf->safe, size);
+			break;
+		case PCI_DMA_TODEVICE:
+			DPRINTK("%s: copy out from unsafe %p, to safe %p, size %d\n",
+				__func__,buf->ptr, buf->safe, size);
+			memcpy(buf->safe, buf->ptr, size);
+			break;
+		case PCI_DMA_BIDIRECTIONAL:
+			BUG();	/* is this allowed?  what does it mean? */
+		default:
+			BUG();
+		}
+		consistent_sync(buf->safe, size, direction);
 	} else {
-		/* assume this is normal memory */
-		buf = bus_to_virt(dma_addr);
-		consistent_sync(buf, size, PCI_DMA_FROMDEVICE);
+		consistent_sync(bus_to_virt(dma_addr), size, direction);
 	}
+
+	local_irq_restore(flags);
+}
+
+void
+sa1111_dma_sync_sg(struct scatterlist *sg, int nelems, int direction)
+{
+	BUG();			/* Not implemented. */
 }
 
 EXPORT_SYMBOL(sa1111_map_single);
 EXPORT_SYMBOL(sa1111_unmap_single);
+EXPORT_SYMBOL(sa1111_map_sg);
+EXPORT_SYMBOL(sa1111_unmap_sg);
+EXPORT_SYMBOL(sa1111_dma_sync_single);
+EXPORT_SYMBOL(sa1111_dma_sync_sg);
 
-static int __init sa1111_init_safe_buffers(void)
+/* **************************************** */
+
+static int __init sa1111_pcibuf_init(void)
 {
-	printk("Initializing SA1111 buffer pool for DMA workaround\n");
-	init_safe_buffers(NULL);
-	return 0;
+	int ret;
+
+	printk(KERN_DEBUG
+	       "sa1111_pcibuf: initializing SA-1111 DMA workaround\n");
+
+	ret = create_safe_buffer_pools();
+
+	return ret;
 }
+module_init(sa1111_pcibuf_init);
 
-static void free_safe_buffers(void)
+static void __exit sa1111_pcibuf_exit(void)
 {
-	pci_pool_destroy(small_buffer_cache);
-	pci_pool_destroy(large_buffer_cache);
+	BUG_ON(!list_empty(&safe_buffers));
+
+	destroy_safe_buffer_pools();
 }
+module_exit(sa1111_pcibuf_exit);
 
-module_init(sa1111_init_safe_buffers);
-module_exit(free_safe_buffers);
+MODULE_AUTHOR("Christopher Hoover <ch@hpl.hp.com>");
+MODULE_DESCRIPTION("Special pci_{map/unmap/dma_sync}_* routines for SA-1111.");
+MODULE_LICENSE("GPL");

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)