1403 lines
36 KiB
Diff
Executable File
1403 lines
36 KiB
Diff
Executable File
Index: linux-2.6.25-source/drivers/scsi/coscsi.c
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.25-source/drivers/scsi/coscsi.c
|
|
@@ -0,0 +1,1179 @@
|
|
+/*
|
|
+ * Copyright (C) 2008 Steve Shoecraft
|
|
+ *
|
|
+ * Cooperative Linux SCSI Driver implementation
|
|
+ */
|
|
+
|
|
+#include <linux/module.h>
|
|
+#include <linux/interrupt.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#include <asm/system.h>
|
|
+#include <asm/dma-mapping.h>
|
|
+#include <asm/string.h>
|
|
+
|
|
+#include <scsi/scsi.h>
|
|
+#include <scsi/scsi_cmnd.h>
|
|
+#include <scsi/scsi_device.h>
|
|
+#include <scsi/scsi_host.h>
|
|
+
|
|
+#include <linux/cooperative.h>
|
|
+#include <linux/cooperative_internal.h>
|
|
+#include <linux/cooperative_pci.h>
|
|
+
|
|
+#include <linux/cdrom.h>
|
|
+#include <scsi/coscsi.h>
|
|
+
|
|
+/* Special pass through type */
|
|
+#define TYPE_PASS 0x1f
|
|
+
|
|
+#define COSCSI_VERSION "1.01"
|
|
+
|
|
+MODULE_AUTHOR("Steve Shoecraft <sshoecraft@earthlink.net>");
|
|
+MODULE_DESCRIPTION("Cooperative Linux SCSI Driver " COSCSI_VERSION);
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
+#define COSCSI_DUMP_STATS 0
|
|
+#define COSCSI_DUMP_CONFIG 0
|
|
+#define COSCSI_DUMP_PARAMS 0
|
|
+
|
|
+#ifdef min
|
|
+#undef min
|
|
+#endif
|
|
+#define min(a,b) ((a) < (b) ? (a) : (b))
|
|
+
|
|
+/* Keep sg size to <= 1 page */
|
|
+#define COSCSI_SGSIZE ( 4096 / sizeof(struct scatterlist) )
|
|
+
|
|
+#define COSCSI_DEBUG 0
|
|
+#define COSCSI_DEBUG_PCI 0
|
|
+#define COSCSI_DEBUG_ISR 0
|
|
+#define COSCSI_DEBUG_HOST 0
|
|
+#define COSCSI_DEBUG_XFER 0
|
|
+#define COSCSI_DEBUG_COMM 0
|
|
+#define COSCSI_DEBUG_INQ 0
|
|
+#define COSCSI_DEBUG_SENSE 0
|
|
+#define COSCSI_DEBUG_PASS 0
|
|
+
|
|
+#if COSCSI_DEBUG_XFER || COSCSI_DEBUG_COMM || COSCSI_DEBUG_SENSE
|
|
+#define DUMP_DATA 1
|
|
+#else
|
|
+#define DUMP_DATA 0
|
|
+#endif
|
|
+
|
|
+/* OPs not found in scsi.h, use from cdrom.h */
|
|
+#define GET_CONFIGURATION GPCMD_GET_CONFIGURATION
|
|
+#define GET_EVENT_STATUS GPCMD_GET_EVENT_STATUS_NOTIFICATION
|
|
+#define READ_DISC_INFO GPCMD_READ_DISC_INFO
|
|
+
|
|
+/* Sense codes */
|
|
+#define LOGICAL_UNIT_NOT_READY 0x4
|
|
+#define INVALID_FIELD_IN_CDB 0x24
|
|
+#define MEDIUM_NOT_PRESENT 0x3a
|
|
+
|
|
+#include "coscsi_rom.h"
|
|
+
|
|
+struct coscsi_device {
|
|
+ int unit;
|
|
+ int type;
|
|
+ coscsi_rom_t *rom;
|
|
+ unsigned long flags;
|
|
+ unsigned long long max_lba;
|
|
+ unsigned long long size;
|
|
+ void *os_handle;
|
|
+ int prevent;
|
|
+ int key;
|
|
+ int asc;
|
|
+ int asq;
|
|
+ int debug;
|
|
+ char msg[192];
|
|
+};
|
|
+typedef struct coscsi_device coscsi_device_t;
|
|
+
|
|
+/* Device flags */
|
|
+enum DEVICE_FLAG {
|
|
+ DFLAG_NONE,
|
|
+ DFLAG_DEBUG, /* Allow debugging output for this dev */
|
|
+ DFLAG_OPEN, /* Prevent medium removal */
|
|
+ DFLAG_CHECK, /* Check condition */
|
|
+ DFLAG_PREVENT, /* Prevent medium removal */
|
|
+ DFLAG_EVENT, /* Outstanding Event */
|
|
+};
|
|
+
|
|
+#define DFLAG(d,f) ((d->flags & f) != 0)
|
|
+#define dprintk(m) if (DFLAG(dp, DFLAG_DEBUG)) printk(KERN_INFO "scsi%d: ", dp->unit), printk(KERN_INFO m)
|
|
+
|
|
+struct coscsi_worker {
|
|
+ coscsi_device_t *dp;
|
|
+ struct scsi_cmnd *scp;
|
|
+};
|
|
+typedef struct coscsi_worker coscsi_worker_t;
|
|
+
|
|
+/* Private info */
|
|
+char scsi_rev[5];
|
|
+static coscsi_device_t devices[CO_MODULE_MAX_COSCSI];
|
|
+
|
|
+#if DUMP_DATA
|
|
+static void _dump_data(int unit, char *str, void *data, int data_len) {
|
|
+ unsigned char *p;
|
|
+ int x,y,len;
|
|
+
|
|
+ printk(KERN_INFO "scsi%d: %s(%d bytes):\n",unit,str,data_len);
|
|
+ len = data_len;
|
|
+ p = data;
|
|
+ for(x=y=0; x < len; x++) {
|
|
+ printk(KERN_INFO " %02x", p[x]);
|
|
+ y++;
|
|
+ if (y > 15) {
|
|
+ printk(KERN_INFO "\n");
|
|
+ y = 0;
|
|
+ }
|
|
+ }
|
|
+ if (y) printk(KERN_INFO "\n");
|
|
+}
|
|
+#define dump_data(u, s,a,b) _dump_data(u,s,a,b)
|
|
+#else
|
|
+#define dump_data(u, s,a,b) /* noop */
|
|
+#endif
|
|
+
|
|
+static spinlock_t coscsi_isr_lock;
|
|
+
|
|
+static irqreturn_t coscsi_isr(int irq, void *dev_id)
|
|
+{
|
|
+ co_message_node_t *node_message;
|
|
+ co_linux_message_t *message;
|
|
+ co_scsi_intr_t *info;
|
|
+ struct scsi_cmnd *scp;
|
|
+
|
|
+ spin_lock(&coscsi_isr_lock);
|
|
+#if COSCSI_DEBUG_ISR
|
|
+ printk(KERN_INFO "coscsi_isr: getting messages!\n");
|
|
+#endif
|
|
+ while (co_get_message(&node_message, CO_DEVICE_SCSI)) {
|
|
+
|
|
+ message = (co_linux_message_t *)&node_message->msg.data;
|
|
+
|
|
+ info = (co_scsi_intr_t *) &message->data;
|
|
+ scp = info->ctx;
|
|
+ scp->result = info->result;
|
|
+ scsi_set_resid(scp, info->delta);
|
|
+#if COSCSI_DEBUG_ISR
|
|
+ printk(KERN_INFO "coscsi_isr: scp: %p result: %d, delta: %d\n", scp, info->result, info->delta);
|
|
+#endif
|
|
+ scp->scsi_done(scp);
|
|
+ co_free_message(node_message);
|
|
+ }
|
|
+ spin_unlock(&coscsi_isr_lock);
|
|
+
|
|
+ return IRQ_HANDLED;
|
|
+}
|
|
+
|
|
+/****************************************************************************************************
|
|
+ *
|
|
+ *
|
|
+ * HOST functions
|
|
+ *
|
|
+ *
|
|
+ ****************************************************************************************************/
|
|
+
|
|
+/*
|
|
+ * Open handle
|
|
+*/
|
|
+static int host_open(coscsi_device_t *dp) {
|
|
+ unsigned long flags;
|
|
+ int rc = 0;
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_open: handle: %p\n", dp->os_handle);
|
|
+#endif
|
|
+ if (!dp->os_handle) {
|
|
+ co_passage_page_assert_valid();
|
|
+ co_passage_page_acquire(&flags);
|
|
+ co_passage_page->operation = CO_OPERATION_DEVICE;
|
|
+ co_passage_page->params[0] = CO_DEVICE_SCSI;
|
|
+ co_passage_page->params[1] = CO_SCSI_OPEN;
|
|
+ co_passage_page->params[2] = dp->unit;
|
|
+
|
|
+ co_switch_wrapper();
|
|
+
|
|
+ rc = co_passage_page->params[0];
|
|
+ if (!rc) dp->os_handle = (void *) co_passage_page->params[1];
|
|
+ co_passage_page_release(flags);
|
|
+ }
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_open: rc: %d, handle: %p\n", rc, dp->os_handle);
|
|
+#endif
|
|
+ if (rc) printk(KERN_ERR "coscsi%d: unable to open device! rc: %x\n", dp->unit, rc);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Close handle
|
|
+*/
|
|
+static int host_close(coscsi_device_t *dp) {
|
|
+ unsigned long flags;
|
|
+ int rc;
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_close: handle: %p\n", dp->os_handle);
|
|
+#endif
|
|
+ if (!dp->os_handle) return 0;
|
|
+ co_passage_page_assert_valid();
|
|
+ co_passage_page_acquire(&flags);
|
|
+ co_passage_page->operation = CO_OPERATION_DEVICE;
|
|
+ co_passage_page->params[0] = CO_DEVICE_SCSI;
|
|
+ co_passage_page->params[1] = CO_SCSI_CLOSE;
|
|
+ co_passage_page->params[2] = dp->unit;
|
|
+
|
|
+ co_switch_wrapper();
|
|
+
|
|
+ rc = co_passage_page->params[0];
|
|
+ co_passage_page_release(flags);
|
|
+
|
|
+ dp->os_handle = 0;
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_close: rc: %d\n", rc);
|
|
+#endif
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+#if COSCSI_DUMP_STATS
|
|
+static unsigned int max_segs = 1;
|
|
+static unsigned int max_xfer = 4096;
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ * Read/Write block(s)
|
|
+*/
|
|
+
|
|
+static int host_rw(coscsi_worker_t *wp, unsigned long long lba, unsigned long num, int write)
|
|
+{
|
|
+ struct scatterlist *sg = scsi_sglist(wp->scp);
|
|
+ struct scsi_cmnd *scp = wp->scp;
|
|
+ unsigned long flags;
|
|
+ co_scsi_io_t *iop;
|
|
+ int count,rc,total;
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (wp->dp->debug) printk(KERN_INFO "host_rw: lba: %lld, sector_size: %d, num: %ld, write: %d\n",
|
|
+ lba, scp->device->sector_size, num, write);
|
|
+#endif
|
|
+
|
|
+ if (!wp->dp->os_handle) {
|
|
+ if (host_open(wp->dp))
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ /* XXX needed when clustering is enabled */
|
|
+ count = dma_map_sg(&scp->device->host->shost_gendev, sg, scsi_sg_count(scp), scp->sc_data_direction);
|
|
+
|
|
+ /* Get passage page */
|
|
+ co_passage_page_assert_valid();
|
|
+ co_passage_page_acquire(&flags);
|
|
+ co_passage_page->operation = CO_OPERATION_DEVICE;
|
|
+ co_passage_page->params[0] = CO_DEVICE_SCSI;
|
|
+ co_passage_page->params[1] = CO_SCSI_IO;
|
|
+ co_passage_page->params[2] = wp->dp->unit;
|
|
+
|
|
+ /* Setup iop */
|
|
+ iop = (co_scsi_io_t *) &co_passage_page->params[3];
|
|
+ iop->scp = scp;
|
|
+ iop->offset = lba * scp->device->sector_size;
|
|
+ iop->count = count;
|
|
+ iop->write = write;
|
|
+ iop->sg = scsi_sglist(scp);
|
|
+ iop->reqlen = total = num * scp->device->sector_size;
|
|
+
|
|
+ /* Do it */
|
|
+ co_switch_wrapper();
|
|
+
|
|
+ rc = co_passage_page->params[0];
|
|
+ co_passage_page_release(flags);
|
|
+
|
|
+ dma_unmap_sg(&scp->device->host->shost_gendev, sg, scsi_sg_count(scp), scp->sc_data_direction);
|
|
+
|
|
+#if COSCSI_DUMP_STATS
|
|
+ if (rc == GOOD) {
|
|
+ if (count > max_segs) {
|
|
+ max_segs = count;
|
|
+ printk(KERN_WARN "COSCSI: max_segs: %d\n", max_segs);
|
|
+ }
|
|
+
|
|
+ if (total > max_xfer) {
|
|
+ max_xfer = total;
|
|
+ printk(KERN_WARN "COSCSI: max_xfer: %dKB\n", max_xfer >> 10);
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (wp->dp->debug) printk(KERN_INFO "host_rw: rc: %d\n", rc);
|
|
+#endif
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int get_bs_bits(coscsi_device_t *dp, int sector_size) {
|
|
+ unsigned long mask = 0x80000000;
|
|
+ int bs_bits;
|
|
+
|
|
+ bs_bits = 31;
|
|
+ while(mask) {
|
|
+ if (sector_size & mask)
|
|
+ break;
|
|
+ mask >>= 1;
|
|
+ bs_bits--;
|
|
+ }
|
|
+
|
|
+ return bs_bits;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * File/Device size
|
|
+*/
|
|
+static int host_size(coscsi_device_t *dp, struct scsi_cmnd *scp) {
|
|
+ unsigned long long s;
|
|
+ unsigned long flags;
|
|
+ int rc, bits;
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_size: getting size...\n");
|
|
+#endif
|
|
+ if (!dp->os_handle) {
|
|
+ if (host_open(dp))
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ co_passage_page_assert_valid();
|
|
+ co_passage_page_acquire(&flags);
|
|
+ co_passage_page->operation = CO_OPERATION_DEVICE;
|
|
+ co_passage_page->params[0] = CO_DEVICE_SCSI;
|
|
+ co_passage_page->params[1] = CO_SCSI_SIZE;
|
|
+ co_passage_page->params[2] = dp->unit;
|
|
+
|
|
+ co_switch_wrapper();
|
|
+
|
|
+ rc = co_passage_page->params[0];
|
|
+ dp->size = *((unsigned long long *)&co_passage_page->params[1]);
|
|
+ co_passage_page_release(flags);
|
|
+
|
|
+ bits = get_bs_bits(dp, scp->device->sector_size);
|
|
+
|
|
+ s = dp->size >> bits;
|
|
+ s *= scp->device->sector_size;
|
|
+ if (s < dp->size) s += scp->device->sector_size;
|
|
+
|
|
+ dp->max_lba = (s >> bits) - 1;
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_size: rc: %d, size: %lld, max_lba: %lld\n", rc, dp->size, dp->max_lba);
|
|
+#endif
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Pass-through
|
|
+*/
|
|
+static int host_pass(coscsi_device_t *dp, struct scsi_cmnd *scp) {
|
|
+ unsigned long flags;
|
|
+ void *buffer;
|
|
+ unsigned long buflen;
|
|
+ co_scsi_pass_t *pass;
|
|
+ int rc;
|
|
+
|
|
+ if (!dp->os_handle) {
|
|
+ if (host_open(dp))
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ /* Scatter/Gather */
|
|
+ if (scsi_sg_count(scp)) {
|
|
+ struct scatterlist *sg;
|
|
+
|
|
+ /* Should never be more than 1 for non r/w transfers */
|
|
+ if (scsi_sg_count(scp) > 1) panic("COSCSI: host_pass: use_sg (%d) > 1!\n", scsi_sg_count(scp));
|
|
+
|
|
+ sg = scsi_sglist(scp);
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_pass: sg: page: %p, offset: %d, length: %d\n",
|
|
+ sg_page(sg), sg->offset, sg->length);
|
|
+#endif
|
|
+ buffer = sg_virt(sg);
|
|
+ buflen = sg->length;
|
|
+ /* Direct */
|
|
+ } else {
|
|
+ buffer = scsi_sglist(scp);
|
|
+ buflen = scsi_bufflen(scp);
|
|
+ }
|
|
+
|
|
+ co_passage_page_assert_valid();
|
|
+ co_passage_page_acquire(&flags);
|
|
+ co_passage_page->operation = CO_OPERATION_DEVICE;
|
|
+ co_passage_page->params[0] = CO_DEVICE_SCSI;
|
|
+ co_passage_page->params[1] = CO_SCSI_PASS;
|
|
+ co_passage_page->params[2] = dp->unit;
|
|
+
|
|
+ pass = (co_scsi_pass_t *) &co_passage_page->params[3];
|
|
+ memcpy(&pass->cdb, &scp->cmnd, 16);
|
|
+ pass->cdb_len = scp->cmd_len;
|
|
+ pass->write = (scp->sc_data_direction == DMA_TO_DEVICE);
|
|
+ pass->buffer = buffer;
|
|
+ pass->buflen = buflen;
|
|
+
|
|
+ co_switch_wrapper();
|
|
+
|
|
+ rc = co_passage_page->params[0];
|
|
+ co_passage_page_release(flags);
|
|
+
|
|
+#if COSCSI_DEBUG_PASS
|
|
+ if (rc == GOOD && dp->debug) dump_data(dp->unit, "host_pass", buffer, buflen);
|
|
+#endif
|
|
+
|
|
+#if COSCSI_DEBUG_HOST
|
|
+ if (dp->debug) printk(KERN_INFO "host_pass: rc: %d\n", rc);
|
|
+#endif
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/****************************************************************************************************
|
|
+ *
|
|
+ *
|
|
+ * SCSI functions
|
|
+ *
|
|
+ *
|
|
+ ****************************************************************************************************/
|
|
+
|
|
+static int check_condition(struct coscsi_device *dp, int key, int asc, int asq) {
|
|
+ dp->key = key;
|
|
+ dp->asc = asc;
|
|
+ dp->asq = asq;
|
|
+ return CHECK_CONDITION;
|
|
+}
|
|
+
|
|
+static int response(coscsi_worker_t *wp, void *data, int len) {
|
|
+ struct scsi_cmnd *scp = wp->scp;
|
|
+ void *buffer;
|
|
+ unsigned long buflen;
|
|
+ int act_len;
|
|
+
|
|
+ /* Scatter/Gather */
|
|
+ if (scsi_sg_count(scp)) {
|
|
+ struct scatterlist *sg;
|
|
+ int i;
|
|
+
|
|
+ /* scatter-gather list too long? */
|
|
+ BUG_ON(scsi_sg_count(scp) > COSCSI_SGSIZE);
|
|
+
|
|
+ scsi_for_each_sg(scp, sg, scsi_sg_count(scp), i) {
|
|
+#if COSCSI_DEBUG
|
|
+ if (wp->dp->debug) printk(KERN_INFO "response: sg: page: %p, offset: %d, length: %d\n",
|
|
+ sg_page(sg), sg->offset, sg->length);
|
|
+#endif
|
|
+ buffer = sg_virt(sg);
|
|
+ buflen = sg->length;
|
|
+ act_len = min(buflen, len);
|
|
+#if COSCSI_DEBUG_COMM
|
|
+ if (wp->dp->debug) dump_data(wp->dp->unit, "response", data, act_len);
|
|
+#endif
|
|
+ memcpy(buffer, data, act_len);
|
|
+ data += act_len;
|
|
+ len -= act_len;
|
|
+ }
|
|
+ /* Direct */
|
|
+ } else {
|
|
+ buffer = scsi_sglist(scp);
|
|
+ buflen = scsi_bufflen(scp);
|
|
+ if (!buflen) return GOOD;
|
|
+ act_len = min(buflen, len);
|
|
+#if COSCSI_DEBUG_COMM
|
|
+ if (wp->dp->debug) dump_data(wp->dp->unit, "response", data, act_len);
|
|
+#endif
|
|
+ memcpy(buffer, data, act_len);
|
|
+ }
|
|
+
|
|
+ return GOOD;
|
|
+}
|
|
+
|
|
+static int unit_ready(coscsi_worker_t *wp) {
|
|
+ int error, rc;
|
|
+
|
|
+ rc = GOOD;
|
|
+ error = (wp->dp->os_handle == 0 ? host_open(wp->dp) : GOOD);
|
|
+ if (error) {
|
|
+ switch(wp->dp->type) {
|
|
+ case TYPE_ROM:
|
|
+ case TYPE_TAPE:
|
|
+ rc = check_condition(wp->dp, NOT_READY, MEDIUM_NOT_PRESENT, 0x2);
|
|
+ break;
|
|
+ default:
|
|
+ rc = check_condition(wp->dp, NOT_READY, LOGICAL_UNIT_NOT_READY, 0x2);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+static int inquiry(coscsi_worker_t *wp) {
|
|
+ int x, alloc_len;
|
|
+ struct scsi_cmnd *scp = wp->scp;
|
|
+
|
|
+ alloc_len = (scp->cmnd[3] << 8) + scp->cmnd[4];
|
|
+#if COSCSI_DEBUG_INQ
|
|
+ if (wp->dp->debug) printk(KERN_INFO "scsi_inq: alloc_len: %d, buflen: %d\n", alloc_len, scsi_bufflen(scp));
|
|
+#endif
|
|
+
|
|
+ /* EVPD? */
|
|
+ if (scp->cmnd[1] & 1) {
|
|
+ coscsi_page_t *vpd = wp->dp->rom->vpd;
|
|
+ int page = scp->cmnd[2];
|
|
+
|
|
+#if COSCSI_DEBUG_INQ
|
|
+ if (wp->dp->debug) printk(KERN_INFO "scsi_inq: sending VPD page %d\n", page);
|
|
+#endif
|
|
+ /* For page 00, generate dynamically */
|
|
+ if (page == 0) {
|
|
+ unsigned char data[32];
|
|
+ int i;
|
|
+
|
|
+ memset(data, 0, sizeof(data));
|
|
+ data[0] = wp->dp->rom->std.page[0];
|
|
+ i = 4;
|
|
+ for(x=0; vpd[x].page; x++) data[i++] = vpd[x].num;
|
|
+ data[3] = i - 3;
|
|
+
|
|
+ return response(wp, data, min(alloc_len, sizeof(data)));
|
|
+ } else {
|
|
+ for(x=0; vpd[x].page; x++) {
|
|
+ if (vpd[x].num == page)
|
|
+ return response(wp, vpd[x].page, min(alloc_len, vpd[x].size));
|
|
+ }
|
|
+ return check_condition(wp->dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+ }
|
|
+
|
|
+ /* Standard page */
|
|
+ } else {
|
|
+ unsigned char *std = wp->dp->rom->std.page;
|
|
+
|
|
+#if COSCSI_DEBUG_INQ
|
|
+ printk(KERN_INFO "scsi_inq: sending STD page\n");
|
|
+#endif
|
|
+ strcpy((char *)&std[8], "coLinux");
|
|
+ std[1] = ((wp->dp->type == TYPE_ROM || wp->dp->type == TYPE_TAPE) ? 0x80 : 0);
|
|
+ memcpy(&std[16], wp->dp->rom->name, strlen(wp->dp->rom->name)+1);
|
|
+ memcpy(&std[32], scsi_rev, min(4, strlen(scsi_rev)+1));
|
|
+ return response(wp, std, min(alloc_len, wp->dp->rom->std.size));
|
|
+ }
|
|
+}
|
|
+
|
|
+static int read_capacity(coscsi_worker_t *wp) {
|
|
+ coscsi_device_t *dp = wp->dp;
|
|
+ struct scsi_cmnd *scp = wp->scp;
|
|
+
|
|
+ /* Get the size */
|
|
+ if (host_size(dp, scp)) return check_condition(dp, HARDWARE_ERROR, 0x3e, 1);
|
|
+
|
|
+ /* Convert to read_capacity format */
|
|
+ if (dp->max_lba > 0xfffffffe || scp->cmnd[8] & 1) {
|
|
+ dp->msg[0] = 0xff;
|
|
+ dp->msg[1] = 0xff;
|
|
+ dp->msg[2] = 0xff;
|
|
+ dp->msg[3] = 0xff;
|
|
+ } else {
|
|
+ dp->msg[0] = (wp->dp->max_lba >> 24);
|
|
+ dp->msg[1] = (wp->dp->max_lba >> 16) & 0xff;
|
|
+ dp->msg[2] = (wp->dp->max_lba >> 8) & 0xff;
|
|
+ dp->msg[3] = wp->dp->max_lba & 0xff;
|
|
+ }
|
|
+ dp->msg[4] = (scp->device->sector_size >> 24);
|
|
+ dp->msg[5] = (scp->device->sector_size >> 16) & 0xff;
|
|
+ dp->msg[6] = (scp->device->sector_size >> 8) & 0xff;
|
|
+ dp->msg[7] = scp->device->sector_size & 0xff;
|
|
+
|
|
+ return response(wp, &dp->msg, 8);
|
|
+}
|
|
+
|
|
+static int mode_sense(coscsi_worker_t *wp) {
|
|
+ unsigned char data[256],*ap;
|
|
+ int offset, bd_len, page;
|
|
+ coscsi_page_t *pages = wp->dp->rom->mode;
|
|
+ coscsi_device_t *dp = wp->dp;
|
|
+ struct scsi_cmnd *scp = wp->scp;
|
|
+ register int x;
|
|
+
|
|
+ memset(data, 0, sizeof(data));
|
|
+ offset = 4;
|
|
+ bd_len = 8;
|
|
+
|
|
+ data[2] = 0x10; /* DPOFUA */
|
|
+ data[3] = bd_len;
|
|
+
|
|
+ ap = data + offset;
|
|
+ if (dp->max_lba > 0xfffffffe) {
|
|
+ ap[0] = 0xff;
|
|
+ ap[1] = 0xff;
|
|
+ ap[2] = 0xff;
|
|
+ ap[3] = 0xff;
|
|
+ } else {
|
|
+ ap[0] = (dp->max_lba >> 24) & 0xff;
|
|
+ ap[1] = (dp->max_lba >> 16) & 0xff;
|
|
+ ap[2] = (dp->max_lba >> 8) & 0xff;
|
|
+ ap[3] = dp->max_lba & 0xff;
|
|
+ }
|
|
+ ap[5] = (scp->device->sector_size >> 16) & 0xff;
|
|
+ ap[6] = (scp->device->sector_size >> 8) & 0xff;
|
|
+ ap[7] = scp->device->sector_size & 0xff;
|
|
+
|
|
+ offset += bd_len;
|
|
+ ap = data + offset;
|
|
+#if COSCSI_DEBUG_SENSE
|
|
+ if (dp->debug) printk(KERN_INFO "mode_sense: ap: %p, offset: %d\n", ap, offset);
|
|
+#endif
|
|
+ page = scp->cmnd[2] & 0x3f;
|
|
+ if (page == 0x3f) {
|
|
+ /* All pages */
|
|
+ if (scp->cmnd[3] == 0 || scp->cmnd[3] == 0xFF) {
|
|
+ for(x=0; pages[x].page; x++) {
|
|
+#if COSCSI_DEBUG_SENSE
|
|
+ if (dp->debug) dump_data(dp->unit, "page", pages[x].page, pages[x].size);
|
|
+#endif
|
|
+ memcpy(ap, pages[x].page, pages[x].size);
|
|
+ ap += pages[x].size;
|
|
+ offset += pages[x].size;
|
|
+ }
|
|
+ } else {
|
|
+ return check_condition(wp->dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+ }
|
|
+ } else {
|
|
+ /* Specific page */
|
|
+ int found = 0;
|
|
+ for(x=0; pages[x].page; x++) {
|
|
+#if COSCSI_DEBUG_SENSE
|
|
+ if (dp->debug) printk(KERN_INFO "mode_sense: pages[%d].num: %d, page: %d\n", x, pages[x].num, page);
|
|
+#endif
|
|
+ if (pages[x].num == page) {
|
|
+#if COSCSI_DEBUG_SENSE
|
|
+ if (dp->debug) dump_data(dp->unit, "page", pages[x].page, pages[x].size);
|
|
+#endif
|
|
+ memcpy(ap, pages[x].page, pages[x].size);
|
|
+ offset += pages[x].size;
|
|
+ found = 1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!found) return check_condition(wp->dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+ }
|
|
+#if COSCSI_DEBUG_SENSE
|
|
+ if (dp->debug) printk(KERN_INFO "scsi_mode_sense: offset: %d\n", offset);
|
|
+#endif
|
|
+ data[0] = offset - 1;
|
|
+ return response(wp, data, min(scp->cmnd[4], offset));
|
|
+}
|
|
+
|
|
+/*
|
|
+The Logical Block Address field contains the LBA of the first block from which data shall be returned. If the
|
|
+Logical Block Address is beyond the range of recorded data, the Drive shall terminate the command with
|
|
+CHECK CONDITION status and SK/ASC/ASCQ values shall be set to ILLEGAL REQUEST/LOGICAL BLOCK
|
|
+ADDRESS OUT OF RANGE.
|
|
+*/
|
|
+
|
|
+static int read_write(coscsi_worker_t *wp) {
|
|
+ unsigned long long lba;
|
|
+ unsigned long num;
|
|
+ register unsigned char *p = wp->scp->cmnd;
|
|
+
|
|
+ lba = num = 0;
|
|
+ switch(*p) {
|
|
+ case READ_16:
|
|
+ case WRITE_16:
|
|
+ {
|
|
+ register int x;
|
|
+
|
|
+ for (x = 0; x < 8; x++) {
|
|
+ if (x) lba <<= 8;
|
|
+ lba |= (u32)(*(p+2+x));
|
|
+ }
|
|
+ }
|
|
+ num = *(p+10) << 24 | *(p+11) << 16 | *(p+12) << 8 | *(p+13);
|
|
+ break;
|
|
+ case READ_12:
|
|
+ case WRITE_12:
|
|
+ lba = (u32)(*(p+2) << 24 | *(p+3) << 16 | *(p+4) << 8 | *(p+5));
|
|
+ num = *(p+6) << 24 | *(p+7) << 16 | *(p+8) << 8 | *(p+9);
|
|
+ break;
|
|
+ case READ_10:
|
|
+ case WRITE_10:
|
|
+ lba = (u32)(*(p+2) << 24 | *(p+3) << 16 | *(p+4) << 8 | *(p+5));
|
|
+ num = *(p+7) << 8 | *(p+8);
|
|
+ break;
|
|
+ case READ_6:
|
|
+ case WRITE_6:
|
|
+ lba = (u32)((*(p+1) & 0x1f) << 16 | *(p+2) << 8 | *(p+3));
|
|
+ num = *(p+4) ? *(p+4) : 0xff;
|
|
+ break;
|
|
+ default:
|
|
+ printk(KERN_ERR "scsi%d: read_write: unknown opcode: %x\n", wp->dp->unit, *p);
|
|
+ return check_condition(wp->dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+ }
|
|
+
|
|
+#if COSCSI_DEBUG_XFER
|
|
+ if (wp->dp->debug) printk(KERN_INFO "read_write: lba: %lld, num: %ld\n", lba, num);
|
|
+#endif
|
|
+
|
|
+ if (host_rw(wp, lba, num, (wp->scp->cmnd[0] & 2) >> 1))
|
|
+ return check_condition(wp->dp, HARDWARE_ERROR, 0x3e, 1);
|
|
+ else
|
|
+ return GOOD;
|
|
+}
|
|
+
|
|
+static int request_sense(coscsi_worker_t *wp) {
|
|
+ coscsi_device_t *dp = wp->dp;
|
|
+
|
|
+ if (wp->scp->cmnd[1] & 1) {
|
|
+ dp->msg[0] = 0x72;
|
|
+ dp->msg[1] = dp->key;
|
|
+ dp->msg[2] = dp->asc;
|
|
+ dp->msg[3] = dp->asq;
|
|
+ } else {
|
|
+ dp->msg[0] = 0x70;
|
|
+ dp->msg[2] = dp->key;
|
|
+ dp->msg[7] = 0xa;
|
|
+ dp->msg[12] = dp->asc;
|
|
+ dp->msg[13] = dp->asq;
|
|
+ }
|
|
+ return response(wp, &dp->msg, min(wp->scp->cmnd[4], 18));
|
|
+}
|
|
+
|
|
+static int prevent_allow(coscsi_worker_t *wp) {
|
|
+ wp->dp->prevent = wp->scp->cmnd[4] & 1;
|
|
+ return GOOD;
|
|
+}
|
|
+
|
|
+static int get_config(coscsi_worker_t *wp) {
|
|
+ char buf[] = {
|
|
+ 0x00,0x00,0x00,0x7c,0x00,0x00,0x00,0x10,
|
|
+ 0x00,0x00,0x03,0x08,0x00,0x10,0x01,0x00,
|
|
+ 0x00,0x08,0x00,0x00,0x00,0x01,0x03,0x04,
|
|
+ 0x00,0x00,0x00,0x01,0x00,0x02,0x03,0x04
|
|
+ };
|
|
+
|
|
+ return response(wp, buf, sizeof(buf));
|
|
+}
|
|
+
|
|
+static int read_toc(coscsi_worker_t *wp) {
|
|
+ int msf = ((wp->scp->cmnd[1] >> 1) & 1);
|
|
+ int len = wp->scp->cmnd[7] << 8 | wp->scp->cmnd[8];
|
|
+ int start;
|
|
+ unsigned char data[12];
|
|
+
|
|
+ /* We only support format 0 when MSF is set */
|
|
+ if (msf && wp->scp->cmnd[2] & 0x0f) return check_condition(wp->dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+
|
|
+ start = (msf ? 32 : 0);
|
|
+
|
|
+ /* TOC header */
|
|
+ data[0] = 0; /* Len MSB */
|
|
+ data[1] = sizeof(data); /* Len LSB */
|
|
+ data[2] = 1; /* 1st track */
|
|
+ data[3] = 1; /* Last track */
|
|
+
|
|
+ /* Track 1 descriptor */
|
|
+ data[4] = 0; /* Reserved */
|
|
+ data[5] = 0x14; /* ADR & CONTROL */
|
|
+ data[6] = 1; /* Track # */
|
|
+ data[7] = 0; /* Reserved */
|
|
+ data[8] = (start >> 24) & 0xff; /* Start */
|
|
+ data[9] = (start >> 16) & 0xff;
|
|
+ data[10] = (start >> 8) & 0xff;
|
|
+ data[11] = start & 0xff;
|
|
+
|
|
+ return response(wp, data, min(len, sizeof(data)));
|
|
+}
|
|
+
|
|
+/*
|
|
+The Polled bit is used to select operational mode. When Polled is set to zero, the Host is requesting
|
|
+asynchronous operation. If the Drive does not support asynchronous operation, the command shall be
|
|
+terminated with CHECK CONDITION status and the values for SK/ASC/ASCQ shall be set to ILLEGAL
|
|
+REQUEST/INVALID FIELD IN CDB.
|
|
+Note 12. If Polled is zero while a Group 2 timeout command is executing, the GET EVENT STATUS
|
|
+NOTIFICATION command may be queued, but it never terminates.
|
|
+When Polled is set to one, the Host is requesting polled operation. The Drive shall return event information for
|
|
+the highest priority requested event. If no event has occurred, the Drive shall report the .No Change. event for
|
|
+the highest priority requested event class.
|
|
+*/
|
|
+
|
|
+static int event_status(coscsi_worker_t *wp) {
|
|
+ return check_condition(wp->dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+}
|
|
+
|
|
+static int coscsi_queue(struct scsi_cmnd *scp, void (*done)(struct scsi_cmnd *)) {
|
|
+ coscsi_device_t *dp;
|
|
+ coscsi_worker_t worker;
|
|
+ int rc;
|
|
+
|
|
+#if COSCSI_DEBUG
|
|
+ printk(KERN_INFO "coscsi_queue: id: %d, lun: %d, cdb[0]: 0x%02x\n",
|
|
+ scp->device->id, scp->device->lun, scp->cmnd[0]);
|
|
+#endif
|
|
+
|
|
+ /* Get device pointer */
|
|
+ dp = &devices[scp->device->id];
|
|
+
|
|
+#if COSCSI_DEBUG_COMM
|
|
+ if (dp->debug) dump_data(dp->unit, "request", &scp->cmnd, sizeof(scp->cmnd));
|
|
+#endif
|
|
+
|
|
+ /* Setup worker */
|
|
+ worker.dp = dp;
|
|
+ worker.scp = scp;
|
|
+
|
|
+ /* Do we have the requested device? */
|
|
+ if ((scp->device->id >= CO_MODULE_MAX_COSCSI) || (dp->rom == 0)) {
|
|
+ if (scp->cmnd[0] == INQUIRY) {
|
|
+ char temp[96];
|
|
+ memset(temp,0,sizeof(temp));
|
|
+ temp[0] = 0x7f;
|
|
+ temp[3] = 2;
|
|
+ temp[4] = 92;
|
|
+ scp->result = response(&worker, temp, min(scp->cmnd[4],96));
|
|
+ } else
|
|
+ scp->result = check_condition(dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+ goto req_done;
|
|
+ }
|
|
+
|
|
+ /* Set done for async funcs */
|
|
+ scp->scsi_done = done;
|
|
+
|
|
+ /* Pass-through? */
|
|
+ if (dp->type == SCSI_PTYPE_PASS) {
|
|
+ switch(scp->cmnd[0]) {
|
|
+ case READ_6:
|
|
+ case READ_10:
|
|
+ case READ_16:
|
|
+ case WRITE_6:
|
|
+ case WRITE_10:
|
|
+ case WRITE_16:
|
|
+ /* r/w may be async */
|
|
+ rc = read_write(&worker);
|
|
+#if COSCSI_ASYNC
|
|
+ if (rc == GOOD) goto req_out;
|
|
+#endif
|
|
+ break;
|
|
+ default:
|
|
+ rc = host_pass(dp, scp);
|
|
+ break;
|
|
+ }
|
|
+ scp->result = rc;
|
|
+ goto req_done;
|
|
+ }
|
|
+
|
|
+ /* Process command */
|
|
+ switch(scp->cmnd[0]) {
|
|
+ case INQUIRY:
|
|
+ scp->result = inquiry(&worker);
|
|
+ break;
|
|
+ case TEST_UNIT_READY:
|
|
+ scp->result = unit_ready(&worker);
|
|
+ break;
|
|
+ case REQUEST_SENSE:
|
|
+ scp->result = request_sense(&worker);
|
|
+ break;
|
|
+ case READ_CAPACITY:
|
|
+ scp->result = read_capacity(&worker);
|
|
+ break;
|
|
+ case REPORT_LUNS:
|
|
+ /* We only support 1 lun right now */
|
|
+ memset(dp->msg, 0, 16);
|
|
+ dp->msg[3] = 1;
|
|
+ scp->result = response(&worker, &dp->msg, 16);
|
|
+ break;
|
|
+ case MODE_SENSE:
|
|
+ scp->result = mode_sense(&worker);
|
|
+ break;
|
|
+ case ALLOW_MEDIUM_REMOVAL:
|
|
+ scp->result = prevent_allow(&worker);
|
|
+ break;
|
|
+ case READ_TOC:
|
|
+ scp->result = read_toc(&worker);
|
|
+ break;
|
|
+ case GET_CONFIGURATION:
|
|
+ scp->result = get_config(&worker);
|
|
+ break;
|
|
+ case GET_EVENT_STATUS:
|
|
+ scp->result = event_status(&worker);
|
|
+ break;
|
|
+ case READ_6:
|
|
+ case READ_10:
|
|
+ case READ_16:
|
|
+ case WRITE_6:
|
|
+ case WRITE_10:
|
|
+ case WRITE_16:
|
|
+ /* r/w may be async */
|
|
+ rc = read_write(&worker);
|
|
+#if COSCSI_ASYNC
|
|
+ if (rc == GOOD) goto req_out;
|
|
+#endif
|
|
+ scp->result = rc;
|
|
+ break;
|
|
+ case SYNCHRONIZE_CACHE:
|
|
+ scp->result = GOOD;
|
|
+ break;
|
|
+ case READ_DISC_INFO:
|
|
+ {
|
|
+ disc_information di = { 0, };
|
|
+
|
|
+ di.disc_information_length = cpu_to_be16(1);
|
|
+ /* di.erasable = 0; */
|
|
+ scp->result = response(&worker, &di, sizeof(di.disc_information_length) + 1);
|
|
+ }
|
|
+ break;
|
|
+ default:
|
|
+ printk(KERN_NOTICE "scsi%d: unhandled opcode: %x\n", dp->unit, scp->cmnd[0]);
|
|
+ scp->result = check_condition(dp, ILLEGAL_REQUEST, INVALID_FIELD_IN_CDB, 0);
|
|
+ }
|
|
+
|
|
+req_done:
|
|
+ done(scp);
|
|
+#if COSCSI_ASYNC
|
|
+req_out:
|
|
+#endif
|
|
+#if COSCSI_DEBUG_COMM
|
|
+ if (dp->debug) printk(KERN_INFO "coscsi_queue: scp->result: %02x (code: %x)\n", scp->result, scp->result & 0xffff);
|
|
+ if (dp->debug) printk(KERN_INFO "------------------------------------------------------------------------\n");
|
|
+#endif
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int coscsi_config(struct scsi_device *sdev) {
|
|
+ switch(sdev->type) {
|
|
+ case TYPE_ROM:
|
|
+ case TYPE_WORM:
|
|
+ /* XXX required to get rid of "unaligned transfer" errors */
|
|
+ blk_queue_hardsect_size(sdev->request_queue, 2048);
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* Don't have SAI_READ_CAPACITY_16 and other 16 byte commands at the moment */
|
|
+ if (sdev->type != SCSI_PTYPE_PASS)
|
|
+ sdev->scsi_level = SCSI_SPC_2;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+struct scsi_host_template coscsi_template = {
|
|
+ .module = THIS_MODULE,
|
|
+ .name = "Cooperative Linux SCSI Adapter",
|
|
+ .proc_name = "coscsi",
|
|
+ .queuecommand = coscsi_queue,
|
|
+ .slave_configure = coscsi_config,
|
|
+ .this_id = -1,
|
|
+ .sg_tablesize = COSCSI_SGSIZE,
|
|
+ .max_sectors = 0xFFFF,
|
|
+ .can_queue = 65535,
|
|
+ .cmd_per_lun = 2048,
|
|
+ .use_clustering = ENABLE_CLUSTERING,
|
|
+ .skip_settle_delay = 1,
|
|
+ .max_host_blocked = 1,
|
|
+};
|
|
+
|
|
+/****************************************************************************************************
|
|
+ *
|
|
+ *
|
|
+ * PCI functions
|
|
+ *
|
|
+ *
|
|
+ ****************************************************************************************************/
|
|
+
|
|
+/*
|
|
+ * PCI Probe - probe for a single device
|
|
+*/
|
|
+static int __devinit coscsi_pci_probe( struct pci_dev *pdev, const struct pci_device_id *ent )
|
|
+{
|
|
+ struct Scsi_Host *shost;
|
|
+ unsigned long flags;
|
|
+ coscsi_device_t *dp;
|
|
+ register int x;
|
|
+ int rc;
|
|
+
|
|
+#if COSCSI_DEBUG
|
|
+ printk(KERN_INFO "coscsi_pci_probe: adding host...\n");
|
|
+#endif
|
|
+
|
|
+ /* Get our config from the host */
|
|
+ memset(&devices, 0, sizeof(devices));
|
|
+ co_passage_page_acquire(&flags);
|
|
+ co_passage_page->operation = CO_OPERATION_DEVICE;
|
|
+ co_passage_page->params[0] = CO_DEVICE_SCSI;
|
|
+ co_passage_page->params[1] = CO_SCSI_GET_CONFIG;
|
|
+ co_passage_page->params[2] = 0;
|
|
+ co_switch_wrapper();
|
|
+
|
|
+ /* Get the result */
|
|
+ if (!co_passage_page->params[0]) {
|
|
+ for(x=0; x < CO_MODULE_MAX_COSCSI; x++) {
|
|
+ if ((co_passage_page->params[x+1] & COSCSI_DEVICE_ENABLED) == 0)
|
|
+ continue;
|
|
+ dp = &devices[x];
|
|
+ dp->unit = x;
|
|
+ dp->type = co_passage_page->params[x+1] & 0x1f;
|
|
+ dp->debug = 1;
|
|
+ switch(dp->type) {
|
|
+ case TYPE_DISK:
|
|
+ dp->rom = &disk_rom;
|
|
+ break;
|
|
+ case TYPE_ROM:
|
|
+ case TYPE_WORM:
|
|
+ dp->rom = &cd_rom;
|
|
+ break;
|
|
+ case TYPE_PASS:
|
|
+ dp->rom = (void *) ~0L;
|
|
+ break;
|
|
+ case TYPE_MEDIUM_CHANGER:
|
|
+// dp->rom = &changer_rom;
|
|
+ break;
|
|
+ case TYPE_TAPE:
|
|
+// dp->rom = &tape_rom;
|
|
+ break;
|
|
+ default:
|
|
+ dp->unit = -1;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Release the page */
|
|
+ co_passage_page_release(flags);
|
|
+
|
|
+#if COSCSI_DUMP_CONFIG
|
|
+ printk(KERN_INFO "SCSI: device configuration:\n");
|
|
+ for(x=0; x < CO_MODULE_MAX_COSCSI; x++) {
|
|
+ dp = &devices[x];
|
|
+ printk(KERN_INFO "scsi%02d: type: %02d, rom: %p\n", dp->unit, dp->type, dp->rom);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* Get shost */
|
|
+ shost = scsi_host_alloc(&coscsi_template, sizeof(void *));
|
|
+ if (!shost) {
|
|
+ printk(KERN_ERR "coscsi_pci_probe: scsi_host_alloc failed");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /* Set params */
|
|
+ shost->irq = SCSI_IRQ;
|
|
+ shost->max_id = CO_MODULE_MAX_COSCSI;
|
|
+ shost->max_lun = 1;
|
|
+ shost->max_channel = 0;
|
|
+
|
|
+#if COSCSI_DUMP_PARAMS
|
|
+#define SDUMP(s,f) printk(KERN_INFO " %16s: %d\n", #f, (s)->f)
|
|
+ printk(KERN_INFO "COSCSI: host parameters:\n");
|
|
+ SDUMP(shost,max_id);
|
|
+ SDUMP(shost,max_lun);
|
|
+ SDUMP(shost,max_channel);
|
|
+ SDUMP(shost,unique_id);
|
|
+ SDUMP(&coscsi_template,can_queue);
|
|
+ SDUMP(&coscsi_template,cmd_per_lun);
|
|
+ SDUMP(&coscsi_template,sg_tablesize);
|
|
+ SDUMP(&coscsi_template,max_sectors);
|
|
+ SDUMP(&coscsi_template,use_clustering);
|
|
+ SDUMP(shost,use_blk_tcq);
|
|
+ SDUMP(shost,reverse_ordering);
|
|
+ SDUMP(&coscsi_template,ordered_tag);
|
|
+ SDUMP(&coscsi_template,max_host_blocked);
|
|
+#undef SDUMP
|
|
+#endif
|
|
+
|
|
+ /* Add host */
|
|
+ rc = scsi_add_host(shost, &pdev->dev);
|
|
+ if (rc) {
|
|
+ printk(KERN_ERR "coscsi_pci_probe: scsi_add_host failed");
|
|
+ goto err_put;
|
|
+ }
|
|
+ pci_set_drvdata(pdev, shost);
|
|
+
|
|
+ /* Scan devs */
|
|
+ scsi_scan_host(shost);
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_put:
|
|
+ scsi_host_put(shost);
|
|
+ return rc;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * PCI Remove - hotplug removal
|
|
+*/
|
|
+static void __devexit coscsi_pci_remove(struct pci_dev *pdev)
|
|
+{
|
|
+ pci_set_drvdata(pdev, NULL);
|
|
+}
|
|
+
|
|
+/* We only support the COSCSI adapter :) */
|
|
+static struct pci_device_id coscsi_pci_ids[] __devinitdata = {
|
|
+ { PCI_DEVICE(PCI_VENDOR_ID_CO, PCI_DEVICE_ID_COSCSI) },
|
|
+ { 0 }
|
|
+};
|
|
+
|
|
+static struct pci_driver coscsi_pci_driver = {
|
|
+ .name = "coscsi",
|
|
+ .id_table = coscsi_pci_ids,
|
|
+ .probe = coscsi_pci_probe, /* FIXME: Use bus_type methods */
|
|
+ .remove = __devexit_p(coscsi_pci_remove), /* FIXME: Use bus_type methods */
|
|
+};
|
|
+
|
|
+extern int coio_test(void);
|
|
+
|
|
+/*
|
|
+ * PCI Init - module load
|
|
+*/
|
|
+static int __init coscsi_pci_init(void) {
|
|
+ int rc;
|
|
+
|
|
+ /* XXX COSCSI_VERSION better be <= 4 bytes */
|
|
+ strncpy(scsi_rev, COSCSI_VERSION, 4);
|
|
+
|
|
+ memset(&devices, 0, sizeof(devices));
|
|
+
|
|
+ rc = request_irq(SCSI_IRQ, &coscsi_isr, IRQF_SAMPLE_RANDOM, "coscsi", NULL);
|
|
+ if (rc) {
|
|
+ printk(KERN_ERR "coscsi_pci_init: unable to get irq %d", SCSI_IRQ);
|
|
+ return rc;
|
|
+ }
|
|
+ spin_lock_init(&coscsi_isr_lock);
|
|
+
|
|
+#if COSCSI_DEBUG_PCI
|
|
+ printk(KERN_INFO "coscsi_pci_init: registering...\n");
|
|
+#endif
|
|
+ return pci_register_driver(&coscsi_pci_driver);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * PCI Exit - module unload
|
|
+*/
|
|
+static void __exit coscsi_pci_exit(void) {
|
|
+ register int x;
|
|
+
|
|
+#if COSCSI_DEBUG_PCI
|
|
+ printk(KERN_INFO "coscsi_pci_exit: closing handles\n");
|
|
+#endif
|
|
+
|
|
+ /* Close the handles */
|
|
+ for(x=0; x < CO_MODULE_MAX_COSCSI; x++) host_close(&devices[x]);
|
|
+
|
|
+ /* Unmap the page */
|
|
+
|
|
+#if COSCSI_DEBUG_PCI
|
|
+ printk(KERN_INFO "coscsi_pci_exit: exiting\n");
|
|
+#endif
|
|
+ pci_unregister_driver(&coscsi_pci_driver);
|
|
+}
|
|
+
|
|
+module_init(coscsi_pci_init);
|
|
+module_exit(coscsi_pci_exit);
|
|
Index: linux-2.6.25-source/drivers/scsi/coscsi_rom.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.25-source/drivers/scsi/coscsi_rom.h
|
|
@@ -0,0 +1,142 @@
|
|
+
|
|
+#ifndef __COSCSI_ROM_H
|
|
+#define __COSCSI_ROM_H
|
|
+
|
|
+/* Mode/Inq page data */
|
|
+struct coscsi_page {
|
|
+ int num;
|
|
+ unsigned char *page;
|
|
+ int size;
|
|
+};
|
|
+typedef struct coscsi_page coscsi_page_t;
|
|
+
|
|
+#define COSCSI_ROM_PAGE(n,p) { n, p, sizeof(p) }
|
|
+
|
|
+struct coscsi_rom {
|
|
+ char *name;
|
|
+ coscsi_page_t std;
|
|
+ coscsi_page_t *vpd;
|
|
+ coscsi_page_t *mode;
|
|
+};
|
|
+typedef struct coscsi_rom coscsi_rom_t;
|
|
+
|
|
+/*
|
|
+ * Disk pages
|
|
+*/
|
|
+
|
|
+/* Standard Inquiry page */
|
|
+static unsigned char disk_std_page[] = {
|
|
+ 0x00,0x00,0x05,0x02,0x5c,0x00,0x01,0x20, /* 00 - 07 */
|
|
+ 0x63,0x6f,0x4c,0x69,0x6e,0x75,0x78,0x00, /* 08 - 15 */
|
|
+ 0x43,0x4f,0x44,0x49,0x53,0x4b,0x00,0x00, /* 16 - 23 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24 - 31 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 32 - 39 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40 - 47 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48 - 55 */
|
|
+ 0x00,0x00,0x00,0x77,0x00,0x14,0x03,0x3d, /* 56 - 63 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 64 - 71 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 72 - 79 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 80 - 87 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 88 - 95 */
|
|
+};
|
|
+
|
|
+#if 0
|
|
+/* Supported VPD Pages */
|
|
+static unsigned char disk_vpd_00[] = {
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 00 - 07 */
|
|
+};
|
|
+#endif
|
|
+
|
|
+#if 0
|
|
+/* Unit Serial Number VPD page */
|
|
+static unsigned char disk_vpd_80[] = {
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 00 - 07 */
|
|
+};
|
|
+#endif
|
|
+
|
|
+/* Device Identification VPD page */
|
|
+static unsigned char disk_vpd_83[] = {
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 00 - 07 */
|
|
+};
|
|
+
|
|
+/* Block Limits VPD page */
|
|
+static unsigned char disk_vpd_b0[] = {
|
|
+ 0x00,0xB0,0x00,0x10,0x00,0x00,0x00,0x00, /* 00 - 07 */
|
|
+};
|
|
+
|
|
+static coscsi_page_t disk_vpd_pages[] = {
|
|
+#if 0
|
|
+ COSCSI_ROM_PAGE(0x80, disk_vpd_80), /* Unit Serial Number */
|
|
+#endif
|
|
+ COSCSI_ROM_PAGE(0x83, disk_vpd_83), /* Device Identification */
|
|
+ COSCSI_ROM_PAGE(0xb0, disk_vpd_b0), /* Block limits (SBC) */
|
|
+ { 0, 0, 0 }
|
|
+};
|
|
+
|
|
+static unsigned char disk_mode_08[] = {
|
|
+ 0x08, 0x12, 0x14, 0x00, 0xff, 0xff, 0x00, 0x00,
|
|
+ 0xff, 0xff, 0xff, 0xff, 0x80, 0x14, 0x00, 0x00,
|
|
+ 0x00, 0x00, 0x00, 0x00
|
|
+};
|
|
+
|
|
+static coscsi_page_t disk_mode_pages[] = {
|
|
+ COSCSI_ROM_PAGE(0x08, disk_mode_08),
|
|
+ { 0, 0, 0 }
|
|
+};
|
|
+
|
|
+static coscsi_rom_t disk_rom = {
|
|
+ .name = "CODISK",
|
|
+ .std = COSCSI_ROM_PAGE(0, disk_std_page),
|
|
+ .vpd = disk_vpd_pages,
|
|
+ .mode = disk_mode_pages,
|
|
+};
|
|
+
|
|
+/*
|
|
+ * CD pages
|
|
+*/
|
|
+
|
|
+static unsigned char cd_std_page[] = {
|
|
+ 0x05,0x80,0x02,0x02,0x1f,0x00,0x00,0x10, /* 00 - 07 */
|
|
+ 0x4f,0x50,0x30,0x34,0x32,0x5a,0x20,0x49, /* 08 - 15 */
|
|
+ 0x52,0x53,0x30,0x36,0x50,0x20,0x20,0x20, /* 16 - 23 */
|
|
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, /* 24 - 31 */
|
|
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, /* 24 - 31 */
|
|
+ 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, /* 32 - 39 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 40 - 47 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 48 - 55 */
|
|
+ 0x00,0x00,0x00,0x77,0x00,0x14,0x03,0x3d, /* 56 - 63 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 64 - 71 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 72 - 79 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 80 - 87 */
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 88 - 95 */
|
|
+};
|
|
+
|
|
+/* Device Identification VPD page */
|
|
+static unsigned char cd_vpd_83[] = {
|
|
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 00 - 07 */
|
|
+};
|
|
+
|
|
+static coscsi_page_t cd_vpd_pages[] = {
|
|
+ COSCSI_ROM_PAGE(0x83, cd_vpd_83), /* Device Identification */
|
|
+ { 0, 0, 0 }
|
|
+};
|
|
+
|
|
+unsigned char cd_mode_2a[] = {
|
|
+ 0x2a,0x18,0x3f,0x00,0x75,0x7f,0x29,0x00, /* 00 - 07 */
|
|
+ 0x16,0x00,0x01,0x00,0x02,0x00,0x16,0x00, /* 08 - 15 */
|
|
+ 0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x01, /* 16 - 23 */
|
|
+};
|
|
+
|
|
+static coscsi_page_t cd_mode_pages[] = {
|
|
+ COSCSI_ROM_PAGE(0x2a, cd_mode_2a),
|
|
+ { 0, 0, 0 }
|
|
+};
|
|
+
|
|
+static coscsi_rom_t cd_rom = {
|
|
+ .name = "COCD",
|
|
+ .std = COSCSI_ROM_PAGE(0, cd_std_page),
|
|
+ .vpd = cd_vpd_pages,
|
|
+ .mode = cd_mode_pages,
|
|
+};
|
|
+
|
|
+#endif
|
|
Index: linux-2.6.25-source/include/scsi/coscsi.h
|
|
===================================================================
|
|
--- /dev/null
|
|
+++ linux-2.6.25-source/include/scsi/coscsi.h
|
|
@@ -0,0 +1,66 @@
|
|
+
|
|
+#ifndef __SCSI_COSCSI_H
|
|
+#define __SCSI_COSCSI_H
|
|
+
|
|
+#if defined(CO_KERNEL) || defined(CO_HOST_KERNEL)
|
|
+
|
|
+/* Set this to 1 to enable background I/O in the host */
|
|
+#define COSCSI_ASYNC 1
|
|
+
|
|
+typedef enum {
|
|
+ CO_SCSI_GET_CONFIG,
|
|
+ CO_SCSI_OPEN,
|
|
+ CO_SCSI_CLOSE,
|
|
+ CO_SCSI_SIZE,
|
|
+ CO_SCSI_IO,
|
|
+ CO_SCSI_PASS,
|
|
+ CO_SCSI_IOS,
|
|
+} CO_SCSI_OPS;
|
|
+
|
|
+#define COSCSI_DEVICE_ENABLED 0x80
|
|
+
|
|
+typedef struct {
|
|
+ void *scp;
|
|
+ unsigned long long offset;
|
|
+ vm_ptr_t sg;
|
|
+ int count;
|
|
+ int reqlen;
|
|
+ int write;
|
|
+} __attribute__((packed)) co_scsi_io_t;
|
|
+
|
|
+typedef struct {
|
|
+ void *ctx;
|
|
+ int result;
|
|
+ int delta;
|
|
+} __attribute__((packed)) co_scsi_intr_t;
|
|
+
|
|
+typedef struct {
|
|
+ unsigned char cdb[16];
|
|
+ unsigned cdb_len: 7;
|
|
+ unsigned write: 1;
|
|
+ vm_ptr_t buffer;
|
|
+ unsigned long buflen;
|
|
+} __attribute__((packed)) co_scsi_pass_t;
|
|
+
|
|
+#endif /* CO_KERNEL || CO_HOST_KERNEL */
|
|
+
|
|
+/* Device types */
|
|
+#define SCSI_PTYPE_DISK 0x00
|
|
+#define SCSI_PTYPE_TAPE 0x01
|
|
+#define SCSI_PTYPE_PRINTER 0x02
|
|
+#define SCSI_PTYPE_PROC 0x03
|
|
+#define SCSI_PTYPE_WORM 0x04
|
|
+#define SCSI_PTYPE_CDDVD 0x05
|
|
+#define SCSI_PTYPE_SCANNER 0x06
|
|
+#define SCSI_PTYPE_OPTICAL 0x07
|
|
+#define SCSI_PTYPE_CHANGER 0x08
|
|
+#define SCSI_PTYPE_COMM 0x09
|
|
+#define SCSI_PTYPE_RAID 0x0C
|
|
+#define SCSI_PTYPE_ENC 0x0D
|
|
+#define SCSI_PTYPE_SDISK 0x0E
|
|
+#define SCSI_PTYPE_CARD 0x0F
|
|
+#define SCSI_PTYPE_BRIDGE 0x10
|
|
+#define SCSI_PTYPE_OSD 0x11
|
|
+#define SCSI_PTYPE_PASS 0x1F /* Special */
|
|
+
|
|
+#endif
|