patch-2.3.41 linux/drivers/scsi/scsi_dma.c

Next file: linux/drivers/scsi/scsi_ioctl.c
Previous file: linux/drivers/scsi/scsi_debug.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.40/linux/drivers/scsi/scsi_dma.c linux/drivers/scsi/scsi_dma.c
@@ -0,0 +1,449 @@
+/*
+ *  scsi_dma.c Copyright (C) 2000 Eric Youngdale
+ *
+ *  mid-level SCSI DMA bounce buffer allocator
+ *
+ */
+
+#define __NO_VERSION__
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/blk.h>
+
+
+#include "scsi.h"
+#include "hosts.h"
+#include "constants.h"
+
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+
+/*
+ * PAGE_SIZE must be a multiple of the sector size (512).  True
+ * for all reasonably recent architectures (even the VAX...).
+ */
+#define SECTOR_SIZE		512
+#define SECTORS_PER_PAGE	(PAGE_SIZE/SECTOR_SIZE)
+
+#if SECTORS_PER_PAGE <= 8
+typedef unsigned char FreeSectorBitmap;
+#elif SECTORS_PER_PAGE <= 32
+typedef unsigned int FreeSectorBitmap;
+#else
+#error You lose.
+#endif
+
+/*
+ * Used for access to internal allocator used for DMA safe buffers.
+ */
+static spinlock_t allocator_request_lock = SPIN_LOCK_UNLOCKED;
+
+static FreeSectorBitmap *dma_malloc_freelist = NULL;
+static int need_isa_bounce_buffers;
+static unsigned int dma_sectors = 0;
+unsigned int scsi_dma_free_sectors = 0;
+unsigned int scsi_need_isa_buffer = 0;
+static unsigned char **dma_malloc_pages = NULL;
+
+/*
+ * Function:    scsi_malloc
+ *
+ * Purpose:     Allocate memory from the DMA-safe pool.
+ *
+ * Arguments:   len       - amount of memory we need.
+ *
+ * Lock status: No locks assumed to be held.  This function is SMP-safe.
+ *
+ * Returns:     Pointer to memory block.
+ *
+ * Notes:       Prior to the new queue code, this function was not SMP-safe.
+ *              This function can only allocate in units of sectors
+ *              (i.e. 512 bytes).
+ *
+ *              We cannot use the normal system allocator becuase we need
+ *              to be able to guarantee that we can process a complete disk
+ *              I/O request without touching the system allocator.  Think
+ *              about it - if the system were heavily swapping, and tried to
+ *              write out a block of memory to disk, and the SCSI code needed
+ *              to allocate more memory in order to be able to write the
+ *              data to disk, you would wedge the system.
+ */
+void *scsi_malloc(unsigned int len)
+{
+	unsigned int nbits, mask;
+	unsigned long flags;
+
+	int i, j;
+	if (len % SECTOR_SIZE != 0 || len > PAGE_SIZE)
+		return NULL;
+
+	nbits = len >> 9;
+	mask = (1 << nbits) - 1;
+
+	spin_lock_irqsave(&allocator_request_lock, flags);
+
+	for (i = 0; i < dma_sectors / SECTORS_PER_PAGE; i++)
+		for (j = 0; j <= SECTORS_PER_PAGE - nbits; j++) {
+			if ((dma_malloc_freelist[i] & (mask << j)) == 0) {
+				dma_malloc_freelist[i] |= (mask << j);
+				scsi_dma_free_sectors -= nbits;
+#ifdef DEBUG
+				SCSI_LOG_MLQUEUE(3, printk("SMalloc: %d %p [From:%p]\n", len, dma_malloc_pages[i] + (j << 9)));
+				printk("SMalloc: %d %p [From:%p]\n", len, dma_malloc_pages[i] + (j << 9));
+#endif
+				spin_unlock_irqrestore(&allocator_request_lock, flags);
+				return (void *) ((unsigned long) dma_malloc_pages[i] + (j << 9));
+			}
+		}
+	spin_unlock_irqrestore(&allocator_request_lock, flags);
+	return NULL;		/* Nope.  No more */
+}
+
+/*
+ * Function:    scsi_free
+ *
+ * Purpose:     Free memory into the DMA-safe pool.
+ *
+ * Arguments:   ptr       - data block we are freeing.
+ *              len       - size of block we are freeing.
+ *
+ * Lock status: No locks assumed to be held.  This function is SMP-safe.
+ *
+ * Returns:     Nothing
+ *
+ * Notes:       This function *must* only be used to free memory
+ *              allocated from scsi_malloc().
+ *
+ *              Prior to the new queue code, this function was not SMP-safe.
+ *              This function can only allocate in units of sectors
+ *              (i.e. 512 bytes).
+ */
+int scsi_free(void *obj, unsigned int len)
+{
+	unsigned int page, sector, nbits, mask;
+	unsigned long flags;
+
+#ifdef DEBUG
+	unsigned long ret = 0;
+
+#ifdef __mips__
+	__asm__ __volatile__("move\t%0,$31":"=r"(ret));
+#else
+	ret = __builtin_return_address(0);
+#endif
+	printk("scsi_free %p %d\n", obj, len);
+	SCSI_LOG_MLQUEUE(3, printk("SFree: %p %d\n", obj, len));
+#endif
+
+	spin_lock_irqsave(&allocator_request_lock, flags);
+
+	for (page = 0; page < dma_sectors / SECTORS_PER_PAGE; page++) {
+		unsigned long page_addr = (unsigned long) dma_malloc_pages[page];
+		if ((unsigned long) obj >= page_addr &&
+		    (unsigned long) obj < page_addr + PAGE_SIZE) {
+			sector = (((unsigned long) obj) - page_addr) >> 9;
+
+			nbits = len >> 9;
+			mask = (1 << nbits) - 1;
+
+			if ((mask << sector) >= (1 << SECTORS_PER_PAGE))
+				panic("scsi_free:Bad memory alignment");
+
+			if ((dma_malloc_freelist[page] &
+			     (mask << sector)) != (mask << sector)) {
+#ifdef DEBUG
+				printk("scsi_free(obj=%p, len=%d) called from %08lx\n",
+				       obj, len, ret);
+#endif
+				panic("scsi_free:Trying to free unused memory");
+			}
+			scsi_dma_free_sectors += nbits;
+			dma_malloc_freelist[page] &= ~(mask << sector);
+			spin_unlock_irqrestore(&allocator_request_lock, flags);
+			return 0;
+		}
+	}
+	panic("scsi_free:Bad offset");
+}
+
+
+/*
+ * Function:    scsi_resize_dma_pool
+ *
+ * Purpose:     Ensure that the DMA pool is sufficiently large to be
+ *              able to guarantee that we can always process I/O requests
+ *              without calling the system allocator.
+ *
+ * Arguments:   None.
+ *
+ * Lock status: No locks assumed to be held.  This function is SMP-safe.
+ *
+ * Returns:     Nothing
+ *
+ * Notes:       Prior to the new queue code, this function was not SMP-safe.
+ *              Go through the device list and recompute the most appropriate
+ *              size for the dma pool.  Then grab more memory (as required).
+ */
+void scsi_resize_dma_pool(void)
+{
+	int i, k;
+	unsigned long size;
+	unsigned long flags;
+	struct Scsi_Host *shpnt;
+	struct Scsi_Host *host = NULL;
+	Scsi_Device *SDpnt;
+	FreeSectorBitmap *new_dma_malloc_freelist = NULL;
+	unsigned int new_dma_sectors = 0;
+	unsigned int new_need_isa_buffer = 0;
+	unsigned char **new_dma_malloc_pages = NULL;
+	int out_of_space = 0;
+
+	spin_lock_irqsave(&allocator_request_lock, flags);
+
+	if (!scsi_hostlist) {
+		/*
+		 * Free up the DMA pool.
+		 */
+		if (scsi_dma_free_sectors != dma_sectors)
+			panic("SCSI DMA pool memory leak %d %d\n", scsi_dma_free_sectors, dma_sectors);
+
+		for (i = 0; i < dma_sectors / SECTORS_PER_PAGE; i++)
+			free_pages((unsigned long) dma_malloc_pages[i], 0);
+		if (dma_malloc_pages)
+			kfree((char *) dma_malloc_pages);
+		dma_malloc_pages = NULL;
+		if (dma_malloc_freelist)
+			kfree((char *) dma_malloc_freelist);
+		dma_malloc_freelist = NULL;
+		dma_sectors = 0;
+		scsi_dma_free_sectors = 0;
+		spin_unlock_irqrestore(&allocator_request_lock, flags);
+		return;
+	}
+	/* Next, check to see if we need to extend the DMA buffer pool */
+
+	new_dma_sectors = 2 * SECTORS_PER_PAGE;		/* Base value we use */
+
+	if (__pa(high_memory) - 1 > ISA_DMA_THRESHOLD)
+		need_isa_bounce_buffers = 1;
+	else
+		need_isa_bounce_buffers = 0;
+
+	if (scsi_devicelist)
+		for (shpnt = scsi_hostlist; shpnt; shpnt = shpnt->next)
+			new_dma_sectors += SECTORS_PER_PAGE;	/* Increment for each host */
+
+	for (host = scsi_hostlist; host; host = host->next) {
+		for (SDpnt = host->host_queue; SDpnt; SDpnt = SDpnt->next) {
+			/*
+			 * sd and sr drivers allocate scatterlists.
+			 * sr drivers may allocate for each command 1x2048 or 2x1024 extra
+			 * buffers for 2k sector size and 1k fs.
+			 * sg driver allocates buffers < 4k.
+			 * st driver does not need buffers from the dma pool.
+			 * estimate 4k buffer/command for devices of unknown type (should panic).
+			 */
+			if (SDpnt->type == TYPE_WORM || SDpnt->type == TYPE_ROM ||
+			    SDpnt->type == TYPE_DISK || SDpnt->type == TYPE_MOD) {
+				int nents = host->sg_tablesize;
+#ifdef DMA_CHUNK_SIZE
+				/* If the architecture does DMA sg merging, make sure
+				   we count with at least 64 entries even for HBAs
+				   which handle very few sg entries.  */
+				if (nents < 64) nents = 64;
+#endif
+				new_dma_sectors += ((nents *
+				sizeof(struct scatterlist) + 511) >> 9) *
+				 SDpnt->queue_depth;
+				if (SDpnt->type == TYPE_WORM || SDpnt->type == TYPE_ROM)
+					new_dma_sectors += (2048 >> 9) * SDpnt->queue_depth;
+			} else if (SDpnt->type == TYPE_SCANNER ||
+				   SDpnt->type == TYPE_PROCESSOR ||
+				   SDpnt->type == TYPE_MEDIUM_CHANGER ||
+				   SDpnt->type == TYPE_ENCLOSURE) {
+				new_dma_sectors += (4096 >> 9) * SDpnt->queue_depth;
+			} else {
+				if (SDpnt->type != TYPE_TAPE) {
+					printk("resize_dma_pool: unknown device type %d\n", SDpnt->type);
+					new_dma_sectors += (4096 >> 9) * SDpnt->queue_depth;
+				}
+			}
+
+			if (host->unchecked_isa_dma &&
+			    need_isa_bounce_buffers &&
+			    SDpnt->type != TYPE_TAPE) {
+				new_dma_sectors += (PAGE_SIZE >> 9) * host->sg_tablesize *
+				    SDpnt->queue_depth;
+				new_need_isa_buffer++;
+			}
+		}
+	}
+
+#ifdef DEBUG_INIT
+	printk("resize_dma_pool: needed dma sectors = %d\n", new_dma_sectors);
+#endif
+
+	/* limit DMA memory to 32MB: */
+	new_dma_sectors = (new_dma_sectors + 15) & 0xfff0;
+
+	/*
+	 * We never shrink the buffers - this leads to
+	 * race conditions that I would rather not even think
+	 * about right now.
+	 */
+#if 0				/* Why do this? No gain and risks out_of_space */
+	if (new_dma_sectors < dma_sectors)
+		new_dma_sectors = dma_sectors;
+#endif
+	if (new_dma_sectors <= dma_sectors) {
+		spin_unlock_irqrestore(&allocator_request_lock, flags);
+		return;		/* best to quit while we are in front */
+        }
+
+	for (k = 0; k < 20; ++k) {	/* just in case */
+		out_of_space = 0;
+		size = (new_dma_sectors / SECTORS_PER_PAGE) *
+		    sizeof(FreeSectorBitmap);
+		new_dma_malloc_freelist = (FreeSectorBitmap *)
+		    kmalloc(size, GFP_ATOMIC);
+		if (new_dma_malloc_freelist) {
+                        memset(new_dma_malloc_freelist, 0, size);
+			size = (new_dma_sectors / SECTORS_PER_PAGE) *
+			    sizeof(*new_dma_malloc_pages);
+			new_dma_malloc_pages = (unsigned char **)
+			    kmalloc(size, GFP_ATOMIC);
+			if (!new_dma_malloc_pages) {
+				size = (new_dma_sectors / SECTORS_PER_PAGE) *
+				    sizeof(FreeSectorBitmap);
+				kfree((char *) new_dma_malloc_freelist);
+				out_of_space = 1;
+			} else {
+                                memset(new_dma_malloc_pages, 0, size);
+                        }
+		} else
+			out_of_space = 1;
+
+		if ((!out_of_space) && (new_dma_sectors > dma_sectors)) {
+			for (i = dma_sectors / SECTORS_PER_PAGE;
+			   i < new_dma_sectors / SECTORS_PER_PAGE; i++) {
+				new_dma_malloc_pages[i] = (unsigned char *)
+				    __get_free_pages(GFP_ATOMIC | GFP_DMA, 0);
+				if (!new_dma_malloc_pages[i])
+					break;
+			}
+			if (i != new_dma_sectors / SECTORS_PER_PAGE) {	/* clean up */
+				int k = i;
+
+				out_of_space = 1;
+				for (i = 0; i < k; ++i)
+					free_pages((unsigned long) new_dma_malloc_pages[i], 0);
+			}
+		}
+		if (out_of_space) {	/* try scaling down new_dma_sectors request */
+			printk("scsi::resize_dma_pool: WARNING, dma_sectors=%u, "
+			       "wanted=%u, scaling\n", dma_sectors, new_dma_sectors);
+			if (new_dma_sectors < (8 * SECTORS_PER_PAGE))
+				break;	/* pretty well hopeless ... */
+			new_dma_sectors = (new_dma_sectors * 3) / 4;
+			new_dma_sectors = (new_dma_sectors + 15) & 0xfff0;
+			if (new_dma_sectors <= dma_sectors)
+				break;	/* stick with what we have got */
+		} else
+			break;	/* found space ... */
+	}			/* end of for loop */
+	if (out_of_space) {
+		spin_unlock_irqrestore(&allocator_request_lock, flags);
+		scsi_need_isa_buffer = new_need_isa_buffer;	/* some useful info */
+		printk("      WARNING, not enough memory, pool not expanded\n");
+		return;
+	}
+	/* When we dick with the actual DMA list, we need to
+	 * protect things
+	 */
+	if (dma_malloc_freelist) {
+		size = (dma_sectors / SECTORS_PER_PAGE) * sizeof(FreeSectorBitmap);
+		memcpy(new_dma_malloc_freelist, dma_malloc_freelist, size);
+		kfree((char *) dma_malloc_freelist);
+	}
+	dma_malloc_freelist = new_dma_malloc_freelist;
+
+	if (dma_malloc_pages) {
+		size = (dma_sectors / SECTORS_PER_PAGE) * sizeof(*dma_malloc_pages);
+		memcpy(new_dma_malloc_pages, dma_malloc_pages, size);
+		kfree((char *) dma_malloc_pages);
+	}
+	scsi_dma_free_sectors += new_dma_sectors - dma_sectors;
+	dma_malloc_pages = new_dma_malloc_pages;
+	dma_sectors = new_dma_sectors;
+	scsi_need_isa_buffer = new_need_isa_buffer;
+
+	spin_unlock_irqrestore(&allocator_request_lock, flags);
+
+#ifdef DEBUG_INIT
+	printk("resize_dma_pool: dma free sectors   = %d\n", scsi_dma_free_sectors);
+	printk("resize_dma_pool: dma sectors        = %d\n", dma_sectors);
+	printk("resize_dma_pool: need isa buffers   = %d\n", scsi_need_isa_buffer);
+#endif
+}
+
+/*
+ * Function:    scsi_init_minimal_dma_pool
+ *
+ * Purpose:     Allocate a minimal (1-page) DMA pool.
+ *
+ * Arguments:   None.
+ *
+ * Lock status: No locks assumed to be held.  This function is SMP-safe.
+ *
+ * Returns:     Nothing
+ *
+ * Notes:       
+ */
+int scsi_init_minimal_dma_pool(void)
+{
+	unsigned long size;
+	unsigned long flags;
+	int has_space = 0;
+
+	spin_lock_irqsave(&allocator_request_lock, flags);
+
+	dma_sectors = PAGE_SIZE / SECTOR_SIZE;
+	scsi_dma_free_sectors = dma_sectors;
+	/*
+	 * Set up a minimal DMA buffer list - this will be used during scan_scsis
+	 * in some cases.
+	 */
+
+	/* One bit per sector to indicate free/busy */
+	size = (dma_sectors / SECTORS_PER_PAGE) * sizeof(FreeSectorBitmap);
+	dma_malloc_freelist = (FreeSectorBitmap *)
+	    kmalloc(size, GFP_ATOMIC);
+	if (dma_malloc_freelist) {
+                memset(dma_malloc_freelist, 0, size);
+		/* One pointer per page for the page list */
+		dma_malloc_pages = (unsigned char **) kmalloc(
+                        (dma_sectors / SECTORS_PER_PAGE) * sizeof(*dma_malloc_pages),
+							     GFP_ATOMIC);
+		if (dma_malloc_pages) {
+                        memset(dma_malloc_pages, 0, size);
+			dma_malloc_pages[0] = (unsigned char *)
+			    __get_free_pages(GFP_ATOMIC | GFP_DMA, 0);
+			if (dma_malloc_pages[0])
+				has_space = 1;
+		}
+	}
+	if (!has_space) {
+		if (dma_malloc_freelist) {
+			kfree((char *) dma_malloc_freelist);
+			if (dma_malloc_pages)
+				kfree((char *) dma_malloc_pages);
+		}
+		spin_unlock_irqrestore(&allocator_request_lock, flags);
+		printk("scsi::init_module: failed, out of memory\n");
+		return 1;
+	}
+
+	spin_unlock_irqrestore(&allocator_request_lock, flags);
+	return 0;
+}

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