SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



I've ported Matthew's scsi RAM driver (originally posted in
http://marc.info/?l=linux-scsi&m=120331663227540&w=2) to the 3.3 kernel.
This could be useful for people interested in doing performance tests on
the I/O stack and file system by emulating a very fast RAM speed scsi
device.

Thanks.

Tim

---
Signed-off-by: Tim Chen <tim.c.chen@xxxxxxxxxxxxxxx>
---
diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig
index 16570aa..8aa2eae 100644
--- a/drivers/scsi/Kconfig
+++ b/drivers/scsi/Kconfig
@@ -1621,6 +1621,16 @@ config SCSI_DEBUG
 	  information. This driver is primarily of use to those testing the
 	  SCSI and block subsystems. If unsure, say N.
 
+config SCSI_RAM
+	tristate "SCSI RAM-based host"
+	depends on SCSI
+	help
+	  This driver simulates a host adapter with one disc attached.
+	  The primary purpose of this driver is for performance testing
+	  of the fs/block/scsi stack; as such it attempts to simulate a
+	  disc which is as fast as RAM.  If you are unsure how to answer
+	  this question, say N.
+
 config SCSI_MESH
 	tristate "MESH (Power Mac internal SCSI) support"
 	depends on PPC32 && PPC_PMAC && SCSI
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 2b88749..394c1c4 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -157,6 +157,7 @@ obj-$(CONFIG_SCSI_OSD_INITIATOR) += osd/
 
 # This goes last, so that "real" scsi devices probe earlier
 obj-$(CONFIG_SCSI_DEBUG)	+= scsi_debug.o
+obj-$(CONFIG_SCSI_RAM)		+= scsi_ram.o
 
 obj-$(CONFIG_SCSI_WAIT_SCAN)	+= scsi_wait_scan.o
 
diff --git a/drivers/scsi/scsi_ram.c b/drivers/scsi/scsi_ram.c
new file mode 100644
index 0000000..d128050
--- /dev/null
+++ b/drivers/scsi/scsi_ram.c
@@ -0,0 +1,620 @@
+/*
+ * scsi_ram.c - A RAM-based SCSI driver for Linux.
+ *
+ * This driver is intended to run as fast as possible, hence the options
+ * to discard writes and reads.
+ * By default, it'll allocate half a gigabyte of RAM to use as a ramdisc;
+ * you can change this with the `capacity' module parameter.
+ *
+ * (C) Copyright 2012 Intel Corporation
+ * Author: Matthew Wilcox <willy@xxxxxxxxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+
+#undef DEBUG
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_dbg.h>
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Matthew Wilcox <willy@xxxxxxxxxxxxxxx>");
+
+#define DRV_NAME "scsi_ram"
+
+static unsigned int sector_size = 512;
+module_param(sector_size, uint, 0444);
+MODULE_PARM_DESC(sector_size, "Size of sectors");
+
+static unsigned int capacity = 1024 * 1024;
+module_param(capacity, uint, 0444);
+MODULE_PARM_DESC(capacity, "Number of logical blocks in device");
+
+static int throw_away_writes;
+module_param(throw_away_writes, int, 0644);
+MODULE_PARM_DESC(throw_away_writes, "Discard all writes to the device");
+
+static int throw_away_reads;
+module_param(throw_away_reads, int, 0644);
+MODULE_PARM_DESC(throw_away_reads, "Don't actually read data from the device");
+
+static int use_thread = 1;
+module_param(use_thread, int, 0644);
+MODULE_PARM_DESC(use_thread, "Use a separate thread to do data accesses");
+
+static void copy_buffer(struct scsi_cmnd *cmnd, char *buf, int len)
+{
+	char *p;
+	struct scatterlist *sg;
+	int i;
+
+	scsi_for_each_sg(cmnd, sg, scsi_sg_count(cmnd), i) {
+		int tocopy = sg->length;
+		if (tocopy > len)
+			tocopy = len;
+
+		p = kmap_atomic(sg_page(sg), KM_USER0);
+		memcpy(p + sg->offset, buf, tocopy);
+		kunmap_atomic(p, KM_USER0);
+
+		len -= tocopy;
+		if (!len)
+			break;
+		buf += tocopy;
+	}
+
+	scsi_set_resid(cmnd, len);
+}
+
+static char inquiry[57] = {
+	0, 0, 5, 0x22, 52, 0, 0, 0x0a,			/* 0-7 */
+	'L', 'i', 'n', 'u', 'x', ' ', ' ', ' ',		/* 8-15 */
+	'R', 'A', 'M', ' ', 'D', 'r', 'i', 'v',		/* 16-23 */
+	'e', ' ', ' ', ' ', ' ', ' ', ' ', ' ',		/* 24-31 */
+	'0', '.', '0', '1', 0, 0, 0, 0,			/* 32-39 */
+	0, 0, 0, 0, 0, 0, 0, 0,				/* 40-47 */
+	0, 0, 0, 0, 0, 0, 0, 0,				/* 48-55 */
+	0						/* 56 */
+};
+
+static char report_luns[] = {
+	0, 0, 0, 8, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0
+};
+
+/*
+ * SCSI requires quantities to be written MSB.  They're frequently misaligned,
+ * so don't mess about with cpu_to_beN, just access it byte-wise
+ */
+static void scsi_ram_put_u32(unsigned char *addr, unsigned int data)
+{
+	addr[0] = data >> 24;
+	addr[1] = data >> 16;
+	addr[2] = data >> 8;
+	addr[3] = data;
+}
+
+static unsigned int scsi_ram_get_u16(unsigned char *addr)
+{
+	unsigned int data;
+	data = addr[0] << 8;
+	data |= addr[1];
+
+	return data;
+}
+
+static unsigned int scsi_ram_get_u24(unsigned char *addr)
+{
+	unsigned int data;
+	data = addr[0] << 16;
+	data |= addr[1] << 8;
+	data |= addr[2];
+
+	return data;
+}
+
+static unsigned int scsi_ram_get_u32(unsigned char *addr)
+{
+	unsigned int data;
+	data = addr[0] << 24;
+	data |= addr[1] << 16;
+	data |= addr[2] << 8;
+	data |= addr[3];
+
+	return data;
+}
+
+static void scsi_ram_setup_sense(struct scsi_cmnd *cmnd, unsigned char key,
+				unsigned char asc, unsigned char ascq)
+{
+	if (0) {
+		cmnd->sense_buffer[0] = 0x72;
+		cmnd->sense_buffer[1] = key;
+		cmnd->sense_buffer[2] = asc;
+		cmnd->sense_buffer[3] = ascq;
+		cmnd->sense_buffer[7] = 0;
+	} else {
+		cmnd->sense_buffer[0] = 0x70;
+		cmnd->sense_buffer[1] = 0;
+		cmnd->sense_buffer[2] = key;
+		cmnd->sense_buffer[7] = 11;
+		cmnd->sense_buffer[12] = asc;
+		cmnd->sense_buffer[13] = ascq;
+	}
+}
+
+static void scsi_ram_inquiry(struct scsi_cmnd *cmnd)
+{
+	if (cmnd->cmnd[1] & 1) {
+		switch (cmnd->cmnd[2]) {
+		default:
+			scsi_ram_setup_sense(cmnd, ILLEGAL_REQUEST, 0x24, 0);
+			cmnd->result = SAM_STAT_CHECK_CONDITION;
+		}
+	} else {
+		copy_buffer(cmnd, inquiry, sizeof(inquiry));
+	}
+}
+
+static void scsi_ram_read_capacity(struct scsi_cmnd *cmnd)
+{
+	char buf[8];
+	scsi_ram_put_u32(buf, capacity - 1);
+	scsi_ram_put_u32(buf + 4, sector_size);
+	copy_buffer(cmnd, buf, sizeof(buf));
+}
+
+static void scsi_ram_mode_sense(struct scsi_cmnd *cmnd)
+{
+
+}
+
+
+static struct page **scsi_ram_data_array;
+
+/*
+ * We could steal the pages we need from the requests as they come in, which
+ * is what rd.c does.  However, that's not a realistic simulator of how a
+ * device would work.  We want the request pages to get freed and go back into
+ * the page allocator.
+ */
+static int scsi_ram_alloc_data(void)
+{
+	unsigned long pages = capacity / PAGE_SIZE * sector_size;
+	unsigned int i;
+
+	scsi_ram_data_array = kmalloc(pages * sizeof(void *), GFP_KERNEL);
+	if (!scsi_ram_data_array)
+		return -ENOMEM;
+
+	for (i = 0; i < pages; i++) {
+		struct page *page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM);
+		scsi_ram_data_array[i] = page;
+
+		/* scsi_ram_free_data will be called on failure */
+		if (!page)
+			return -ENOMEM;
+		clear_highpage(page);
+	}
+
+	return 0;
+}
+
+static void scsi_ram_free_data(void)
+{
+	unsigned long pages = capacity / PAGE_SIZE * sector_size;
+	unsigned int i;
+
+	if (!scsi_ram_data_array)
+		return;
+
+	for (i = 0; i < pages; i++) {
+		struct page *page = scsi_ram_data_array[i];
+		if (!page)
+			break;
+		__free_page(page);
+	}
+
+	kfree(scsi_ram_data_array);
+}
+
+static void scsi_ram_too_big(struct scsi_cmnd *cmnd, unsigned int start,
+				unsigned int len)
+{
+	printk(KERN_WARNING, "Request exceeded device capacity! %u %u\n", start, len);
+	scsi_ram_setup_sense(cmnd, ILLEGAL_REQUEST, 0x21, 0);
+	cmnd->result = SAM_STAT_CHECK_CONDITION;
+}
+
+static void *get_data_page(unsigned int pfn)
+{
+	return kmap_atomic(scsi_ram_data_array[pfn], KM_USER0);
+}
+
+static void put_data_page(void *addr)
+{
+	kunmap_atomic(addr, KM_USER0);
+}
+
+static void *get_sg_page(struct page *page)
+{
+	return kmap_atomic(page, KM_USER1);
+}
+
+static void put_sg_page(void *addr)
+{
+	kunmap_atomic(addr, KM_USER1);
+}
+
+static void scsi_ram_read(struct scsi_cmnd *cmnd, unsigned int startB,
+							unsigned int lenB)
+{
+	unsigned long start = startB * sector_size;
+	unsigned long len = lenB * sector_size;
+	struct scatterlist *sg;
+	unsigned i, from_off = start % PAGE_SIZE, data_pfn = start / PAGE_SIZE;
+
+	if (startB > capacity || (startB + lenB) > capacity)
+		return scsi_ram_too_big(cmnd, startB, lenB);
+
+	if (throw_away_reads)
+		return;
+
+	scsi_for_each_sg(cmnd, sg, scsi_sg_count(cmnd), i) {
+		struct page *sgpage = sg_page(sg);
+		unsigned int to_off = sg->offset;
+		unsigned int sg_copy = sg->length;
+		if (sg_copy > len)
+			sg_copy = len;
+		len -= sg_copy;
+
+		while (sg_copy > 0) {
+			char *vto, *vfrom;
+			unsigned int page_copy;
+
+			if (from_off > to_off)
+				page_copy = PAGE_SIZE - from_off;
+			else
+				page_copy = PAGE_SIZE - to_off;
+			if (page_copy > sg_copy)
+				page_copy = sg_copy;
+
+			vfrom = get_data_page(data_pfn);
+			vto = get_sg_page(sgpage);
+			memcpy(vto + to_off, vfrom + from_off, page_copy);
+			put_sg_page(vto);
+			put_data_page(vfrom);
+
+			from_off += page_copy;
+			if (from_off == PAGE_SIZE) {
+				from_off = 0;
+				data_pfn++;
+			}
+			to_off += page_copy;
+			if (to_off == PAGE_SIZE) {
+				to_off = 0;
+				sgpage++;
+			}
+			sg_copy -= page_copy;
+		}
+		if (!len)
+			break;
+	}
+}
+
+static void scsi_ram_write(struct scsi_cmnd *cmnd, unsigned int startB,
+							unsigned int lenB)
+{
+	unsigned long start = startB * sector_size;
+	unsigned long len = lenB * sector_size;
+	struct scatterlist *sg;
+	unsigned i, to_off = start % PAGE_SIZE, data_pfn = start / PAGE_SIZE;
+
+	if (startB > capacity || (startB + lenB) > capacity)
+		return scsi_ram_too_big(cmnd, startB, lenB);
+
+	if (throw_away_writes)
+		return;
+
+	scsi_for_each_sg(cmnd, sg, scsi_sg_count(cmnd), i) {
+		struct page *sgpage = sg_page(sg);
+		unsigned int from_off = sg->offset;
+		unsigned int sg_copy = sg->length;
+		if (sg_copy > len)
+			sg_copy = len;
+		len -= sg_copy;
+
+		while (sg_copy > 0) {
+			char *vto, *vfrom;
+			unsigned int page_copy;
+
+			if (from_off > to_off)
+				page_copy = PAGE_SIZE - from_off;
+			else
+				page_copy = PAGE_SIZE - to_off;
+			if (page_copy > sg_copy)
+				page_copy = sg_copy;
+
+			vfrom = get_sg_page(sgpage);
+			vto = get_data_page(data_pfn);
+			memcpy(vto + to_off, vfrom + from_off, page_copy);
+			put_data_page(vto);
+			put_sg_page(vfrom);
+
+			from_off += page_copy;
+			if (from_off == PAGE_SIZE) {
+				from_off = 0;
+				sgpage++;
+			}
+			to_off += page_copy;
+			if (to_off == PAGE_SIZE) {
+				to_off = 0;
+				data_pfn++;
+			}
+			sg_copy -= page_copy;
+		}
+		if (!len)
+			break;
+	}
+}
+
+static void scsi_ram_read_6(struct scsi_cmnd *cmnd)
+{
+	unsigned int first_block = scsi_ram_get_u24(cmnd->cmnd + 1) & 0x1fffff;
+	unsigned int length = cmnd->cmnd[4];
+	if (!length)
+		length = 256;
+	scsi_ram_read(cmnd, first_block, length);
+}
+
+static void scsi_ram_read_10(struct scsi_cmnd *cmnd)
+{
+	unsigned int first_block = scsi_ram_get_u32(cmnd->cmnd + 2);
+	unsigned int length = scsi_ram_get_u16(cmnd->cmnd + 7);
+	scsi_ram_read(cmnd, first_block, length);
+}
+
+static void scsi_ram_write_6(struct scsi_cmnd *cmnd)
+{
+	unsigned int first_block = scsi_ram_get_u24(cmnd->cmnd + 1) & 0x1fffff;
+	unsigned int length = cmnd->cmnd[4];
+	if (!length)
+		length = 256;
+	scsi_ram_write(cmnd, first_block, length);
+}
+
+static void scsi_ram_write_10(struct scsi_cmnd *cmnd)
+{
+	unsigned int first_block = scsi_ram_get_u32(cmnd->cmnd + 2);
+	unsigned int length = scsi_ram_get_u16(cmnd->cmnd + 7);
+	scsi_ram_write(cmnd, first_block, length);
+}
+
+static void scsi_ram_execute_command(struct scsi_cmnd *cmnd)
+{
+#ifdef DEBUG
+	scsi_print_command(cmnd);
+#endif
+
+	switch (cmnd->cmnd[0]) {
+	case INQUIRY:
+		scsi_ram_inquiry(cmnd);
+		break;
+	case REPORT_LUNS:
+		copy_buffer(cmnd, report_luns, sizeof(report_luns));
+		break;
+	case TEST_UNIT_READY:
+		cmnd->result = 0;
+		break;
+	case READ_CAPACITY:
+		scsi_ram_read_capacity(cmnd);
+		break;
+	case MODE_SENSE:
+		scsi_ram_mode_sense(cmnd);
+		break;
+	case READ_6:
+		scsi_ram_read_6(cmnd);
+		break;
+	case READ_10:
+		scsi_ram_read_10(cmnd);
+		break;
+	case WRITE_6:
+		scsi_ram_write_6(cmnd);
+		break;
+	case WRITE_10:
+		scsi_ram_write_10(cmnd);
+		break;
+	default:
+		cmnd->result = DID_ABORT << 16;
+	}
+
+	cmnd->scsi_done(cmnd);
+}
+
+struct scsi_ram_device {
+	struct list_head commands;
+	struct Scsi_Host *host;
+	struct task_struct *thread;
+};
+
+static struct scsi_ram_device *scsi_ram_devices[16];
+
+/* Overrides scsi_pointer */
+struct scsi_ram_cmnd {
+	struct list_head queue;
+};
+
+static int scsi_ram_device_thread(void *data)
+{
+	struct scsi_ram_device *ram_device = data;
+	struct Scsi_Host *host = ram_device->host;
+	unsigned long flags;
+
+	while (!kthread_should_stop()) {
+		struct scsi_cmnd *cmnd;
+		struct scsi_ram_cmnd *ram_cmnd;
+
+		spin_lock_irqsave(host->host_lock, flags);
+		if (list_empty(&ram_device->commands)) {
+			set_current_state(TASK_INTERRUPTIBLE);
+			spin_unlock_irq(host->host_lock);
+			schedule();
+			continue;
+		}
+
+		ram_cmnd = list_first_entry(&ram_device->commands,
+						struct scsi_ram_cmnd, queue);
+		list_del(&ram_cmnd->queue);
+		spin_unlock_irqrestore(host->host_lock, flags);
+
+		cmnd = container_of((struct scsi_pointer *)ram_cmnd,
+							struct scsi_cmnd, SCp);
+		scsi_ram_execute_command(cmnd);
+	}
+	__set_current_state(TASK_RUNNING);
+
+	return 0;
+}
+
+static int scsi_ram_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *cmnd)
+{
+	struct scsi_ram_cmnd *ram_cmnd = (void *)&cmnd->SCp;
+	struct scsi_ram_device *ram_device = scsi_ram_devices[cmnd->device->id];
+	unsigned long flags;
+
+	pr_debug("%s: Queueing command\n", DRV_NAME);
+	if (!ram_device)
+		goto bad_device;
+
+	if (use_thread) {
+		spin_lock_irqsave(shost->host_lock, flags);
+		if (list_empty(&ram_device->commands))
+			wake_up_process(ram_device->thread);
+		list_add_tail(&ram_cmnd->queue, &ram_device->commands);
+		spin_unlock_irqrestore(shost->host_lock, flags);
+	} else {
+		scsi_ram_execute_command(cmnd);
+	}
+
+	return 0;
+
+ bad_device:
+	cmnd->result = DID_BAD_TARGET << 16;
+	cmnd->scsi_done(cmnd);
+	return 0;
+}
+
+static int scsi_ram_slave_alloc(struct scsi_device *sdev)
+{
+	struct scsi_ram_device *ram_device;
+
+	pr_debug("%s: slave_alloc %d:%d\n", DRV_NAME, sdev->id, sdev->lun);
+
+	/* For the moment, create only device 0, lun 0 */
+	if (sdev->id != 0)
+		return 0;
+	if (sdev->lun != 0)
+		return 0;
+
+	ram_device = kmalloc(sizeof(*ram_device), GFP_KERNEL);
+	if (!ram_device)
+		goto nomem;
+	INIT_LIST_HEAD(&ram_device->commands);
+	ram_device->host = sdev->host;
+	if (scsi_ram_alloc_data())
+		goto nomem;
+	ram_device->thread = kthread_run(scsi_ram_device_thread, ram_device,
+					 "scsi_ram_%d", sdev->id);
+	if (IS_ERR(ram_device->thread))
+		goto nomem;
+
+	scsi_ram_devices[sdev->id] = ram_device;
+	return 0;
+
+ nomem:
+	scsi_ram_free_data();
+	kfree(ram_device);
+	return -ENOMEM;
+}
+
+static void scsi_ram_slave_destroy(struct scsi_device *sdev)
+{
+	struct scsi_ram_device *ram_device = scsi_ram_devices[sdev->id];
+
+	pr_debug("%s: slave_destroy %d:%d\n", DRV_NAME, sdev->id, sdev->lun);
+	if (!ram_device)
+		return;
+	if (sdev->lun != 0)
+		return;
+
+	kthread_stop(ram_device->thread);
+	scsi_ram_free_data();
+	kfree(ram_device);
+	scsi_ram_devices[sdev->id] = NULL;
+}
+
+static int scsi_ram_eh_host_reset_handler(struct scsi_cmnd *scmd)
+{
+	pr_debug("%s: eh_host_reset_handler\n", DRV_NAME);
+	return 0;
+}
+
+static struct scsi_host_template scsi_ram_template = {
+	.proc_name = DRV_NAME,
+	.name = DRV_NAME,
+	.queuecommand = scsi_ram_queuecommand,
+	.eh_host_reset_handler = scsi_ram_eh_host_reset_handler,
+	.slave_alloc = scsi_ram_slave_alloc,
+	.slave_destroy = scsi_ram_slave_destroy,
+	.can_queue = 64,
+	.this_id = 7,
+	.sg_tablesize = SG_ALL,
+	.max_sectors = 1024,
+	.cmd_per_lun = 64,
+	.skip_settle_delay = 1,
+	.use_clustering = DISABLE_CLUSTERING,
+};
+
+static struct Scsi_Host *scsi_ram_host;
+
+static int __init scsi_ram_init(void)
+{
+	int error;
+
+	scsi_ram_host = scsi_host_alloc(&scsi_ram_template, 0);
+	if (!scsi_ram_host)
+		return -ENOMEM;
+
+	error = scsi_add_host(scsi_ram_host, NULL);
+	if (error)
+		goto free_host;
+
+	scsi_scan_host(scsi_ram_host);
+	return 0;
+
+ free_host:
+	scsi_host_put(scsi_ram_host);
+	return error;
+}
+
+static void __exit scsi_ram_exit(void)
+{
+	scsi_remove_host(scsi_ram_host);
+	scsi_host_put(scsi_ram_host);
+}
+
+module_init(scsi_ram_init);
+module_exit(scsi_ram_exit);


--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Ext4 Filesystem]     [Union Filesystem]     [Filesystem Testing]     [Ceph Users]     [Ecryptfs]     [AutoFS]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux Cachefs]     [Reiser Filesystem]     [Linux RAID]     [Samba]     [Device Mapper]     [CEPH Development]
  Powered by Linux