From: Greg Tucker <greg.b.tucker@xxxxxxxxx> Enable Linux to access the other core as if it were a scsi target. 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 | 623 +++++++++++++++++++++++++++++++++++++ 3 files changed, 631 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..d32097b --- /dev/null +++ b/drivers/scsi/iop13xx-imu-scsi.c @@ -0,0 +1,623 @@ +/* + * drivers/scsi/iop13xx-imu-scsi.c + * + * SCSI low-level driver that forwards messages through the IOP342 IMU hw to 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/iop13xx-imu.h> + +#define MODULE_VERS "1.0" +#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 +//#define IMU_DEBUG + +/* 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)) + +#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) + +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 { + //unsigned char flags; + //unsigned int len : 24; + 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"); + + *start = buffer + offset; + ret = p - buffer - offset; + if (ret > length) + ret = length; + + return ret; +} + +#if 0 +void iop_loopback_test(struct scsi_cmnd *scp) +{ + + switch (scp->cmnd[0]) { + case READ_10: + case READ_6: + case WRITE_10: + case WRITE_6: + case MODE_SENSE_10: + case READ_CAPACITY: + case TEST_UNIT_READY: + default: + break; + } + memzero(scp->sense_buffer, SCSI_SENSE_BUFFERSIZE); + scp->result = (DID_OK << 16); + scp->result = 0; + if (scp->scsi_done) + scp->scsi_done(scp); +} +#else +# define iop_loopback_test(x) +#endif + +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); + 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); + done(scp); + return SCSI_MLQUEUE_HOST_BUSY; + } + + msg = iop_queue_allocate(Q_NUM); + if (msg == NULL) { + imu_warn("failed to allocate imu msg\n"); + 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; // todo: fill out + msg->cdb_cmd.datalen = scp->request_bufflen; + memcpy(msg->cdb_cmd.cdb, scp->cmnd, 16); + + switch (scp->cmnd[0]) { + case TEST_UNIT_READY: + break; + case READ_10: + case READ_6: + case WRITE_10: + case WRITE_6: + case READ_CAPACITY: + default: + 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, + DMA_BIDIRECTIONAL); + if (buff == 0) { + imu_warn("null return from dma_map_single\n"); + msg->flags = IMU_FCODE_STATUS; + msg->status = 0; //todo: what is error status? + 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; + + // This shouldn't be necessary + //dma_sync_single(NULL, buff, scp->request_bufflen, DMA_BIDIRECTIONAL); + imu_debug("cmd msg with 1 sgl entry, addr=0x%x\n", + buff); + } + 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, + DMA_BIDIRECTIONAL); + 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; //todo: need to send error status + iop_queue_postmsg(Q_NUM, msg); + return 1; + } + // This shouldn't be necessary + //dma_sync_sg(NULL, sglist, use_sg, DMA_BIDIRECTIONAL); + + 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); + + } + + break; + } + + iop_queue_postmsg(Q_NUM, msg); + iop_loopback_test(scp); + + return 0; +} + +static int imu_eh_abort(struct scsi_cmnd *cmd) +{ + imu_warn("Received eh command abort\n"); + //dbg_print_cmd(cmd); + return SUCCESS; +} + +static int imu_eh_device_reset(struct scsi_cmnd *cmd) +{ + int target = cmd->device->id; + imu_warn("Received eh device reset for target %d\n", target); + return SUCCESS; +} + +static int imu_eh_host_reset(struct scsi_cmnd *cmd) +{ + imu_warn("Received eh host reset\n"); + return SUCCESS; +} + +static int imu_eh_bus_reset(struct scsi_cmnd *cmd) +{ + imu_warn("Received eh bus reset\n"); + return SUCCESS; +} + +static struct scsi_host_template imu_scsi_template = { + .module = THIS_MODULE, + .proc_info = imu_proc_info, + .name = "iop13xx IMU SCSI", + .queuecommand = imu_queuecommand, + .eh_abort_handler = imu_eh_abort, + .eh_device_reset_handler = imu_eh_device_reset, + .eh_host_reset_handler = imu_eh_host_reset, + .eh_bus_reset_handler = imu_eh_bus_reset, + .can_queue = Q_MSG_ITEMS - 2, + .this_id = -1, + .cmd_per_lun = 2, + .sg_tablesize = SGL_PER_CMD, /* 1, SG_ALL, SG_NONE */ + .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); + 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; + } + //dbg_print_cmd(scp); + + 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); + printk("got sense data of %d bytes\n", + msg->resp_cmd.senselen); + scsi_print_sense("IMUscsi", scp); + } + + 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_callback(int queueid) +{ + struct imu_queue_params *queue = &imu_queue[queueid]; + struct imu_queue *queue_hw = (struct imu_queue *) + (IMU_Q0_BASE + (queueid * 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); + + /* switch to regular callback and call */ + iop_doorbell_reg_callback(NR_IMU_DOORBELLS - 1 + + (IMU_DB_RQ0NE - IMU_DB_QUEUE_IRQ_OFF) + + (queueid * 2), queue_rq_scsi_callback); + + imu_debug + ("init_scsi_callback registerd q=%d rxbase=0x%x rxphy=0x%x size=0x%x\n", + queueid, (int)queue->rxbase, phy_rxbase, rq_items * Q_MSG_SIZE); + queue_rq_scsi_callback(queueid); +} + +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 one of following + // non-shared device tex.cb = 0x010.00 + // shared device tex.cb = 0x001.01 + + 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)); + iounmap(imu_queue[Q_NUM].txbase); + + 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; // todo:change to 0 after connect request added + + /* Send connection request msg */ + msg = iop_queue_allocate(Q_NUM); + if (msg == NULL) { + imu_warn("failed to allocate imu msg for connect\n"); + 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); // dev? + 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); + + /* Send disconnect request message */ + /* Removed for debug + msg = iop_queue_allocate(Q_NUM); + if (msg == NULL) { + imu_warn("failed to allocate imu msg for disconnect\n"); + } + else { + msg->fcode = IMU_FCODE_DISCONNECT_REQ; + msg->array_context = 0; + msg->app_context = 0; + msg->connection_handle = imu_scsi->connection_handle;; + iop_queue_postmsg(Q_NUM, msg); + } + */ + 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