2012-05-16 17:07:06

by Tim Chen

[permalink] [raw]
Subject: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

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 <[email protected]>
---
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 <[email protected]>
+ *
+ * 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 <[email protected]>");
+
+#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);


2012-05-16 17:18:30

by Joe Perches

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, 2012-05-16 at 10:07 -0700, Tim Chen wrote:
> 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.

just trivia:

> diff --git a/drivers/scsi/scsi_ram.c b/drivers/scsi/scsi_ram.c
[]
> +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);

Probably ignored a warning here. No comma after KERN_WARNING.

I'd add #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt before
any include and use pr_<level> instead.

[]

> +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);

No need for DRV_NAME when using pr_fmt

[]

> +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);

here too, etc...

2012-05-16 17:41:20

by Bart Van Assche

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On 05/16/12 17:07, Tim Chen wrote:

> +/*
> + * 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;
> +}


Please drop these functions and use get/put_unaligned_be*() instead.
Maybe it's a good idea to add get/put_unaligned_be24() helper functions
in the appropriate <linux/unaligned/...> header file - there is more
SCSI code that stores and retrieves 24-bit numbers.

Thanks,

Bart.

2012-05-16 19:02:15

by Tim Chen

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, 2012-05-16 at 10:18 -0700, Joe Perches wrote:
> On Wed, 2012-05-16 at 10:07 -0700, Tim Chen wrote:
> > 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.
>
> just trivia:
>
> > diff --git a/drivers/scsi/scsi_ram.c b/drivers/scsi/scsi_ram.c
> []
> > +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);
>
> Probably ignored a warning here. No comma after KERN_WARNING.
>
> I'd add #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt before
> any include and use pr_<level> instead.
>
> []
>


Thanks for your feedback. I've updated the driver as suggested.

Tim

---
Signed-off-by: Tim Chen <[email protected]>
---
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..f7d17cd
--- /dev/null
+++ b/drivers/scsi/scsi_ram.c
@@ -0,0 +1,621 @@
+/*
+ * 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 <[email protected]>
+ *
+ * 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
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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 <[email protected]>");
+
+#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)
+{
+ pr_warn("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("Queueing command\n");
+ 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("slave_alloc %d:%d\n", 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("slave_destroy %d:%d\n", 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("eh_host_reset_handler\n");
+ 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);

2012-05-16 19:31:59

by chetan L

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, May 16, 2012 at 1:07 PM, Tim Chen <[email protected]> wrote:

> +       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);

The spin_[un]lock/ variants don't match.


> +static int scsi_ram_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *cmnd)
....

> +       if (use_thread) {
> +               spin_lock_irqsave(shost->host_lock, flags);

> +               if (list_empty(&ram_device->commands))
> +                       wake_up_process(ram_device->thread);

Didn't look in detail but if the queue is empty then why would you
want to wake up the kthread? What if you just wake_up after
list_add_tail below?


> +               list_add_tail(&ram_cmnd->queue, &ram_device->commands);
> +               spin_unlock_irqrestore(shost->host_lock, flags);
> +       } else {
> +               scsi_ram_execute_command(cmnd);
> +       }


Chetan Loke

2012-05-16 19:34:12

by Matthew Wilcox

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, May 16, 2012 at 03:31:55PM -0400, chetan loke wrote:
> > + ? ? ? ? ? ? ? if (list_empty(&ram_device->commands))
> > + ? ? ? ? ? ? ? ? ? ? ? wake_up_process(ram_device->thread);
>
> Didn't look in detail but if the queue is empty then why would you
> want to wake up the kthread? What if you just wake_up after
> list_add_tail below?

If the list is non-empty, then the kthread has already been woken up
and doesn't need to be woken again.

> > + ? ? ? ? ? ? ? list_add_tail(&ram_cmnd->queue, &ram_device->commands);
> > + ? ? ? ? ? ? ? spin_unlock_irqrestore(shost->host_lock, flags);
> > + ? ? ? } else {
> > + ? ? ? ? ? ? ? scsi_ram_execute_command(cmnd);
> > + ? ? ? }
>
>
> Chetan Loke

2012-05-16 19:38:05

by chetan L

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, May 16, 2012 at 3:35 PM, Matthew Wilcox <[email protected]> wrote:
> On Wed, May 16, 2012 at 03:31:55PM -0400, chetan loke wrote:
>> > +               if (list_empty(&ram_device->commands))
>> > +                       wake_up_process(ram_device->thread);
>>
>> Didn't look in detail but if the queue is empty then why would you
>> want to wake up the kthread? What if you just wake_up after
>> list_add_tail below?
>
> If the list is non-empty, then the kthread has already been woken up
> and doesn't need to be woken again.

Sorry, not able to follow. wait_even_interruptible will put kthread to
sleep. So how will it be already awake?

2012-05-16 19:42:39

by Matthew Wilcox

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, May 16, 2012 at 03:37:56PM -0400, chetan loke wrote:
> On Wed, May 16, 2012 at 3:35 PM, Matthew Wilcox <[email protected]> wrote:
> > On Wed, May 16, 2012 at 03:31:55PM -0400, chetan loke wrote:
> >> > + ? ? ? ? ? ? ? if (list_empty(&ram_device->commands))
> >> > + ? ? ? ? ? ? ? ? ? ? ? wake_up_process(ram_device->thread);
> >>
> >> Didn't look in detail but if the queue is empty then why would you
> >> want to wake up the kthread? What if you just wake_up after
> >> list_add_tail below?
> >
> > If the list is non-empty, then the kthread has already been woken up
> > and doesn't need to be woken again.
>
> Sorry, not able to follow. wait_even_interruptible will put kthread to
> sleep. So how will it be already awake?

Consider the following:

CPU 0 CPU 1
->queuecommand
lock
wakes kthread
queues command 1
unlock

->queuecommand
lock
kthread wakes
lock
queues command 2
unlock
dequeues command 1
dequeues command 2
unlock


See? No need to wake the kthread *if there's already something on the
queue*, because you know it was already woken by whoever put the first
command on the queue.

2012-05-16 19:54:10

by chetan L

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, May 16, 2012 at 3:43 PM, Matthew Wilcox <[email protected]> wrote:
> On Wed, May 16, 2012 at 03:37:56PM -0400, chetan loke wrote:
>> On Wed, May 16, 2012 at 3:35 PM, Matthew Wilcox <[email protected]> wrote:
>> > On Wed, May 16, 2012 at 03:31:55PM -0400, chetan loke wrote:
>> >> > +               if (list_empty(&ram_device->commands))
>> >> > +                       wake_up_process(ram_device->thread);
>> >>
>> >> Didn't look in detail but if the queue is empty then why would you
>> >> want to wake up the kthread? What if you just wake_up after
>> >> list_add_tail below?
>> >
>> > If the list is non-empty, then the kthread has already been woken up
>> > and doesn't need to be woken again.
>>
>> Sorry, not able to follow. wait_even_interruptible will put kthread to
>> sleep. So how will it be already awake?
>
> Consider the following:
>
> CPU 0                   CPU 1
> ->queuecommand
> lock
> wakes kthread
> queues command 1
> unlock
>
> ->queuecommand
> lock
>                        kthread wakes
>                        lock
> queues command 2
> unlock
>                        dequeues command 1
>                        dequeues command 2
>                        unlock
>
>
> See?  No need to wake the kthread *if there's already something on the
> queue*, because you know it was already woken by whoever put the first
> command on the queue.

I thought that 'scsi_ram_device_thread' has a
'wait_event_interruptible' call if it sees that the list is empty. I
don't see that call that's why the confusion. Either queuecmd or
kthread will get the lock and so why keep the kthread spinning in that
while loop if there's nothing to do?

2012-05-16 20:05:43

by Tim Chen

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, 2012-05-16 at 15:31 -0400, chetan loke wrote:
> On Wed, May 16, 2012 at 1:07 PM, Tim Chen <[email protected]> wrote:
>
> > + 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);
>
> The spin_[un]lock/ variants don't match.
>

Thanks. Fixed it in the updated driver below.

>
> > +static int scsi_ram_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *cmnd)
> ....
>
> > + if (use_thread) {
> > + spin_lock_irqsave(shost->host_lock, flags);
>
> > + if (list_empty(&ram_device->commands))
> > + wake_up_process(ram_device->thread);
>
> Didn't look in detail but if the queue is empty then why would you
> want to wake up the kthread? What if you just wake_up after
> list_add_tail below?
>

The other kthread is woken up to dequeue and execute the command.

Tim

---
Signed-off-by: Tim Chen <[email protected]>
---
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..5076e7a
--- /dev/null
+++ b/drivers/scsi/scsi_ram.c
@@ -0,0 +1,621 @@
+/*
+ * 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 <[email protected]>
+ *
+ * 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
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#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 <[email protected]>");
+
+#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)
+{
+ pr_warn("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_irqrestore(host->host_lock, flags);
+ 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("Queueing command\n");
+ 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("slave_alloc %d:%d\n", 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("slave_destroy %d:%d\n", 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("eh_host_reset_handler\n");
+ 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);

2012-05-16 20:21:21

by Matthew Wilcox

[permalink] [raw]
Subject: Re: SCSI RAM driver ported to 3.3 kernel for file system and I/O testing

On Wed, May 16, 2012 at 03:54:06PM -0400, chetan loke wrote:
> I thought that 'scsi_ram_device_thread' has a
> 'wait_event_interruptible' call if it sees that the list is empty. I
> don't see that call that's why the confusion. Either queuecmd or
> kthread will get the lock and so why keep the kthread spinning in that
> while loop if there's nothing to do?

It doesn't spin, it sleeps:

+ spin_lock_irqsave(host->host_lock, flags);
+ if (list_empty(&ram_device->commands)) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_unlock_irqrestore(host->host_lock, flags);
+ schedule();
+ continue;
+ }