From: Greg Tucker <[EMAIL PROTECTED]>
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 <[EMAIL PROTECTED]>
Signed-off-by: Dan Williams <[EMAIL PROTECTED]>
---
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 <[EMAIL PROTECTED]>
+ *
+ */
+
+#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 [EMAIL PROTECTED]
More majordomo info at http://vger.kernel.org/majordomo-info.html