[PATCH 2.6.20 2/2] iop13xx: imu scsi driver

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

 



From: Greg Tucker <greg.b.tucker@xxxxxxxxx>

Enable Linux to access the other core as if it were a scsi target.

Made changes suggested by James Bottomley such as dma_map direction not
bidirectional, proper SCSI return conditons, reset handlers and cleanup.

Signed-off-by: Greg Tucker <greg.b.tucker@xxxxxxxxx>
Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx>
---

 arch/arm/mach-iop13xx/imu/Kconfig |    7 
 drivers/scsi/Makefile             |    1 
 drivers/scsi/iop13xx-imu-scsi.c   |  607 +++++++++++++++++++++++++++++++++++++
 3 files changed, 615 insertions(+), 0 deletions(-)

diff --git a/arch/arm/mach-iop13xx/imu/Kconfig b/arch/arm/mach-iop13xx/imu/Kconfig
index ee49b37..96d9d3a 100644
--- a/arch/arm/mach-iop13xx/imu/Kconfig
+++ b/arch/arm/mach-iop13xx/imu/Kconfig
@@ -16,4 +16,11 @@ config IOP_IMU_DEV
 	---help---
 	This is a char driver that passes messages throught the IMU.
 
+config IOP_IMU_SCSI
+	tristate "IOP IMU scsi driver"
+	depends on IOP_IMU
+	---help---
+	This is a low-level SCSI driver that passes SCSI commands
+	to core 2.
+
 endmenu
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index bd7c988..c1ccf57 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -132,6 +132,7 @@ obj-$(CONFIG_SCSI_IBMVSCSIS)	+= ibmvscsi/
 obj-$(CONFIG_SCSI_HPTIOP)	+= hptiop.o
 obj-$(CONFIG_SCSI_STEX)		+= stex.o
 
+obj-$(CONFIG_IOP_IMU_SCSI)	+= iop13xx-imu-scsi.o
 obj-$(CONFIG_ARM)		+= arm/
 
 obj-$(CONFIG_CHR_DEV_ST)	+= st.o
diff --git a/drivers/scsi/iop13xx-imu-scsi.c b/drivers/scsi/iop13xx-imu-scsi.c
new file mode 100644
index 0000000..2d64183
--- /dev/null
+++ b/drivers/scsi/iop13xx-imu-scsi.c
@@ -0,0 +1,607 @@
+/*
+ * drivers/scsi/iop13xx-imu-scsi.c
+ *
+ * SCSI low-level driver that forwards messages through the IOP342 IMU hw to 
+ * the other core.
+ *
+ * Copyright (C) 2005, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
+ * Place - Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * Author: Greg Tucker <greg.b.tucker@xxxxxxxxx>
+ *
+ */
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi_dbg.h>
+#include <asm/arch/imu.h>
+
+#define MODULE_VERS    "1.1"
+#define MODULE_NAME    "IMUscsi"
+#define IMU_WW_NAME     (0xeeed)
+#define IMU_MAX_CMD_LEN 32
+#define Q_NUM           0
+#define Q_PHYS_BASE     (0xffe00000 + (512*1024))
+#define Q_MSG_SIZE      256
+#define Q_MSG_ITEMS     16
+
+/* Derived params */
+#define Q_SIZE          (Q_MSG_ITEMS*Q_MSG_SIZE)
+#define SGL_PER_CMD  ((Q_MSG_SIZE-IMU_MAX_CMD_LEN - 24)/sizeof(struct imu_sge))
+
+/* #define IMU_DEBUG */
+#ifdef IMU_DEBUG
+# define imu_debug(fmt,arg...) printk(MODULE_NAME ": " fmt,##arg)
+# define dbg_print_cmd(cmd)    scsi_print_command(cmd)
+#else
+# define imu_debug(fmt,arg...) do { } while (0)
+# define dbg_print_cmd(cmd)    do { } while (0)
+#endif
+#define imu_warn(fmt,arg...) printk(KERN_WARNING MODULE_NAME ": " fmt,##arg)
+
+/* #define IMU_SCSI_PROFILING */
+#ifdef IMU_SCSI_PROFILING
+unsigned imu_queue_commands = 0;
+unsigned imu_rx_callbacks = 0;
+unsigned imu_failed_alloc = 0;
+# define imu_scsi_prof(x) x
+#else
+# define imu_scsi_prof(x)
+#endif
+
+struct imu_scsidata {
+	struct Scsi_Host *host;
+	u32 connection_handle;
+};
+
+struct imu_scsidata *imu_scsi;
+
+struct imu_sgl {
+	u32 len;
+	u32 addr;
+	u32 last_sge;
+};
+
+struct imu_sge {
+	u32 flen;
+	u32 addr_l;
+	u32 addr_h;
+};
+
+enum fcodes {
+	IMU_FCODE_ERROR = 0,
+	IMU_FCODE_CMD,
+	IMU_FCODE_XFER,
+	IMU_FCODE_STATUS,
+	IMU_FCODE_RESP,
+	IMU_FCODE_MANAGE,
+	IMU_FCODE_CONNECT_REQ,
+	IMU_FCODE_CONNECT_RESP,
+	IMU_FCODE_DISCONNECT_REQ,
+	IMU_FCODE_DISCONNECT_RESP,
+	IMU_FCODE_CACHE_ALLOC_REQ,
+	IMU_FCODE_CACHE_ALLOC_RESP,
+	IMU_FCODE_LOG_REQ,
+	IMU_FCODE_LOG_RESP,
+};
+
+struct imu_cmd {
+	union {
+		struct {
+			unsigned short fcode;
+			unsigned char flags;
+			unsigned char reserved;
+		};
+		u32 head;
+	};
+	u64 array_context;
+	u64 app_context;
+	u32 connection_handle;
+	union {
+		union {
+			struct {
+				u64 lun;
+				u32 ctl;
+				u32 datalen;
+				unsigned char cdb[16];
+				union {
+					struct imu_sgl sgl;
+					struct imu_sge sge;
+				};
+			} cdb_cmd;
+			unsigned char cmd[IMU_MAX_CMD_LEN];
+		};		/* targ_cmd; */
+
+		struct {
+			u32 scsi_status;
+			u32 residual_count;
+			u32 response_code;
+			u16 reserved;
+			u16 senselen;
+			unsigned char sense_data[100];
+		} resp_cmd;
+
+		struct {
+			u64 lun;
+			u32 management_func;
+			u64 management_task;
+		} management_cmd;
+
+		struct {
+			u32 blocks;	/* cache alloc command */
+			u32 address[32];
+		} cache_cmd;
+
+		u32 status;	/* status command, connect/disconnect response */
+
+		u64 world_wide_name;	/* connect request command */
+	};
+};
+
+/* SGE command flags */
+#define FLAG_LAST_ELEMENT           (1<<31)
+
+/* Target Command message flags */
+#define FLAG_CMD_RECEIVER_MUST_XFER (1<<(22-16))
+#define FLAG_CMD_SGL_NOT_IN_MSG     (1<<(20-16))
+/* Data transfer command flags */
+#define FLAG_DATA_LAST_XFER         (1<<(23-16))
+#define FLAG_DATA_SGL_NOT_IN_MSG    (1<<(20-16))
+/* Command response flags */
+#define FLAG_RSP_RESIDUAL_UNDER     (1<<(22-16))
+#define FLAG_RSP_RESIDUAL_OVER      (1<<(21-16))
+#define FLAG_REP_CODE_VALID_DATA    (1<<(20-16))
+#define FLAG_REP_SENSE_VALID        (1<<(19-16))
+
+/* response msssage codes */
+enum imu_resp_codes {
+	IMU_RESP_SUCCESS = 0,
+	IMU_RESP_REJECT,
+	IMU_RESP_FAILED,
+	IMU_RESP_INVALID,
+};
+
+
+int imu_proc_info(struct Scsi_Host *host, char *buffer, char **start,
+		  off_t offset, int length, int in)
+{
+	int ret;
+	char *p = buffer;
+
+	if (in)
+		return 0;
+
+	p += sprintf(p, "iop13xx IMU SCSI driver ver " MODULE_VERS "\n");
+
+#ifdef IMU_SCSI_PROFILING
+	p += sprintf(p,
+		     "  queue-commands: %d\n"
+		     "  rx-callbacks:   %d\n"
+		     "  failed-q-alloc: %d\n",
+		     imu_queue_commands, imu_rx_callbacks, imu_failed_alloc
+		);
+#endif
+
+	*start = buffer + offset;
+	ret = p - buffer - offset;
+	if (ret > length)
+		ret = length;
+
+	return ret;
+}
+
+
+int imu_scsi_send_gen_msg(int fcode)
+{
+	struct imu_cmd *msg;
+
+	msg = iop_queue_allocate(Q_NUM);
+	if (msg == NULL) {
+		imu_warn("failed to allocate imu msg for disconnect\n");
+		imu_scsi_prof(imu_failed_alloc++);
+		return FAILED;
+	}
+
+	msg->fcode             = fcode;
+	msg->array_context     = 0;
+	msg->app_context       = 0;
+	msg->connection_handle = imu_scsi->connection_handle;
+	if (iop_queue_postmsg(Q_NUM, msg))
+		return FAILED;
+
+	return SUCCESS;
+}
+
+static int imu_queuecommand(struct scsi_cmnd *scp,
+			    void (*done) (struct scsi_cmnd *))
+{
+	struct imu_cmd *msg;
+	int i, use_sg;
+	struct scatterlist *sglist;
+	struct imu_sge *sge, *sge_i;
+
+	imu_debug("imu_queuecommand for id=%d\n", scp->device->id);
+	imu_scsi_prof(imu_queue_commands++);
+	dbg_print_cmd(scp);
+
+	if (scp->device->id != 0) {
+		scp->result = (DID_BAD_TARGET << 16);
+		done(scp);
+		return 0;
+	}
+
+	scp->scsi_done = done;
+
+	if (!imu_scsi->connection_handle) {
+		scp->result = (DID_RESET << 16) | (SUGGEST_RETRY << 24);
+		return SCSI_MLQUEUE_HOST_BUSY;
+	}
+
+	msg = iop_queue_allocate(Q_NUM);
+	if (msg == NULL) {
+		imu_warn("failed to allocate imu msg\n");
+		imu_scsi_prof(imu_failed_alloc++);
+		return SCSI_MLQUEUE_HOST_BUSY;
+	}
+
+	msg->fcode = IMU_FCODE_CMD;
+	msg->flags = FLAG_CMD_RECEIVER_MUST_XFER;
+	msg->array_context = 0;
+	msg->app_context = (u32) scp;
+	msg->connection_handle = imu_scsi->connection_handle;
+	msg->cdb_cmd.lun = scp->device->lun << 8;
+	msg->cdb_cmd.ctl = 0;
+	msg->cdb_cmd.datalen = scp->request_bufflen;
+	memcpy(msg->cdb_cmd.cdb, scp->cmnd, 16);
+
+
+	if (scp->sc_data_direction == DMA_NONE)
+		imu_debug("direction DMA_NONE\n");
+
+	else if (scp->use_sg == 0) {
+		dma_addr_t buff = 0;
+		if (NULL == scp->request_buffer)
+			imu_warn("got cmd with NULL request_buffer\n");
+		else
+			buff = dma_map_single(NULL, scp->request_buffer,
+					      scp->request_bufflen,
+					      scp->sc_data_direction);
+		if (buff == 0) {
+			imu_warn("null return from dma_map_single\n");
+			msg->flags = IMU_FCODE_STATUS;
+			msg->status = 0;
+			iop_queue_postmsg(Q_NUM, msg);
+			return 1;
+		}
+		sge = &(msg->cdb_cmd.sge);
+		sge->addr_l = buff;
+		sge->addr_h = 0;
+		sge->flen = scp->request_bufflen | FLAG_LAST_ELEMENT;
+
+		imu_debug("cmd msg with 1 sgl entry, addr=0x%x\n", buff);
+	}
+	else if (scp->use_sg > 0) {
+		sglist = (struct scatterlist *)scp->request_buffer;
+		if (NULL == sglist) {
+			imu_warn("got null scp->buffer\n");
+			use_sg = 0;
+		} else
+			use_sg = dma_map_sg(NULL, sglist, scp->use_sg, 
+					    scp->sc_data_direction);
+		if (use_sg == 0) {
+			/* Send error status message */
+			imu_warn("null return from dma_map_sg\n");
+			msg->flags = IMU_FCODE_STATUS;
+			msg->status = 0;
+			iop_queue_postmsg(Q_NUM, msg);
+			return 1;
+		}
+
+		sge = sge_i = &(msg->cdb_cmd.sge);
+
+		for (i = 0; i < use_sg; i++, sglist++, sge_i++) {
+			sge_i->addr_l = sg_dma_address(sglist);
+			sge_i->addr_h = 0;
+			sge_i->flen = sg_dma_len(sglist);
+		}
+		sge[use_sg - 1].flen |= FLAG_LAST_ELEMENT;
+
+		imu_debug("cmd msg with %d sgl entries, "
+			  "last flen=0x%x last addr=0x%x\n", use_sg, 
+			  sge[use_sg - 1].flen,
+			  sge[use_sg - 1].addr_l);
+
+	}
+
+	if (iop_queue_postmsg(Q_NUM, msg))
+		return SCSI_MLQUEUE_DEVICE_BUSY;
+
+	return 0;
+}
+
+
+static int imu_eh_host_reset(struct scsi_cmnd *cmd)
+{
+	imu_warn("Received eh host reset\n");
+
+	if (imu_scsi->connection_handle && 
+	    imu_scsi_send_gen_msg(IMU_FCODE_ERROR))
+		return SUCCESS;
+
+	return FAILED;
+}
+
+
+static struct scsi_host_template imu_scsi_template = {
+	.module = THIS_MODULE,
+	.proc_info = imu_proc_info,
+	.name = "iop13xx IMU SCSI",
+	.queuecommand = imu_queuecommand,
+	.eh_host_reset_handler = imu_eh_host_reset,
+	.can_queue = Q_MSG_ITEMS - 2,
+	.this_id = -1,
+	.cmd_per_lun = 2,
+	.sg_tablesize = SGL_PER_CMD,
+	.use_clustering = ENABLE_CLUSTERING,
+	.proc_name = "iop13xx-imu",
+};
+
+void queue_rq_scsi_callback(int queueid)
+{
+	struct imu_cmd *msg;
+	struct scsi_cmnd *scp;
+
+	imu_debug("queue_rq_scsi_callback on queue %d\n", queueid);
+	imu_scsi_prof(imu_rx_callbacks++);
+
+	msg = iop_queue_getmsg(queueid);
+	if (msg == NULL)
+		return;
+
+	switch (msg->fcode) {
+
+	case IMU_FCODE_RESP:
+
+		scp = (struct scsi_cmnd *)((u32) msg->app_context);
+		if (!scp) {
+			imu_warn("got an invalid scp pointer\n");
+			break;
+		}
+
+		switch (msg->resp_cmd.response_code) {
+
+		case IMU_RESP_SUCCESS:
+			scp->result = (DID_OK << 16);
+			break;
+		case IMU_RESP_REJECT:
+		case IMU_RESP_FAILED:
+			scp->result = (DID_ABORT << 16);
+			break;
+		case IMU_RESP_INVALID:
+		default:
+			scp->result = (DID_ERROR << 16);
+			imu_warn("got a non success resp command:%d\n",
+				 msg->resp_cmd.scsi_status);
+			break;
+		}
+
+		if (msg->flags & FLAG_REP_SENSE_VALID) {
+			memcpy(scp->sense_buffer, msg->resp_cmd.sense_data,
+			       msg->resp_cmd.senselen < SCSI_SENSE_BUFFERSIZE ?
+			       msg->resp_cmd.senselen : SCSI_SENSE_BUFFERSIZE);
+			imu_debug("sense data in command of len %d\n",
+				  msg->resp_cmd.senselen);
+#ifdef IMU_DEBUG
+			scsi_print_sense("IMUscsi", scp);
+#endif
+		}
+
+		if (scp->scsi_done)
+			scp->scsi_done(scp);
+		else
+			imu_warn("no scsi_done set in response cmd\n");
+		break;
+
+	case IMU_FCODE_CONNECT_RESP:
+		if (!msg->status)
+			imu_scsi->connection_handle = msg->connection_handle;
+		else
+			imu_warn("rejected or failed connection request");
+
+		break;
+
+	case IMU_FCODE_DISCONNECT_RESP:
+		imu_scsi->connection_handle = 0;
+		break;
+
+	case IMU_FCODE_CMD:
+	case IMU_FCODE_XFER:
+	case IMU_FCODE_MANAGE:
+	case IMU_FCODE_CONNECT_REQ:
+	case IMU_FCODE_DISCONNECT_REQ:
+	case IMU_FCODE_CACHE_ALLOC_REQ:
+	case IMU_FCODE_CACHE_ALLOC_RESP:
+	case IMU_FCODE_LOG_REQ:
+	case IMU_FCODE_LOG_RESP:
+	case IMU_FCODE_ERROR:
+	default:
+		imu_warn("got a bad or unhandled fcode response %d\n",
+			 msg->fcode);
+		break;
+	}
+
+	iop_queue_rxfree(queueid, msg);
+	return;
+
+}
+
+/* We need to access queue structure to look for overlap */
+extern struct imu_queue_params imu_queue[];
+
+void init_scsi_bh(struct work_struct *unused)
+{
+	struct imu_queue_params *queue = &imu_queue[Q_NUM];
+	struct imu_queue *queue_hw = (struct imu_queue *)
+	    (IMU_Q0_BASE + (Q_NUM * sizeof(struct imu_queue)));
+
+	int phy_rxbase = queue_hw->rqlbar;
+	int rq_items = queue_hw->rqcr & 0xffff;
+
+	queue->rxbase = ioremap(phy_rxbase, rq_items * Q_MSG_SIZE);
+	/* todo: see about changing to cacheable and invalidate before alloc */
+
+	/* switch to regular callback and call */
+	iop_doorbell_reg_callback(NR_IMU_DOORBELLS - 1 +
+				  (IMU_DB_RQ0NE - IMU_DB_QUEUE_IRQ_OFF) +
+				  (Q_NUM * 2), queue_rq_scsi_callback);
+
+	imu_debug("init_scsi_callback registerd "
+		  "q=%d rxbase=0x%x rxphy=0x%x size=0x%x\n",
+		  Q_NUM, (int)queue->rxbase, phy_rxbase, rq_items * Q_MSG_SIZE);
+	queue_rq_scsi_callback(Q_NUM);
+	iop_doorbell_enable(IMU_DB_RQ0NE + (Q_NUM * 2));
+}
+
+
+static DECLARE_WORK(init_imu_scsi_tq, init_scsi_bh);
+
+void init_scsi_callback(int queueid)
+{
+	iop_doorbell_disable(IMU_DB_RQ0NE + (queueid * 2));
+	schedule_work(&init_imu_scsi_tq);
+}
+
+void error_scsi_callback(int queueid)
+{
+}
+
+static int imu_attach(void)
+{
+	char *queue_base;
+	int err;
+
+	imu_debug("imu_attach\n");
+
+	imu_queue[Q_NUM].rxbase = 0;
+
+	queue_base = ioremap(Q_PHYS_BASE + (Q_NUM * Q_SIZE), Q_SIZE);
+	/* todo: see about changing to bufferable mem and dmb before post */
+
+	if (queue_base == NULL) {
+		imu_warn("could not ioremap region\n");
+		return -1;
+	}
+
+	err = iop_queue_init(Q_NUM,
+			     (void *)Q_PHYS_BASE + (Q_NUM * Q_SIZE),
+			     queue_base,
+			     Q_MSG_SIZE,
+			     Q_MSG_ITEMS,
+			     init_scsi_callback, error_scsi_callback);
+	if (err) {
+		imu_warn("could not init queue\n");
+		iounmap(queue_base);
+		return -1;
+	}
+
+	printk(KERN_INFO MODULE_NAME ": using queue %d base:0x%x phy:0x%x\n",
+	       Q_NUM, (int)queue_base, Q_PHYS_BASE + (Q_NUM * Q_SIZE));
+
+	return 0;
+}
+
+static int imu_detach(void)
+{
+
+	iop_doorbell_disable(IMU_DB_RQ0NE + (Q_NUM * 2));
+	iop_doorbell_disable(IMU_DB_SQ0NF + (Q_NUM * 2));
+	if (imu_queue[Q_NUM].txbase) {
+		iounmap(imu_queue[Q_NUM].txbase);
+		imu_queue[Q_NUM].txbase = 0;
+	}
+	if (imu_queue[Q_NUM].rxbase) {
+		iounmap(imu_queue[Q_NUM].rxbase);
+		imu_queue[Q_NUM].rxbase = 0;
+	}
+
+	return 0;
+}
+
+static int __init imu_scsi_init(void)
+{
+	struct Scsi_Host *host;
+	struct imu_scsidata *data;
+	struct imu_cmd *msg;
+	int ret;
+
+	if (imu_attach()) {
+		imu_warn("imu queue alloc failed\n");
+		return -1;
+	}
+
+	host = scsi_host_alloc(&imu_scsi_template, sizeof(struct imu_scsidata));
+	if (!host)
+		return -ENOMEM;
+
+	data = (struct imu_scsidata *)host->hostdata;
+	imu_scsi = data;
+	data->host = host;
+	data->connection_handle = 1;  /* should be 0; bug in current target has
+					 bad response to connection request */
+
+	/* Send connection request msg */
+	msg = iop_queue_allocate(Q_NUM);
+	if (msg == NULL) {
+		imu_warn("failed to allocate imu msg for connect\n");
+		imu_scsi_prof(imu_failed_alloc++);
+		return -1;
+	}
+	msg->fcode = IMU_FCODE_CONNECT_REQ;
+	msg->array_context = 0;
+	msg->app_context = 0;
+	msg->connection_handle = 0;
+	msg->world_wide_name = IMU_WW_NAME;
+	iop_queue_postmsg(Q_NUM, msg);
+
+	imu_debug("scsi_add_host\n");
+
+	ret = scsi_add_host(host, NULL);
+	if (!ret) {
+		scsi_scan_host(host);
+		imu_debug("scsi_scan_host complete\n");
+	}
+
+	return ret;
+}
+
+static void __exit imu_scsi_exit(void)
+{
+	struct Scsi_Host *host = imu_scsi->host;
+
+	scsi_remove_host(host);
+	scsi_host_put(host);
+	imu_scsi_send_gen_msg(IMU_FCODE_DISCONNECT_REQ);
+	imu_detach();
+	printk(KERN_INFO MODULE_NAME ": Detached\n");
+}
+
+module_init(imu_scsi_init);
+module_exit(imu_scsi_exit);
+
+MODULE_AUTHOR("Greg Tucker");
+MODULE_DESCRIPTION("iop13xx IMU SCSI driver");
-
To unsubscribe from this list: send the line "unsubscribe linux-scsi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Index of Archives]     [SCSI Target Devel]     [Linux SCSI Target Infrastructure]     [Kernel Newbies]     [IDE]     [Security]     [Git]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux ATA RAID]     [Linux IIO]     [Samba]     [Device Mapper]
  Powered by Linux