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