diff --git a/block/ll_rw_blk.c b/block/ll_rw_blk.c --- a/block/ll_rw_blk.c +++ b/block/ll_rw_blk.c @@ -2291,19 +2291,20 @@ int blk_rq_map_user(request_queue_t *q, else bio = bio_copy_user(q, uaddr, len, reading); - if (!IS_ERR(bio)) { - rq->bio = rq->biotail = bio; - blk_rq_bio_prep(q, rq, bio); + if (IS_ERR(bio)) + return PTR_ERR(bio); - rq->buffer = rq->data = NULL; - rq->data_len = len; - return 0; + if (bio->bi_size != len) { + bio_endio(bio, bio->bi_size, 0); + bio_unmap_user(bio); + return -EINVAL; } - /* - * bio is the err-ptr - */ - return PTR_ERR(bio); + rq->bio = rq->biotail = bio; + blk_rq_bio_prep(q, rq, bio); + rq->buffer = rq->data = NULL; + rq->data_len = len; + return 0; } EXPORT_SYMBOL(blk_rq_map_user); @@ -2329,7 +2330,7 @@ EXPORT_SYMBOL(blk_rq_map_user); * unmapping. */ int blk_rq_map_user_iov(request_queue_t *q, struct request *rq, - struct sg_iovec *iov, int iov_count) + struct sg_iovec *iov, int iov_count, unsigned int len) { struct bio *bio; @@ -2343,6 +2344,12 @@ int blk_rq_map_user_iov(request_queue_t if (IS_ERR(bio)) return PTR_ERR(bio); + if (bio->bi_size != len) { + bio_endio(bio, bio->bi_size, 0); + bio_unmap_user(bio); + return -EINVAL; + } + rq->bio = rq->biotail = bio; blk_rq_bio_prep(q, rq, bio); rq->buffer = rq->data = NULL; @@ -2764,16 +2771,12 @@ static void init_request_from_bio(struct req->errors = 0; req->hard_sector = req->sector = bio->bi_sector; - req->hard_nr_sectors = req->nr_sectors = bio_sectors(bio); - req->current_nr_sectors = req->hard_cur_sectors = bio_cur_sectors(bio); - req->nr_phys_segments = bio_phys_segments(req->q, bio); - req->nr_hw_segments = bio_hw_segments(req->q, bio); - req->buffer = bio_data(bio); /* see ->buffer comment above */ req->waiting = NULL; - req->bio = req->biotail = bio; req->ioprio = bio_prio(bio); req->rq_disk = bio->bi_bdev->bd_disk; req->start_time = jiffies; + + blk_rq_bio_prep(req->q, req, bio); } static int __make_request(request_queue_t *q, struct bio *bio) @@ -3402,9 +3405,6 @@ EXPORT_SYMBOL(end_request); void blk_rq_bio_prep(request_queue_t *q, struct request *rq, struct bio *bio) { - /* first three bits are identical in rq->flags and bio->bi_rw */ - rq->flags |= (bio->bi_rw & 7); - rq->nr_phys_segments = bio_phys_segments(q, bio); rq->nr_hw_segments = bio_hw_segments(q, bio); rq->current_nr_sectors = bio_cur_sectors(bio); diff --git a/block/scsi_ioctl.c b/block/scsi_ioctl.c --- a/block/scsi_ioctl.c +++ b/block/scsi_ioctl.c @@ -274,7 +274,8 @@ static int sg_io(struct file *file, requ goto out; } - ret = blk_rq_map_user_iov(q, rq, iov, hdr->iovec_count); + ret = blk_rq_map_user_iov(q, rq, iov, hdr->iovec_count, + hdr->dxfer_len); kfree(iov); } else if (hdr->dxfer_len) ret = blk_rq_map_user(q, rq, hdr->dxferp, hdr->dxfer_len); diff --git a/drivers/scsi/Kconfig b/drivers/scsi/Kconfig --- a/drivers/scsi/Kconfig +++ b/drivers/scsi/Kconfig @@ -27,6 +27,13 @@ config SCSI However, do not compile this as a module if your root file system (the one containing the directory /) is located on a SCSI device. +config SCSI_TGT + tristate "SCSI target support" + depends on SCSI && NET && EXPERIMENTAL + ---help--- + If you want to use SCSI target mode drivers enable this option. + If you choose M, the module will be called scsi_tgt. + config SCSI_PROC_FS bool "legacy /proc/scsi/ support" depends on SCSI && PROC_FS @@ -890,6 +897,20 @@ config SCSI_IBMVSCSI To compile this driver as a module, choose M here: the module will be called ibmvscsic. +config SCSI_IBMVSCSIS + tristate "IBM Virtual SCSI Server support" + depends on PPC_PSERIES && SCSI_TGT && SCSI_SRP + help + This is the SRP target driver for IBM pSeries virtual environments. + + The userspace component needed to initialize the driver and + documentation can be found: + + http://stgt.berlios.de/ + + To compile this driver as a module, choose M here: the + module will be called ibmvstgt. + config SCSI_INITIO tristate "Initio 9100U(W) support" depends on PCI && SCSI @@ -1827,6 +1848,15 @@ config ZFCP called zfcp. If you want to compile it as a module, say M here and read . +config SCSI_SRP + tristate "SCSI RDMA Protocol helper library" + depends on SCSI + help + If you wish to use SRP target drivers, say Y. + + To compile this driver as a module, choose M here: the + module will be called libsrp. + endmenu source "drivers/scsi/pcmcia/Kconfig" diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile --- a/drivers/scsi/Makefile +++ b/drivers/scsi/Makefile @@ -21,6 +21,7 @@ CFLAGS_seagate.o = -DARBITRATE -DPARIT subdir-$(CONFIG_PCMCIA) += pcmcia obj-$(CONFIG_SCSI) += scsi_mod.o +obj-$(CONFIG_SCSI_TGT) += scsi_tgt.o obj-$(CONFIG_RAID_ATTRS) += raid_class.o @@ -122,7 +123,9 @@ obj-$(CONFIG_SCSI_FCAL) += fcal.o obj-$(CONFIG_SCSI_LASI700) += 53c700.o lasi700.o obj-$(CONFIG_SCSI_NSP32) += nsp32.o obj-$(CONFIG_SCSI_IPR) += ipr.o +obj-$(CONFIG_SCSI_SRP) += libsrp.o obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsi/ +obj-$(CONFIG_SCSI_IBMVSCSIS) += ibmvscsi/ obj-$(CONFIG_SCSI_SATA_AHCI) += libata.o ahci.o obj-$(CONFIG_SCSI_SATA_SVW) += libata.o sata_svw.o obj-$(CONFIG_SCSI_ATA_PIIX) += libata.o ata_piix.o @@ -155,6 +158,8 @@ scsi_mod-y += scsi.o hosts.o scsi_ioct scsi_mod-$(CONFIG_SYSCTL) += scsi_sysctl.o scsi_mod-$(CONFIG_SCSI_PROC_FS) += scsi_proc.o +scsi_tgt-y += scsi_tgt_lib.o scsi_tgt_if.o + sd_mod-objs := sd.o sr_mod-objs := sr.o sr_ioctl.o sr_vendor.o ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \ diff --git a/drivers/scsi/hosts.c b/drivers/scsi/hosts.c --- a/drivers/scsi/hosts.c +++ b/drivers/scsi/hosts.c @@ -264,6 +264,11 @@ static void scsi_host_dev_release(struct if (shost->work_q) destroy_workqueue(shost->work_q); + if (shost->uspace_req_q) { + kfree(shost->uspace_req_q->queuedata); + scsi_free_queue(shost->uspace_req_q); + } + scsi_destroy_command_freelist(shost); kfree(shost->shost_data); diff --git a/drivers/scsi/ibmvscsi/Makefile b/drivers/scsi/ibmvscsi/Makefile --- a/drivers/scsi/ibmvscsi/Makefile +++ b/drivers/scsi/ibmvscsi/Makefile @@ -3,3 +3,5 @@ obj-$(CONFIG_SCSI_IBMVSCSI) += ibmvscsic ibmvscsic-y += ibmvscsi.o ibmvscsic-$(CONFIG_PPC_ISERIES) += iseries_vscsi.o ibmvscsic-$(CONFIG_PPC_PSERIES) += rpa_vscsi.o + +obj-$(CONFIG_SCSI_IBMVSCSIS) += ibmvstgt.o diff --git a/drivers/scsi/ibmvscsi/ibmvstgt.c b/drivers/scsi/ibmvscsi/ibmvstgt.c new file mode 100644 --- /dev/null +++ b/drivers/scsi/ibmvscsi/ibmvstgt.c @@ -0,0 +1,943 @@ +/* + * IBM eServer i/pSeries Virtual SCSI Target Driver + * Copyright (C) 2003-2005 Dave Boutcher (boutcher@us.ibm.com) IBM Corp. + * Santiago Leon (santil@us.ibm.com) IBM Corp. + * Linda Xie (lxie@us.ibm.com) IBM Corp. + * + * Copyright (C) 2005-2006 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ibmvscsi.h" + +#define INITIAL_SRP_LIMIT 16 +#define DEFAULT_MAX_SECTORS 512 + +#define TGT_NAME "ibmvstgt" + +/* + * Hypervisor calls. + */ +#define h_copy_rdma(l, sa, sb, da, db) \ + plpar_hcall_norets(H_COPY_RDMA, l, sa, sb, da, db) +#define h_send_crq(ua, l, h) \ + plpar_hcall_norets(H_SEND_CRQ, ua, l, h) +#define h_reg_crq(ua, tok, sz)\ + plpar_hcall_norets(H_REG_CRQ, ua, tok, sz); +#define h_free_crq(ua) \ + plpar_hcall_norets(H_FREE_CRQ, ua); + +/* tmp - will replace with SCSI logging stuff */ +#define eprintk(fmt, args...) \ +do { \ + printk("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ +} while (0) +/* #define dprintk eprintk */ +#define dprintk(fmt, args...) + +struct vio_port { + struct vio_dev *dma_dev; + + struct crq_queue crq_queue; + struct work_struct crq_work; + + unsigned long liobn; + unsigned long riobn; +}; + +static struct workqueue_struct *vtgtd; + +/* + * These are fixed for the system and come from the Open Firmware device tree. + * We just store them here to save getting them every time. + */ +static char system_id[64] = ""; +static char partition_name[97] = "UNKNOWN"; +static unsigned int partition_number = -1; + +static struct vio_port *target_to_port(struct srp_target *target) +{ + return (struct vio_port *) target->ldata; +} + +static inline union viosrp_iu *vio_iu(struct iu_entry *iue) +{ + return (union viosrp_iu *) (iue->sbuf->buf); +} + +static int send_iu(struct iu_entry *iue, uint64_t length, uint8_t format) +{ + struct srp_target *target = iue->target; + struct vio_port *vport = target_to_port(target); + long rc, rc1; + union { + struct viosrp_crq cooked; + uint64_t raw[2]; + } crq; + + /* First copy the SRP */ + rc = h_copy_rdma(length, vport->liobn, iue->sbuf->dma, + vport->riobn, iue->remote_token); + + if (rc) + eprintk("Error %ld transferring data\n", rc); + + crq.cooked.valid = 0x80; + crq.cooked.format = format; + crq.cooked.reserved = 0x00; + crq.cooked.timeout = 0x00; + crq.cooked.IU_length = length; + crq.cooked.IU_data_ptr = vio_iu(iue)->srp.rsp.tag; + + if (rc == 0) + crq.cooked.status = 0x99; /* Just needs to be non-zero */ + else + crq.cooked.status = 0x00; + + rc1 = h_send_crq(vport->dma_dev->unit_address, crq.raw[0], crq.raw[1]); + + if (rc1) { + eprintk("%ld sending response\n", rc1); + return rc1; + } + + return rc; +} + +#define SRP_RSP_SENSE_DATA_LEN 18 + +static int send_rsp(struct iu_entry *iue, unsigned char status, + unsigned char asc) +{ + union viosrp_iu *iu = vio_iu(iue); + uint64_t tag = iu->srp.rsp.tag; + + /* If the linked bit is on and status is good */ + if (test_bit(V_LINKED, &iue->flags) && (status == NO_SENSE)) + status = 0x10; + + memset(iu, 0, sizeof(struct srp_rsp)); + iu->srp.rsp.opcode = SRP_RSP; + iu->srp.rsp.req_lim_delta = 1; + iu->srp.rsp.tag = tag; + + if (test_bit(V_DIOVER, &iue->flags)) + iu->srp.rsp.flags |= SRP_RSP_FLAG_DIOVER; + + iu->srp.rsp.data_in_res_cnt = 0; + iu->srp.rsp.data_out_res_cnt = 0; + + iu->srp.rsp.flags &= ~SRP_RSP_FLAG_RSPVALID; + + iu->srp.rsp.resp_data_len = 0; + iu->srp.rsp.status = status; + if (status) { + uint8_t *sense = iu->srp.rsp.data; + + if (iue->scmd) { + iu->srp.rsp.flags |= SRP_RSP_FLAG_SNSVALID; + iu->srp.rsp.sense_data_len = SCSI_SENSE_BUFFERSIZE; + memcpy(sense, iue->scmd->sense_buffer, + SCSI_SENSE_BUFFERSIZE); + } else { + iu->srp.rsp.status = SAM_STAT_CHECK_CONDITION; + iu->srp.rsp.flags |= SRP_RSP_FLAG_SNSVALID; + iu->srp.rsp.sense_data_len = SRP_RSP_SENSE_DATA_LEN; + + /* Valid bit and 'current errors' */ + sense[0] = (0x1 << 7 | 0x70); + /* Sense key */ + sense[2] = status; + /* Additional sense length */ + sense[7] = 0xa; /* 10 bytes */ + /* Additional sense code */ + sense[12] = asc; + } + } + + send_iu(iue, sizeof(iu->srp.rsp) + SRP_RSP_SENSE_DATA_LEN, + VIOSRP_SRP_FORMAT); + + return 0; +} + +static void handle_cmd_queue(struct srp_target *target) +{ + struct iu_entry *iue; + unsigned long flags; + +retry: + spin_lock_irqsave(&target->lock, flags); + + list_for_each_entry(iue, &target->cmd_queue, ilist) { + if (!test_and_set_bit(V_FLYING, &iue->flags)) { + spin_unlock_irqrestore(&target->lock, flags); + srp_cmd_perform(iue, (struct srp_cmd *) iue->sbuf->buf); + goto retry; + } + } + + spin_unlock_irqrestore(&target->lock, flags); +} + +static int ibmvstgt_rdma(struct iu_entry *iue, struct scatterlist *sg, int nsg, + struct srp_direct_buf *md, int nmd, + enum dma_data_direction dir, unsigned int rest) +{ + struct srp_target *target = iue->target; + struct vio_port *vport = target_to_port(target); + dma_addr_t token; + long err; + unsigned int done = 0; + int i, sidx, soff; + + sidx = soff = 0; + token = sg_dma_address(sg + sidx); + + for (i = 0; i < nmd && rest; i++) { + unsigned int mdone, mlen; + + mlen = min(rest, md[i].len); + for (mdone = 0; mlen;) { + int slen = min(sg_dma_len(sg + sidx) - soff, mlen); + + if (dir == DMA_TO_DEVICE) + err = h_copy_rdma(slen, + vport->riobn, + md[i].va + mdone, + vport->liobn, + token + soff); + else + err = h_copy_rdma(slen, + vport->liobn, + token + soff, + vport->riobn, + md[i].va + mdone); + + if (err != H_Success) { + eprintk("rdma error %d %d\n", dir, slen); + goto out; + } + + mlen -= slen; + mdone += slen; + soff += slen; + done += slen; + + if (soff == sg_dma_len(sg + sidx)) { + sidx++; + soff = 0; + token = sg_dma_address(sg + sidx); + + if (sidx > nsg) { + eprintk("out of sg %p %d %d\n", + iue, sidx, nsg); + goto out; + } + } + }; + + rest -= mlen; + } +out: + + return 0; +} + +static int ibmvstgt_transfer_data(struct scsi_cmnd *scmd, + void (*done)(struct scsi_cmnd *)) +{ + struct iu_entry *iue = (struct iu_entry *) scmd->SCp.ptr; + int err; + + err = srp_transfer_data(scmd, &vio_iu(iue)->srp.cmd, ibmvstgt_rdma); + done(scmd); + + return err; +} + +static int ibmvstgt_cmd_done(struct scsi_cmnd *scmd, + void (*done)(struct scsi_cmnd *)) +{ + unsigned long flags; + struct iu_entry *iue = (struct iu_entry *) scmd->SCp.ptr; + struct srp_target *target = iue->target; + + dprintk("%p %p %x\n", iue, target, vio_iu(iue)->srp.cmd.cdb[0]); + + spin_lock_irqsave(&target->lock, flags); + list_del(&iue->ilist); + spin_unlock_irqrestore(&target->lock, flags); + + if (scmd->result != SAM_STAT_GOOD) { + eprintk("operation failed %p %d %x\n", + iue, scmd->result, vio_iu(iue)->srp.cmd.cdb[0]); + send_rsp(iue, HARDWARE_ERROR, 0x00); + } else + send_rsp(iue, NO_SENSE, 0x00); + + done(scmd); + srp_iu_put(iue); + return 0; +} + +int send_adapter_info(struct iu_entry *iue, + dma_addr_t remote_buffer, uint16_t length) +{ + struct srp_target *target = iue->target; + struct vio_port *vport = target_to_port(target); + struct Scsi_Host *shost = target->shost; + dma_addr_t data_token; + struct mad_adapter_info_data *info; + int err; + + info = dma_alloc_coherent(target->dev, sizeof(*info), &data_token, + GFP_KERNEL); + if (!info) { + eprintk("bad dma_alloc_coherent %p\n", target); + return 1; + } + + /* Get remote info */ + err = h_copy_rdma(sizeof(*info), vport->riobn, remote_buffer, + vport->liobn, data_token); + if (err == H_Success) { + dprintk("Client connect: %s (%d)\n", + info->partition_name, info->partition_number); + } + + memset(info, 0, sizeof(*info)); + + strcpy(info->srp_version, "16.a"); + strncpy(info->partition_name, partition_name, + sizeof(info->partition_name)); + info->partition_number = partition_number; + info->mad_version = 1; + info->os_type = 2; + info->port_max_txu[0] = shost->hostt->max_sectors << 9; + + /* Send our info to remote */ + err = h_copy_rdma(sizeof(*info), vport->liobn, data_token, + vport->riobn, remote_buffer); + + dma_free_coherent(target->dev, sizeof(*info), info, data_token); + + if (err != H_Success) { + eprintk("Error sending adapter info %d\n", err); + return 1; + } + + return 0; +} + +static void process_login(struct iu_entry *iue) +{ + union viosrp_iu *iu = vio_iu(iue); + struct srp_login_rsp *rsp = &iu->srp.login_rsp; + uint64_t tag = iu->srp.rsp.tag; + + /* TODO handle case that requested size is wrong and + * buffer format is wrong + */ + memset(iu, 0, sizeof(struct srp_login_rsp)); + rsp->opcode = SRP_LOGIN_RSP; + rsp->req_lim_delta = INITIAL_SRP_LIMIT; + rsp->tag = tag; + rsp->max_it_iu_len = sizeof(union srp_iu); + rsp->max_ti_iu_len = sizeof(union srp_iu); + /* direct and indirect */ + rsp->buf_fmt = SRP_BUF_FORMAT_DIRECT | SRP_BUF_FORMAT_INDIRECT; + + send_iu(iue, sizeof(*rsp), VIOSRP_SRP_FORMAT); +} + +static inline void queue_cmd(struct iu_entry *iue) +{ + struct srp_target *target = iue->target; + unsigned long flags; + + spin_lock_irqsave(&target->lock, flags); + list_add_tail(&iue->ilist, &target->cmd_queue); + spin_unlock_irqrestore(&target->lock, flags); +} + +static int process_tsk_mgmt(struct iu_entry *iue) +{ + union viosrp_iu *iu = vio_iu(iue); + int fn; + + dprintk("%p %u\n", iue, iu->srp.tsk_mgmt.tsk_mgmt_func); + + switch (iu->srp.tsk_mgmt.tsk_mgmt_func) { + case SRP_TSK_ABORT_TASK: + fn = ABORT_TASK; + break; + case SRP_TSK_ABORT_TASK_SET: + fn = ABORT_TASK_SET; + break; + case SRP_TSK_CLEAR_TASK_SET: + fn = CLEAR_TASK_SET; + break; + case SRP_TSK_LUN_RESET: + fn = LOGICAL_UNIT_RESET; + break; + case SRP_TSK_CLEAR_ACA: + fn = CLEAR_ACA; + break; + default: + fn = 0; + } + if (fn) + scsi_tgt_tsk_mgmt_request(iue->target->shost, fn, + iu->srp.tsk_mgmt.task_tag, + (struct scsi_lun *) &iu->srp.tsk_mgmt.lun, + iue); + else + send_rsp(iue, ILLEGAL_REQUEST, 0x20); + + return !fn; +} + +static int process_mad_iu(struct iu_entry *iue) +{ + union viosrp_iu *iu = vio_iu(iue); + struct viosrp_adapter_info *info; + struct viosrp_host_config *conf; + + switch (iu->mad.empty_iu.common.type) { + case VIOSRP_EMPTY_IU_TYPE: + eprintk("%s\n", "Unsupported EMPTY MAD IU"); + break; + case VIOSRP_ERROR_LOG_TYPE: + eprintk("%s\n", "Unsupported ERROR LOG MAD IU"); + iu->mad.error_log.common.status = 1; + send_iu(iue, sizeof(iu->mad.error_log), VIOSRP_MAD_FORMAT); + break; + case VIOSRP_ADAPTER_INFO_TYPE: + info = &iu->mad.adapter_info; + info->common.status = send_adapter_info(iue, info->buffer, + info->common.length); + send_iu(iue, sizeof(*info), VIOSRP_MAD_FORMAT); + break; + case VIOSRP_HOST_CONFIG_TYPE: + conf = &iu->mad.host_config; + conf->common.status = 1; + send_iu(iue, sizeof(*conf), VIOSRP_MAD_FORMAT); + break; + default: + eprintk("Unknown type %u\n", iu->srp.rsp.opcode); + } + + return 1; +} + +static int process_srp_iu(struct iu_entry *iue) +{ + union viosrp_iu *iu = vio_iu(iue); + int done = 1; + u8 opcode = iu->srp.rsp.opcode; + + switch (opcode) { + case SRP_LOGIN_REQ: + process_login(iue); + break; + case SRP_TSK_MGMT: + done = process_tsk_mgmt(iue); + break; + case SRP_CMD: + queue_cmd(iue); + done = 0; + break; + case SRP_LOGIN_RSP: + case SRP_I_LOGOUT: + case SRP_T_LOGOUT: + case SRP_RSP: + case SRP_CRED_REQ: + case SRP_CRED_RSP: + case SRP_AER_REQ: + case SRP_AER_RSP: + eprintk("Unsupported type %u\n", opcode); + break; + default: + eprintk("Unknown type %u\n", opcode); + } + + return done; +} + +static void process_iu(struct viosrp_crq *crq, struct srp_target *target) +{ + struct vio_port *vport = target_to_port(target); + struct iu_entry *iue; + long err, done; + + iue = srp_iu_get(target); + if (!iue) { + eprintk("Error getting IU from pool, %p\n", target); + return; + } + + iue->remote_token = crq->IU_data_ptr; + + err = h_copy_rdma(crq->IU_length, vport->riobn, + iue->remote_token, vport->liobn, iue->sbuf->dma); + + if (err != H_Success) + eprintk("%ld transferring data error %p\n", err, iue); + + if (crq->format == VIOSRP_MAD_FORMAT) + done = process_mad_iu(iue); + else + done = process_srp_iu(iue); + + if (done) + srp_iu_put(iue); +} + +static irqreturn_t ibmvstgt_interrupt(int irq, void *data, struct pt_regs *regs) +{ + struct srp_target *target = (struct srp_target *) data; + struct vio_port *vport = target_to_port(target); + + vio_disable_interrupts(vport->dma_dev); + queue_work(vtgtd, &vport->crq_work); + + return IRQ_HANDLED; +} + +static int crq_queue_create(struct crq_queue *queue, struct srp_target *target) +{ + int err; + struct vio_port *vport = target_to_port(target); + + queue->msgs = (struct viosrp_crq *) get_zeroed_page(GFP_KERNEL); + if (!queue->msgs) + goto malloc_failed; + queue->size = PAGE_SIZE / sizeof(*queue->msgs); + + queue->msg_token = dma_map_single(target->dev, queue->msgs, + queue->size * sizeof(*queue->msgs), + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(queue->msg_token)) + goto map_failed; + + err = h_reg_crq(vport->dma_dev->unit_address, queue->msg_token, + PAGE_SIZE); + + /* If the adapter was left active for some reason (like kexec) + * try freeing and re-registering + */ + if (err == H_Resource) { + do { + err = h_free_crq(vport->dma_dev->unit_address); + } while (err == H_Busy || H_isLongBusy(err)); + + err = h_reg_crq(vport->dma_dev->unit_address, queue->msg_token, + PAGE_SIZE); + } + + if (err != H_Success && err != 2) { + eprintk("Error 0x%x opening virtual adapter\n", err); + goto reg_crq_failed; + } + + err = request_irq(vport->dma_dev->irq, &ibmvstgt_interrupt, + SA_INTERRUPT, "ibmvstgt", target); + if (err) + goto req_irq_failed; + + vio_enable_interrupts(vport->dma_dev); + + h_send_crq(vport->dma_dev->unit_address, 0xC001000000000000, 0); + + queue->cur = 0; + spin_lock_init(&queue->lock); + + return 0; + +req_irq_failed: + do { + err = h_free_crq(vport->dma_dev->unit_address); + } while (err == H_Busy || H_isLongBusy(err)); + +reg_crq_failed: + dma_unmap_single(target->dev, queue->msg_token, + queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL); +map_failed: + free_page((unsigned long) queue->msgs); + +malloc_failed: + return -ENOMEM; +} + +static void crq_queue_destroy(struct srp_target *target) +{ + struct vio_port *vport = target_to_port(target); + struct crq_queue *queue = &vport->crq_queue; + int err; + + free_irq(vport->dma_dev->irq, target); + do { + err = h_free_crq(vport->dma_dev->unit_address); + } while (err == H_Busy || H_isLongBusy(err)); + + dma_unmap_single(target->dev, queue->msg_token, + queue->size * sizeof(*queue->msgs), DMA_BIDIRECTIONAL); + + free_page((unsigned long) queue->msgs); +} + +static void process_crq(struct viosrp_crq *crq, struct srp_target *target) +{ + struct vio_port *vport = target_to_port(target); + dprintk("%x %x\n", crq->valid, crq->format); + + switch (crq->valid) { + case 0xC0: + /* initialization */ + switch (crq->format) { + case 0x01: + h_send_crq(vport->dma_dev->unit_address, + 0xC002000000000000, 0); + break; + case 0x02: + break; + default: + eprintk("Unknown format %u\n", crq->format); + } + break; + case 0xFF: + /* transport event */ + break; + case 0x80: + /* real payload */ + switch (crq->format) { + case VIOSRP_SRP_FORMAT: + case VIOSRP_MAD_FORMAT: + process_iu(crq, target); + break; + case VIOSRP_OS400_FORMAT: + case VIOSRP_AIX_FORMAT: + case VIOSRP_LINUX_FORMAT: + case VIOSRP_INLINE_FORMAT: + eprintk("Unsupported format %u\n", crq->format); + break; + default: + eprintk("Unknown format %u\n", crq->format); + } + break; + default: + eprintk("unknown message type 0x%02x!?\n", crq->valid); + } +} + +static inline struct viosrp_crq *next_crq(struct crq_queue *queue) +{ + struct viosrp_crq *crq; + unsigned long flags; + + spin_lock_irqsave(&queue->lock, flags); + crq = &queue->msgs[queue->cur]; + if (crq->valid & 0x80) { + if (++queue->cur == queue->size) + queue->cur = 0; + } else + crq = NULL; + spin_unlock_irqrestore(&queue->lock, flags); + + return crq; +} + +static void handle_crq(void *data) +{ + struct srp_target *target = (struct srp_target *) data; + struct vio_port *vport = target_to_port(target); + struct viosrp_crq *crq; + int done = 0; + + while (!done) { + while ((crq = next_crq(&vport->crq_queue)) != NULL) { + process_crq(crq, target); + crq->valid = 0x00; + } + + vio_enable_interrupts(vport->dma_dev); + + crq = next_crq(&vport->crq_queue); + if (crq) { + vio_disable_interrupts(vport->dma_dev); + process_crq(crq, target); + crq->valid = 0x00; + } else + done = 1; + } + + handle_cmd_queue(target); +} + + +static int ibmvstgt_eh_abort_handler(struct scsi_cmnd *scmd) +{ + unsigned long flags; + struct iu_entry *iue = (struct iu_entry *) scmd->SCp.ptr; + struct srp_target *target = iue->target; + + dprintk("%p %p %x\n", iue, target, vio_iu(iue)->srp.cmd.cdb[0]); + + spin_lock_irqsave(&target->lock, flags); + list_del(&iue->ilist); + spin_unlock_irqrestore(&target->lock, flags); + + srp_iu_put(iue); + + return 0; +} + +static int ibmvstgt_tsk_mgmt_response(u64 mid, int result) +{ + struct iu_entry *iue = (struct iu_entry *) ((void *) mid); + union viosrp_iu *iu = vio_iu(iue); + unsigned char status, asc; + + eprintk("%p %d\n", iue, result); + status = NO_SENSE; + asc = 0; + + switch (iu->srp.tsk_mgmt.tsk_mgmt_func) { + case SRP_TSK_ABORT_TASK: + asc = 0x14; + if (result) + status = ABORTED_COMMAND; + break; + default: + break; + } + + send_rsp(iue, status, asc); + srp_iu_put(iue); + + return 0; +} + +static ssize_t system_id_show(struct class_device *cdev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%s\n", system_id); +} + +static ssize_t partition_number_show(struct class_device *cdev, char *buf) +{ + return snprintf(buf, PAGE_SIZE, "%x\n", partition_number); +} + +static ssize_t unit_address_show(struct class_device *cdev, char *buf) +{ + struct Scsi_Host *shost = class_to_shost(cdev); + struct srp_target *target = host_to_target(shost); + struct vio_port *vport = target_to_port(target); + return snprintf(buf, PAGE_SIZE, "%x\n", vport->dma_dev->unit_address); +} + +static CLASS_DEVICE_ATTR(system_id, S_IRUGO, system_id_show, NULL); +static CLASS_DEVICE_ATTR(partition_number, S_IRUGO, partition_number_show, NULL); +static CLASS_DEVICE_ATTR(unit_address, S_IRUGO, unit_address_show, NULL); + +static struct class_device_attribute *ibmvstgt_attrs[] = { + &class_device_attr_system_id, + &class_device_attr_partition_number, + &class_device_attr_unit_address, + NULL, +}; + +static struct scsi_host_template ibmvstgt_sht = { + .name = TGT_NAME, + .module = THIS_MODULE, + .can_queue = INITIAL_SRP_LIMIT, + .sg_tablesize = SG_ALL, + .use_clustering = DISABLE_CLUSTERING, + .max_sectors = DEFAULT_MAX_SECTORS, + .transfer_response = ibmvstgt_cmd_done, + .transfer_data = ibmvstgt_transfer_data, + .eh_abort_handler = ibmvstgt_eh_abort_handler, + .tsk_mgmt_response = ibmvstgt_tsk_mgmt_response, + .shost_attrs = ibmvstgt_attrs, + .proc_name = TGT_NAME, +}; + +static int ibmvstgt_probe(struct vio_dev *dev, const struct vio_device_id *id) +{ + struct Scsi_Host *shost; + struct srp_target *target; + struct vio_port *vport; + unsigned int *dma, dma_size; + int err = -ENOMEM; + + vport = kzalloc(sizeof(struct vio_port), GFP_KERNEL); + if (!vport) + return err; + shost = scsi_host_alloc(&ibmvstgt_sht, sizeof(struct srp_target)); + if (!shost) + goto free_vport; + err = scsi_tgt_alloc_queue(shost); + if (err) + goto put_host; + + target = host_to_target(shost); + target->shost = shost; + vport->dma_dev = dev; + target->ldata = vport; + err = srp_target_alloc(target, &dev->dev, INITIAL_SRP_LIMIT, + SRP_MAX_IU_LEN); + if (err) + goto put_host; + + dma = (unsigned int *) vio_get_attribute(dev, "ibm,my-dma-window", + &dma_size); + if (!dma || dma_size != 40) { + eprintk("Couldn't get window property %d\n", dma_size); + err = -EIO; + goto free_srp_target; + } + vport->liobn = dma[0]; + vport->riobn = dma[5]; + + INIT_WORK(&vport->crq_work, handle_crq, target); + + err = crq_queue_create(&vport->crq_queue, target); + if (err) + goto free_srp_target; + + err = scsi_add_host(shost, target->dev); + if (err) + goto destroy_queue; + return 0; + +destroy_queue: + crq_queue_destroy(target); +free_srp_target: + srp_target_free(target); +put_host: + scsi_host_put(shost); +free_vport: + kfree(vport); + return err; +} + +static int ibmvstgt_remove(struct vio_dev *dev) +{ + struct srp_target *target = (struct srp_target *) dev->dev.driver_data; + struct Scsi_Host *shost = target->shost; + + srp_target_free(target); + crq_queue_destroy(target); + scsi_remove_host(shost); + scsi_host_put(shost); + return 0; +} + +static struct vio_device_id ibmvstgt_device_table[] __devinitdata = { + {"v-scsi-host", "IBM,v-scsi-host"}, + {"",""} +}; + +MODULE_DEVICE_TABLE(vio, ibmvstgt_device_table); + +static struct vio_driver ibmvstgt_driver = { + .id_table = ibmvstgt_device_table, + .probe = ibmvstgt_probe, + .remove = ibmvstgt_remove, + .driver = { + .name = "ibmvscsi", + .owner = THIS_MODULE, + } +}; + +static int get_system_info(void) +{ + struct device_node *rootdn; + char *id, *model, *name; + unsigned int *num; + + rootdn = find_path_device("/"); + if (!rootdn) + return -ENOENT; + + model = get_property(rootdn, "model", NULL); + id = get_property(rootdn, "system-id", NULL); + if (model && id) + snprintf(system_id, sizeof(system_id), "%s-%s", model, id); + + name = get_property(rootdn, "ibm,partition-name", NULL); + if (name) + strncpy(partition_name, name, sizeof(partition_name)); + + num = (unsigned int *) get_property(rootdn, "ibm,partition-no", NULL); + if (num) + partition_number = *num; + + return 0; +} + +static int ibmvstgt_init(void) +{ + int err = -ENOMEM; + + printk("IBM eServer i/pSeries Virtual SCSI Target Driver\n"); + + vtgtd = create_workqueue("ibmvtgtd"); + if (!vtgtd) + return err; + + err = get_system_info(); + if (err) + goto destroy_wq; + + err = vio_register_driver(&ibmvstgt_driver); + if (err) + goto destroy_wq; + + return 0; + +destroy_wq: + destroy_workqueue(vtgtd); + return err; +} + +static void ibmvstgt_exit(void) +{ + printk("Unregister IBM virtual SCSI driver\n"); + + destroy_workqueue(vtgtd); + vio_unregister_driver(&ibmvstgt_driver); +} + +MODULE_DESCRIPTION("IBM Virtual SCSI Target"); +MODULE_AUTHOR("Dave Boutcher"); +MODULE_LICENSE("GPL"); + +module_init(ibmvstgt_init); +module_exit(ibmvstgt_exit); diff --git a/drivers/scsi/libsrp.c b/drivers/scsi/libsrp.c new file mode 100644 --- /dev/null +++ b/drivers/scsi/libsrp.c @@ -0,0 +1,449 @@ +/* + * SCSI RDAM Protocol lib functions + * + * Copyright (C) 2006 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum srp_task_attributes { + SRP_SIMPLE_TASK = 0, + SRP_HEAD_TASK = 1, + SRP_ORDERED_TASK = 2, + SRP_ACA_TASK = 4 +}; + +/* tmp - will replace with SCSI logging stuff */ +#define eprintk(fmt, args...) \ +do { \ + printk("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ +} while (0) +/* #define dprintk eprintk */ +#define dprintk(fmt, args...) + +static int srp_iu_pool_alloc(struct srp_queue *q, size_t max, + struct srp_buf **ring) +{ + int i; + struct iu_entry *iue; + + q->pool = kcalloc(max, sizeof(struct iu_entry *), GFP_KERNEL); + if (!q->pool) + return -ENOMEM; + q->items = kcalloc(max, sizeof(struct iu_entry), GFP_KERNEL); + if (!q->items) + goto free_pool; + + spin_lock_init(&q->lock); + q->queue = kfifo_init((void *) q->pool, max * sizeof(void *), + GFP_KERNEL, &q->lock); + if (IS_ERR(q->queue)) + goto free_item; + + for (i = 0, iue = q->items; i < max; i++) { + __kfifo_put(q->queue, (void *) &iue, sizeof(void *)); + iue->sbuf = ring[i]; + iue++; + } + return 0; + +free_item: + kfree(q->items); +free_pool: + kfree(q->pool); + return -ENOMEM; +} + +static void srp_iu_pool_free(struct srp_queue *q) +{ + kfree(q->items); + kfree(q->pool); +} + +static struct srp_buf ** srp_ring_alloc(struct device *dev, + size_t max, size_t size) +{ + int i; + struct srp_buf **ring; + + ring = kcalloc(max, sizeof(struct srp_buf *), GFP_KERNEL); + if (!ring) + return NULL; + + for (i = 0; i < max; i++) { + ring[i] = kzalloc(sizeof(struct srp_buf), GFP_KERNEL); + if (!ring[i]) + goto out; + ring[i]->buf = dma_alloc_coherent(dev, size, &ring[i]->dma, + GFP_KERNEL); + if (!ring[i]->buf) + goto out; + } + return ring; + +out: + for (i = 0; i < max && ring[i]; i++) { + if (ring[i]->buf) + dma_free_coherent(dev, size, ring[i]->buf, ring[i]->dma); + kfree(ring[i]); + } + kfree(ring); + + return NULL; +} + +static void srp_ring_free(struct device *dev, struct srp_buf **ring, size_t max, + size_t size) +{ + int i; + + for (i = 0; i < max; i++) { + dma_free_coherent(dev, size, ring[i]->buf, ring[i]->dma); + kfree(ring[i]); + } +} + +int srp_target_alloc(struct srp_target *target, struct device *dev, + size_t nr, size_t iu_size) +{ + int err; + + spin_lock_init(&target->lock); + INIT_LIST_HEAD(&target->cmd_queue); + + target->dev = dev; + target->dev->driver_data = target; + + target->srp_iu_size = iu_size; + target->rx_ring_size = nr; + target->rx_ring = srp_ring_alloc(target->dev, nr, iu_size); + if (!target->rx_ring) + return -ENOMEM; + err = srp_iu_pool_alloc(&target->iu_queue, nr, target->rx_ring); + if (err) + goto free_ring; + + return 0; + +free_ring: + srp_ring_free(target->dev, target->rx_ring, nr, iu_size); + return -ENOMEM; +} +EXPORT_SYMBOL_GPL(srp_target_alloc); + +void srp_target_free(struct srp_target *target) +{ + srp_ring_free(target->dev, target->rx_ring, target->rx_ring_size, + target->srp_iu_size); + srp_iu_pool_free(&target->iu_queue); +} +EXPORT_SYMBOL_GPL(srp_target_free); + +struct iu_entry *srp_iu_get(struct srp_target *target) +{ + struct iu_entry *iue = NULL; + + kfifo_get(target->iu_queue.queue, (void *) &iue, sizeof(void *)); + BUG_ON(!iue); + + iue->target = target; + iue->scmd = NULL; + INIT_LIST_HEAD(&iue->ilist); + iue->flags = 0; + return iue; +} +EXPORT_SYMBOL_GPL(srp_iu_get); + +void srp_iu_put(struct iu_entry *iue) +{ + kfifo_put(iue->target->iu_queue.queue, (void *) &iue, sizeof(void *)); +} +EXPORT_SYMBOL_GPL(srp_iu_put); + +static int direct_data(struct scsi_cmnd *scmd, struct srp_direct_buf *md, + enum dma_data_direction dir, rdma_io_t rdma_io) +{ + struct iu_entry *iue = (struct iu_entry *) scmd->SCp.ptr; + struct srp_target *target = iue->target; + struct scatterlist *sg = scmd->request_buffer; + int nsg, err; + + dprintk("%p %u %u %u %d\n", iue, scmd->request_bufflen, scmd->bufflen, + md->len, scmd->use_sg); + + nsg = dma_map_sg(target->dev, sg, scmd->use_sg, DMA_BIDIRECTIONAL); + if (!nsg) { + printk("fail to map %p %d\n", iue, scmd->use_sg); + return 0; + } + err = rdma_io(iue, sg, nsg, md, 1, dir, + min(scmd->request_bufflen, md->len)); + + dma_unmap_sg(target->dev, sg, nsg, DMA_BIDIRECTIONAL); + + return err; +} + +static int indirect_data(struct scsi_cmnd *scmd, struct srp_cmd *cmd, + struct srp_indirect_buf *id, + enum dma_data_direction dir, rdma_io_t rdma_io) +{ + struct iu_entry *iue = (struct iu_entry *) scmd->SCp.ptr; + struct srp_target *target = iue->target; + struct srp_direct_buf *md; + struct scatterlist dummy, *sg = scmd->request_buffer; + dma_addr_t token = 0; + long err; + unsigned int done = 0; + int nmd, nsg; + + nmd = id->table_desc.len / sizeof(struct srp_direct_buf); + + dprintk("%p %u %u %u %u %d %d %d\n", + iue, scmd->request_bufflen, scmd->bufflen, + id->len, scmd->offset, nmd, + cmd->data_in_desc_cnt, cmd->data_out_desc_cnt); + + if ((dir == DMA_FROM_DEVICE && nmd == cmd->data_in_desc_cnt) || + (dir == DMA_TO_DEVICE && nmd == cmd->data_out_desc_cnt)) { + md = &id->desc_list[0]; + goto rdma; + } + + md = dma_alloc_coherent(target->dev, id->table_desc.len, + &token, GFP_KERNEL); + if (!md) { + eprintk("Can't get dma memory %u\n", id->table_desc.len); + return 0; + } + + sg_init_one(&dummy, md, id->table_desc.len); + sg_dma_address(&dummy) = token; + err = rdma_io(iue, &dummy, 1, &id->table_desc, 1, DMA_TO_DEVICE, + id->table_desc.len); + if (err < 0) { + eprintk("Error copying indirect table %ld\n", err); + goto free_mem; + } + +rdma: + nsg = dma_map_sg(target->dev, sg, scmd->use_sg, DMA_BIDIRECTIONAL); + if (!nsg) { + eprintk("fail to map %p %d\n", iue, scmd->use_sg); + goto free_mem; + } + + err = rdma_io(iue, sg, nsg, md, nmd, dir, + min(scmd->request_bufflen, id->len)); + dma_unmap_sg(target->dev, sg, nsg, DMA_BIDIRECTIONAL); + +free_mem: + if (token) + dma_free_coherent(target->dev, id->table_desc.len, md, token); + + return done; +} + +static int data_out_desc_size(struct srp_cmd *cmd) +{ + int size = 0; + u8 fmt = cmd->buf_fmt >> 4; + + switch (fmt) { + case SRP_NO_DATA_DESC: + break; + case SRP_DATA_DESC_DIRECT: + size = sizeof(struct srp_direct_buf); + break; + case SRP_DATA_DESC_INDIRECT: + size = sizeof(struct srp_indirect_buf) + + sizeof(struct srp_direct_buf) * cmd->data_out_desc_cnt; + break; + default: + eprintk("client error. Invalid data_out_format %x\n", fmt); + break; + } + return size; +} + +static int __srp_transfer_data(struct scsi_cmnd *scmd, struct srp_cmd *cmd, + enum dma_data_direction dir, rdma_io_t rdma_io) +{ + struct srp_direct_buf *md; + struct srp_indirect_buf *id; + int offset, err = 0; + u8 format; + + offset = cmd->add_cdb_len * 4; + if (dir == DMA_FROM_DEVICE) + offset += data_out_desc_size(cmd); + + if (dir == DMA_TO_DEVICE) + format = cmd->buf_fmt >> 4; + else + format = cmd->buf_fmt & ((1U << 4) - 1); + + switch (format) { + case SRP_NO_DATA_DESC: + break; + case SRP_DATA_DESC_DIRECT: + md = (struct srp_direct_buf *) + (cmd->add_data + offset); + err = direct_data(scmd, md, dir, rdma_io); + break; + case SRP_DATA_DESC_INDIRECT: + id = (struct srp_indirect_buf *) + (cmd->add_data + offset); + err = indirect_data(scmd, cmd, id, dir, rdma_io); + break; + default: + eprintk("Unknown format %d %x\n", dir, format); + break; + } + + return err; +} + +/* TODO: this can be called multiple times for a single command. */ +int srp_transfer_data(struct scsi_cmnd *scmd, struct srp_cmd *cmd, + rdma_io_t rdma_io) +{ + struct iu_entry *iue = (struct iu_entry *) scmd->SCp.ptr; + enum dma_data_direction dir; + + if (test_bit(V_WRITE, &iue->flags)) + dir = DMA_TO_DEVICE; + else + dir = DMA_FROM_DEVICE; + __srp_transfer_data(scmd, cmd, dir, rdma_io); + return 0; +} +EXPORT_SYMBOL_GPL(srp_transfer_data); + +static int vscsis_data_length(struct srp_cmd *cmd, enum dma_data_direction dir) +{ + struct srp_direct_buf *md; + struct srp_indirect_buf *id; + int len = 0, offset = cmd->add_cdb_len * 4; + u8 fmt; + + if (dir == DMA_TO_DEVICE) + fmt = cmd->buf_fmt >> 4; + else { + fmt = cmd->buf_fmt & ((1U << 4) - 1); + offset += data_out_desc_size(cmd); + } + + switch (fmt) { + case SRP_NO_DATA_DESC: + break; + case SRP_DATA_DESC_DIRECT: + md = (struct srp_direct_buf *) (cmd->add_data + offset); + len = md->len; + break; + case SRP_DATA_DESC_INDIRECT: + id = (struct srp_indirect_buf *) (cmd->add_data + offset); + len = id->len; + break; + default: + eprintk("invalid data format %x\n", fmt); + break; + } + return len; +} + +static uint8_t getcontrolbyte(u8 *cdb) +{ + return cdb[COMMAND_SIZE(cdb[0]) - 1]; +} + +static inline uint8_t getlink(struct srp_cmd *cmd) +{ + return (getcontrolbyte(cmd->cdb) & 0x01); +} + +int srp_cmd_perform(struct iu_entry *iue, struct srp_cmd *cmd) +{ + struct Scsi_Host *shost = iue->target->shost; + enum dma_data_direction data_dir; + struct scsi_cmnd *scmd; + int tag, len; + + if (getlink(cmd)) + __set_bit(V_LINKED, &iue->flags); + + tag = MSG_SIMPLE_TAG; + + switch (cmd->task_attr) { + case SRP_SIMPLE_TASK: + tag = MSG_SIMPLE_TAG; + break; + case SRP_ORDERED_TASK: + tag = MSG_ORDERED_TAG; + break; + case SRP_HEAD_TASK: + tag = MSG_HEAD_TAG; + break; + default: + eprintk("Task attribute %d not supported\n", cmd->task_attr); + tag = MSG_ORDERED_TAG; + } + + switch (cmd->cdb[0]) { + case WRITE_6: + case WRITE_10: + case WRITE_VERIFY: + case WRITE_12: + case WRITE_VERIFY_12: + __set_bit(V_WRITE, &iue->flags); + } + + if (cmd->buf_fmt >> 4) + data_dir = DMA_TO_DEVICE; + else + data_dir = DMA_FROM_DEVICE; + len = vscsis_data_length(cmd, data_dir); + + dprintk("%p %x %lx %d %d %d %llx\n", iue, cmd->cdb[0], + cmd->lun, data_dir, len, tag, (unsigned long long) cmd->tag); + + scmd = scsi_host_get_command(shost, data_dir, GFP_KERNEL); + BUG_ON(!scmd); + scmd->SCp.ptr = (char *) iue; + memcpy(scmd->cmnd, cmd->cdb, MAX_COMMAND_SIZE); + scmd->request_bufflen = len; + scmd->tag = tag; + iue->scmd = scmd; + scsi_tgt_queue_command(scmd, (struct scsi_lun *) &cmd->lun, cmd->tag); + + return 0; +} +EXPORT_SYMBOL_GPL(srp_cmd_perform); + +MODULE_DESCRIPTION("SCSI RDAM Protocol lib functions"); +MODULE_AUTHOR("FUJITA Tomonori"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -236,6 +236,58 @@ static struct scsi_cmnd *__scsi_get_comm } /* + * Function: scsi_host_get_command() + * + * Purpose: Allocate and setup a scsi command block and blk request + * + * Arguments: shost - scsi host + * data_dir - dma data dir + * gfp_mask- allocator flags + * + * Returns: The allocated scsi command structure. + * + * This should be called by target LLDs to get a command. + */ +struct scsi_cmnd *scsi_host_get_command(struct Scsi_Host *shost, + enum dma_data_direction data_dir, + gfp_t gfp_mask) +{ + int write = (data_dir == DMA_TO_DEVICE); + struct request *rq; + struct scsi_cmnd *cmd; + + /* Bail if we can't get a reference to the device */ + if (!get_device(&shost->shost_gendev)) + return NULL; + + rq = blk_get_request(shost->uspace_req_q, write, gfp_mask); + if (!rq) + goto put_dev; + + cmd = __scsi_get_command(shost, gfp_mask); + if (!cmd) + goto release_rq; + + memset(cmd, 0, sizeof(*cmd)); + cmd->sc_data_direction = data_dir; + cmd->jiffies_at_alloc = jiffies; + cmd->request = rq; + + rq->special = cmd; + rq->flags |= REQ_SPECIAL | REQ_BLOCK_PC; + + return cmd; + +release_rq: + blk_put_request(rq); +put_dev: + put_device(&shost->shost_gendev); + return NULL; + +} +EXPORT_SYMBOL_GPL(scsi_host_get_command); + +/* * Function: scsi_get_command() * * Purpose: Allocate and setup a scsi command block @@ -274,6 +326,43 @@ struct scsi_cmnd *scsi_get_command(struc EXPORT_SYMBOL(scsi_get_command); /* + * Function: scsi_host_put_command() + * + * Purpose: Free a scsi command block + * + * Arguments: shost - scsi host + * cmd - command block to free + * + * Returns: Nothing. + * + * Notes: The command must not belong to any lists. + */ +void scsi_host_put_command(struct Scsi_Host *shost, struct scsi_cmnd *cmd) +{ + struct request_queue *q = shost->uspace_req_q; + struct request *rq = cmd->request; + unsigned long flags; + + /* changing locks here, don't need to restore the irq state */ + spin_lock_irqsave(&shost->free_list_lock, flags); + if (unlikely(list_empty(&shost->free_list))) { + list_add(&cmd->list, &shost->free_list); + cmd = NULL; + } + spin_unlock(&shost->free_list_lock); + + spin_lock(q->queue_lock); + __blk_put_request(q, rq); + spin_unlock_irqrestore(q->queue_lock, flags); + + if (likely(cmd != NULL)) + kmem_cache_free(shost->cmd_pool->slab, cmd); + + put_device(&shost->shost_gendev); +} +EXPORT_SYMBOL_GPL(scsi_host_put_command); + +/* * Function: scsi_put_command() * * Purpose: Free a scsi command block diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -804,7 +804,7 @@ static struct scsi_cmnd *scsi_end_reques return NULL; } -static struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *cmd, gfp_t gfp_mask) +struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *cmd, gfp_t gfp_mask) { struct scsi_host_sg_pool *sgp; struct scatterlist *sgl; @@ -845,7 +845,9 @@ static struct scatterlist *scsi_alloc_sg return sgl; } -static void scsi_free_sgtable(struct scatterlist *sgl, int index) +EXPORT_SYMBOL(scsi_alloc_sgtable); + +void scsi_free_sgtable(struct scatterlist *sgl, int index) { struct scsi_host_sg_pool *sgp; @@ -855,6 +857,8 @@ static void scsi_free_sgtable(struct sca mempool_free(sgl, sgp->pool); } +EXPORT_SYMBOL(scsi_free_sgtable); + /* * Function: scsi_release_buffers() * @@ -1687,29 +1691,40 @@ u64 scsi_calculate_bounce_limit(struct S } EXPORT_SYMBOL(scsi_calculate_bounce_limit); -struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) +struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, + request_fn_proc *request_fn) { - struct Scsi_Host *shost = sdev->host; struct request_queue *q; - q = blk_init_queue(scsi_request_fn, NULL); + q = blk_init_queue(request_fn, NULL); if (!q) return NULL; - blk_queue_prep_rq(q, scsi_prep_fn); - blk_queue_max_hw_segments(q, shost->sg_tablesize); blk_queue_max_phys_segments(q, SCSI_MAX_PHYS_SEGMENTS); blk_queue_max_sectors(q, shost->max_sectors); blk_queue_bounce_limit(q, scsi_calculate_bounce_limit(shost)); blk_queue_segment_boundary(q, shost->dma_boundary); - blk_queue_issue_flush_fn(q, scsi_issue_flush_fn); - blk_queue_softirq_done(q, scsi_softirq_done); if (!shost->use_clustering) clear_bit(QUEUE_FLAG_CLUSTER, &q->queue_flags); return q; } +EXPORT_SYMBOL(__scsi_alloc_queue); + +struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) +{ + struct request_queue *q; + + q = __scsi_alloc_queue(sdev->host, scsi_request_fn); + if (!q) + return NULL; + + blk_queue_prep_rq(q, scsi_prep_fn); + blk_queue_issue_flush_fn(q, scsi_issue_flush_fn); + blk_queue_softirq_done(q, scsi_softirq_done); + return q; +} void scsi_free_queue(struct request_queue *q) { diff --git a/drivers/scsi/scsi_tgt_if.c b/drivers/scsi/scsi_tgt_if.c new file mode 100644 --- /dev/null +++ b/drivers/scsi/scsi_tgt_if.c @@ -0,0 +1,232 @@ +/* + * SCSI target kernel/user interface functions + * + * Copyright (C) 2005 FUJITA Tomonori + * Copyright (C) 2005 Mike Christie + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scsi_tgt_priv.h" + +static int tgtd_pid; +static struct sock *nl_sk; + +static int send_event_rsp(uint16_t type, struct tgt_event *p, gfp_t flags, + pid_t pid) +{ + struct tgt_event *ev; + struct nlmsghdr *nlh; + struct sk_buff *skb; + uint32_t len; + + len = NLMSG_SPACE(sizeof(*ev)); + skb = alloc_skb(len, flags); + if (!skb) + return -ENOMEM; + + nlh = __nlmsg_put(skb, pid, 0, type, len - sizeof(*nlh), 0); + + ev = NLMSG_DATA(nlh); + memcpy(ev, p, sizeof(*ev)); + + return netlink_unicast(nl_sk, skb, pid, 0); +} + +int scsi_tgt_uspace_send(struct scsi_cmnd *cmd, struct scsi_lun *lun, u64 tag, + gfp_t flags) +{ + struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); + struct sk_buff *skb; + struct nlmsghdr *nlh; + struct tgt_event *ev; + int err, len; + + len = NLMSG_SPACE(sizeof(*ev)); + /* + * TODO: add MAX_COMMAND_SIZE to ev and add mempool + */ + skb = alloc_skb(NLMSG_SPACE(len), flags); + if (!skb) + return -ENOMEM; + + nlh = __nlmsg_put(skb, tgtd_pid, 0, TGT_KEVENT_CMD_REQ, + len - sizeof(*nlh), 0); + + ev = NLMSG_DATA(nlh); + ev->k.cmd_req.host_no = shost->host_no; + ev->k.cmd_req.cid = cmd->request->tag; + ev->k.cmd_req.data_len = cmd->request_bufflen; + memcpy(ev->k.cmd_req.scb, cmd->cmnd, sizeof(ev->k.cmd_req.scb)); + memcpy(ev->k.cmd_req.lun, lun, sizeof(ev->k.cmd_req.lun)); + ev->k.cmd_req.attribute = cmd->tag; + ev->k.cmd_req.tag = tag; + + dprintk("%p %d %u %u %x %llx\n", cmd, shost->host_no, ev->k.cmd_req.cid, + ev->k.cmd_req.data_len, cmd->tag, + (unsigned long long) ev->k.cmd_req.tag); + + err = netlink_unicast(nl_sk, skb, tgtd_pid, 0); + if (err < 0) + printk(KERN_ERR "scsi_tgt_uspace_send: could not send skb %d\n", + err); + return err; +} + +int scsi_tgt_uspace_send_status(struct scsi_cmnd *cmd, gfp_t gfp_mask) +{ + struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); + struct tgt_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.k.cmd_done.host_no = shost->host_no; + ev.k.cmd_done.cid = cmd->request->tag; + ev.k.cmd_done.result = cmd->result; + + return send_event_rsp(TGT_KEVENT_CMD_DONE, &ev, gfp_mask, tgtd_pid); +} + +int scsi_tgt_uspace_send_tsk_mgmt(int host_no, int function, u64 tag, + struct scsi_lun *scsilun, void *data) +{ + struct tgt_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.k.tsk_mgmt_req.host_no = host_no; + ev.k.tsk_mgmt_req.function = function; + ev.k.tsk_mgmt_req.tag = tag; + memcpy(ev.k.tsk_mgmt_req.lun, scsilun, sizeof(ev.k.tsk_mgmt_req.lun)); + ev.k.tsk_mgmt_req.mid = (u64) (unsigned long) data; + + dprintk("%d %x %llx %llx\n", host_no, function, (unsigned long long) tag, + (unsigned long long) ev.k.tsk_mgmt_req.mid); + + return send_event_rsp(TGT_KEVENT_TSK_MGMT_REQ, &ev, GFP_KERNEL, tgtd_pid); +} + +static int event_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) +{ + struct tgt_event *ev = NLMSG_DATA(nlh); + int err = 0; + + dprintk("%d %d %d\n", nlh->nlmsg_type, + nlh->nlmsg_pid, current->pid); + + switch (nlh->nlmsg_type) { + case TGT_UEVENT_REQ: + tgtd_pid = NETLINK_CREDS(skb)->pid; + break; + case TGT_UEVENT_CMD_RSP: + /* TODO: handle multiple cmds in one event */ + err = scsi_tgt_kspace_exec(ev->u.cmd_rsp.host_no, + ev->u.cmd_rsp.cid, + ev->u.cmd_rsp.result, + ev->u.cmd_rsp.len, + ev->u.cmd_rsp.uaddr, + ev->u.cmd_rsp.rw); + break; + case TGT_UEVENT_TSK_MGMT_RSP: + err = scsi_tgt_kspace_tsk_mgmt(ev->u.tsk_mgmt_rsp.host_no, + ev->u.tsk_mgmt_rsp.mid, + ev->u.tsk_mgmt_rsp.result); + break; + default: + eprintk("unknown type %d\n", nlh->nlmsg_type); + err = -EINVAL; + } + + return err; +} + +static int event_recv_skb(struct sk_buff *skb) +{ + int err; + uint32_t rlen; + struct nlmsghdr *nlh; + struct tgt_event ev; + + while (skb->len >= NLMSG_SPACE(0)) { + nlh = (struct nlmsghdr *) skb->data; + if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) + return 0; + rlen = NLMSG_ALIGN(nlh->nlmsg_len); + if (rlen > skb->len) + rlen = skb->len; + err = event_recv_msg(skb, nlh); + + dprintk("%d %d\n", nlh->nlmsg_type, err); + /* + * TODO for passthru commands the lower level should + * probably handle the result or we should modify this + */ + switch (nlh->nlmsg_type) { + case TGT_UEVENT_CMD_RSP: + case TGT_UEVENT_TSK_MGMT_RSP: + break; + default: + memset(&ev, 0, sizeof(ev)); + ev.k.event_rsp.err = err; + send_event_rsp(TGT_KEVENT_RSP, &ev, + GFP_KERNEL | __GFP_NOFAIL, + nlh->nlmsg_pid); + } + skb_pull(skb, rlen); + } + return 0; +} + +static void event_recv(struct sock *sk, int length) +{ + struct sk_buff *skb; + + while ((skb = skb_dequeue(&sk->sk_receive_queue))) { + if (NETLINK_CREDS(skb)->uid) { + skb_pull(skb, skb->len); + kfree_skb(skb); + continue; + } + + if (event_recv_skb(skb) && skb->len) + skb_queue_head(&sk->sk_receive_queue, skb); + else + kfree_skb(skb); + } +} + +void __exit scsi_tgt_if_exit(void) +{ + sock_release(nl_sk->sk_socket); +} + +int __init scsi_tgt_if_init(void) +{ + nl_sk = netlink_kernel_create(NETLINK_TGT, 1, event_recv, + THIS_MODULE); + if (!nl_sk) + return -ENOMEM; + + return 0; +} diff --git a/drivers/scsi/scsi_tgt_lib.c b/drivers/scsi/scsi_tgt_lib.c new file mode 100644 --- /dev/null +++ b/drivers/scsi/scsi_tgt_lib.c @@ -0,0 +1,672 @@ +/* + * SCSI target lib functions + * + * Copyright (C) 2005 Mike Christie + * Copyright (C) 2005 FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include <../drivers/md/dm-bio-list.h> + +#include "scsi_tgt_priv.h" + +static struct workqueue_struct *scsi_tgtd; +static kmem_cache_t *scsi_tgt_cmd_cache; + +/* + * TODO: this struct will be killed when the block layer supports large bios + * and James's work struct code is in + */ +struct scsi_tgt_cmd { + /* TODO replace work with James b's code */ + struct work_struct work; + /* TODO replace the lists with a large bio */ + struct bio_list xfer_done_list; + struct bio_list xfer_list; + struct scsi_lun *lun; + + struct list_head hash_list; + struct request *rq; + u64 tag; + + void *buffer; + unsigned bufflen; +}; + +#define TGT_HASH_ORDER 4 +#define cmd_hashfn(cid) hash_long((cid), TGT_HASH_ORDER) + +struct scsi_tgt_queuedata { + struct Scsi_Host *shost; + struct list_head cmd_hash[1 << TGT_HASH_ORDER]; + spinlock_t cmd_hash_lock; + + struct work_struct uspace_send_work; + + spinlock_t cmd_req_lock; + struct mutex cmd_req_mutex; + struct list_head cmd_req; +}; + +static void scsi_unmap_user_pages(struct scsi_tgt_cmd *tcmd) +{ + struct bio *bio; + + /* must call bio_endio in case bio was bounced */ + while ((bio = bio_list_pop(&tcmd->xfer_done_list))) { + bio_endio(bio, bio->bi_size, 0); + bio_unmap_user(bio); + } + + while ((bio = bio_list_pop(&tcmd->xfer_list))) { + bio_endio(bio, bio->bi_size, 0); + bio_unmap_user(bio); + } +} + +static void scsi_tgt_cmd_destroy(void *data) +{ + struct scsi_cmnd *cmd = data; + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + struct scsi_tgt_queuedata *qdata = cmd->request->q->queuedata; + unsigned long flags; + + dprintk("cmd %p %d %lu\n", cmd, cmd->sc_data_direction, + rq_data_dir(cmd->request)); + + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + list_del(&tcmd->hash_list); + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); + + /* + * We must set rq->flags here because bio_map_user and + * blk_rq_bio_prep ruined ti. + */ + if (cmd->sc_data_direction == DMA_TO_DEVICE) + cmd->request->flags |= 1; + else + cmd->request->flags &= ~1UL; + + scsi_unmap_user_pages(tcmd); + kmem_cache_free(scsi_tgt_cmd_cache, tcmd); + scsi_host_put_command(scsi_tgt_cmd_to_host(cmd), cmd); +} + +static void init_scsi_tgt_cmd(struct request *rq, struct scsi_tgt_cmd *tcmd) +{ + struct scsi_tgt_queuedata *qdata = rq->q->queuedata; + unsigned long flags; + struct list_head *head; + static u32 tag = 0; + + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + rq->tag = tag++; + head = &qdata->cmd_hash[cmd_hashfn(rq->tag)]; + list_add(&tcmd->hash_list, head); + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); +} + +static void scsi_tgt_uspace_send_fn(void *data) +{ + struct request_queue *q = data; + struct scsi_tgt_queuedata *qdata = q->queuedata; + struct request *rq; + struct scsi_cmnd *cmd; + struct scsi_tgt_cmd *tcmd; + unsigned long flags; + int err; + +retry: + err = 0; + if (list_empty(&qdata->cmd_req)) + return; + + mutex_lock(&qdata->cmd_req_mutex); + + spin_lock_irqsave(&qdata->cmd_req_lock, flags); + if (list_empty(&qdata->cmd_req)) { + spin_unlock_irqrestore(&qdata->cmd_req_lock, flags); + mutex_unlock(&qdata->cmd_req_mutex); + goto out; + } + rq = list_entry_rq(qdata->cmd_req.next); + list_del_init(&rq->queuelist); + spin_unlock_irqrestore(&qdata->cmd_req_lock, flags); + + tcmd = rq->end_io_data; + init_scsi_tgt_cmd(rq, tcmd); + cmd = rq->special; + err = scsi_tgt_uspace_send(cmd, tcmd->lun, tcmd->tag, GFP_ATOMIC); + if (err < 0) { + eprintk("failed to send: %p %d\n", cmd, err); + + spin_lock_irqsave(&qdata->cmd_req_lock, flags); + list_add(&rq->queuelist, &qdata->cmd_req); + spin_unlock_irqrestore(&qdata->cmd_req_lock, flags); + } + + mutex_unlock(&qdata->cmd_req_mutex); +out: + /* TODO: proper error handling */ + if (err < 0) + queue_delayed_work(scsi_tgtd, &qdata->uspace_send_work, + HZ / 10); + else + goto retry; +} + +/** + * scsi_tgt_alloc_queue - setup queue used for message passing + * shost: scsi host + * + * This should be called by the LLD after host allocation. + * And will be released when the host is released. + **/ +int scsi_tgt_alloc_queue(struct Scsi_Host *shost) +{ + struct scsi_tgt_queuedata *queuedata; + struct request_queue *q; + int err, i; + + /* + * Do we need to send a netlink event or should uspace + * just respond to the hotplug event? + */ + q = __scsi_alloc_queue(shost, NULL); + if (!q) + return -ENOMEM; + + queuedata = kzalloc(sizeof(*queuedata), GFP_KERNEL); + if (!queuedata) { + err = -ENOMEM; + goto cleanup_queue; + } + queuedata->shost = shost; + q->queuedata = queuedata; + + /* + * this is a silly hack. We should probably just queue as many + * command as is recvd to userspace. uspace can then make + * sure we do not overload the HBA + */ + q->nr_requests = shost->hostt->can_queue; + /* + * We currently only support software LLDs so this does + * not matter for now. Do we need this for the cards we support? + * If so we should make it a host template value. + */ + blk_queue_dma_alignment(q, 0); + shost->uspace_req_q = q; + + for (i = 0; i < ARRAY_SIZE(queuedata->cmd_hash); i++) + INIT_LIST_HEAD(&queuedata->cmd_hash[i]); + spin_lock_init(&queuedata->cmd_hash_lock); + + INIT_LIST_HEAD(&queuedata->cmd_req); + spin_lock_init(&queuedata->cmd_req_lock); + INIT_WORK(&queuedata->uspace_send_work, scsi_tgt_uspace_send_fn, q); + mutex_init(&queuedata->cmd_req_mutex); + + return 0; + +cleanup_queue: + blk_cleanup_queue(q); + return err; +} +EXPORT_SYMBOL_GPL(scsi_tgt_alloc_queue); + +struct Scsi_Host *scsi_tgt_cmd_to_host(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_queuedata *queue = cmd->request->q->queuedata; + return queue->shost; +} +EXPORT_SYMBOL_GPL(scsi_tgt_cmd_to_host); + +/** + * scsi_tgt_queue_command - queue command for userspace processing + * @cmd: scsi command + * @scsilun: scsi lun + * @noblock: set to nonzero if the command should be queued + **/ +int scsi_tgt_queue_command(struct scsi_cmnd *cmd, struct scsi_lun *scsilun, + u64 tag) +{ + struct request_queue *q = cmd->request->q; + struct scsi_tgt_queuedata *qdata = q->queuedata; + unsigned long flags; + struct scsi_tgt_cmd *tcmd; + + /* + * It would be better to allocate scsi_tgt_cmd structure in + * scsi_host_get_command and not to fail due to OOM. + */ + tcmd = kmem_cache_alloc(scsi_tgt_cmd_cache, GFP_ATOMIC); + if (!tcmd) + return -ENOMEM; + cmd->request->end_io_data = tcmd; + + bio_list_init(&tcmd->xfer_list); + bio_list_init(&tcmd->xfer_done_list); + tcmd->lun = scsilun; + tcmd->tag = tag; + tcmd->rq = cmd->request; + + spin_lock_irqsave(&qdata->cmd_req_lock, flags); + list_add_tail(&cmd->request->queuelist, &qdata->cmd_req); + spin_unlock_irqrestore(&qdata->cmd_req_lock, flags); + + queue_work(scsi_tgtd, &qdata->uspace_send_work); + return 0; +} +EXPORT_SYMBOL_GPL(scsi_tgt_queue_command); + +/* + * This is run from a interrpt handler normally and the unmap + * needs process context so we must queue + */ +static void scsi_tgt_cmd_done(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + + dprintk("cmd %p %lu\n", cmd, rq_data_dir(cmd->request)); + + scsi_tgt_uspace_send_status(cmd, GFP_ATOMIC); + INIT_WORK(&tcmd->work, scsi_tgt_cmd_destroy, cmd); + queue_work(scsi_tgtd, &tcmd->work); +} + +static int __scsi_tgt_transfer_response(struct scsi_cmnd *cmd) +{ + struct Scsi_Host *shost = scsi_tgt_cmd_to_host(cmd); + int err; + + dprintk("cmd %p %lu\n", cmd, rq_data_dir(cmd->request)); + + err = shost->hostt->transfer_response(cmd, scsi_tgt_cmd_done); + switch (err) { + case SCSI_MLQUEUE_HOST_BUSY: + case SCSI_MLQUEUE_DEVICE_BUSY: + return -EAGAIN; + } + + return 0; +} + +static void scsi_tgt_transfer_response(struct scsi_cmnd *cmd) +{ + int err; + + err = __scsi_tgt_transfer_response(cmd); + if (!err) + return; + + cmd->result = DID_BUS_BUSY << 16; + if (scsi_tgt_uspace_send_status(cmd, GFP_ATOMIC) <= 0) + /* the eh will have to pick this up */ + printk(KERN_ERR "Could not send cmd %p status\n", cmd); +} + +static int scsi_tgt_init_cmd(struct scsi_cmnd *cmd, gfp_t gfp_mask) +{ + struct request *rq = cmd->request; + struct scsi_tgt_cmd *tcmd = rq->end_io_data; + int count; + + cmd->use_sg = rq->nr_phys_segments; + cmd->request_buffer = scsi_alloc_sgtable(cmd, gfp_mask); + if (!cmd->request_buffer) + return -ENOMEM; + + cmd->request_bufflen = rq->data_len; + + dprintk("cmd %p addr %p cnt %d %lu\n", cmd, tcmd->buffer, cmd->use_sg, + rq_data_dir(rq)); + count = blk_rq_map_sg(rq->q, rq, cmd->request_buffer); + if (likely(count <= cmd->use_sg)) { + cmd->use_sg = count; + return 0; + } + + eprintk("cmd %p addr %p cnt %d\n", cmd, tcmd->buffer, cmd->use_sg); + scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); + return -EINVAL; +} + +/* TODO: test this crap and replace bio_map_user with new interface maybe */ +static int scsi_map_user_pages(struct scsi_tgt_cmd *tcmd, struct scsi_cmnd *cmd, + int rw) +{ + struct request_queue *q = cmd->request->q; + struct request *rq = cmd->request; + void *uaddr = tcmd->buffer; + unsigned int len = tcmd->bufflen; + struct bio *bio; + int err; + + while (len > 0) { + dprintk("%lx %u\n", (unsigned long) uaddr, len); + bio = bio_map_user(q, NULL, (unsigned long) uaddr, len, rw); + if (IS_ERR(bio)) { + err = PTR_ERR(bio); + dprintk("fail to map %lx %u %d %x\n", + (unsigned long) uaddr, len, err, cmd->cmnd[0]); + goto unmap_bios; + } + + uaddr += bio->bi_size; + len -= bio->bi_size; + + /* + * The first bio is added and merged. We could probably + * try to add others using scsi_merge_bio() but for now + * we keep it simple. The first bio should be pretty large + * (either hitting the 1 MB bio pages limit or a queue limit) + * already but for really large IO we may want to try and + * merge these. + */ + if (!rq->bio) { + blk_rq_bio_prep(q, rq, bio); + rq->data_len = bio->bi_size; + } else + /* put list of bios to transfer in next go around */ + bio_list_add(&tcmd->xfer_list, bio); + } + + cmd->offset = 0; + err = scsi_tgt_init_cmd(cmd, GFP_KERNEL); + if (err) + goto unmap_bios; + + return 0; + +unmap_bios: + if (rq->bio) { + bio_unmap_user(rq->bio); + while ((bio = bio_list_pop(&tcmd->xfer_list))) + bio_unmap_user(bio); + } + + return err; +} + +static int scsi_tgt_transfer_data(struct scsi_cmnd *); + +static void scsi_tgt_data_transfer_done(struct scsi_cmnd *cmd) +{ + struct scsi_tgt_cmd *tcmd = cmd->request->end_io_data; + struct bio *bio; + int err; + + /* should we free resources here on error ? */ + if (cmd->result) { +send_uspace_err: + if (scsi_tgt_uspace_send_status(cmd, GFP_ATOMIC) <= 0) + /* the tgt uspace eh will have to pick this up */ + printk(KERN_ERR "Could not send cmd %p status\n", cmd); + return; + } + + dprintk("cmd %p request_bufflen %u bufflen %u\n", + cmd, cmd->request_bufflen, tcmd->bufflen); + + scsi_free_sgtable(cmd->request_buffer, cmd->sglist_len); + bio_list_add(&tcmd->xfer_done_list, cmd->request->bio); + + tcmd->buffer += cmd->request_bufflen; + cmd->offset += cmd->request_bufflen; + + if (!tcmd->xfer_list.head) { + scsi_tgt_transfer_response(cmd); + return; + } + + dprintk("cmd2 %p request_bufflen %u bufflen %u\n", + cmd, cmd->request_bufflen, tcmd->bufflen); + + bio = bio_list_pop(&tcmd->xfer_list); + BUG_ON(!bio); + + blk_rq_bio_prep(cmd->request->q, cmd->request, bio); + cmd->request->data_len = bio->bi_size; + err = scsi_tgt_init_cmd(cmd, GFP_ATOMIC); + if (err) { + cmd->result = DID_ERROR << 16; + goto send_uspace_err; + } + + if (scsi_tgt_transfer_data(cmd)) { + cmd->result = DID_NO_CONNECT << 16; + goto send_uspace_err; + } +} + +static int scsi_tgt_transfer_data(struct scsi_cmnd *cmd) +{ + int err; + struct Scsi_Host *host = scsi_tgt_cmd_to_host(cmd); + + err = host->hostt->transfer_data(cmd, scsi_tgt_data_transfer_done); + switch (err) { + case SCSI_MLQUEUE_HOST_BUSY: + case SCSI_MLQUEUE_DEVICE_BUSY: + return -EAGAIN; + default: + return 0; + } +} + +static int scsi_tgt_copy_sense(struct scsi_cmnd *cmd, unsigned long uaddr, + unsigned len) +{ + char __user *p = (char __user *) uaddr; + + if (copy_from_user(cmd->sense_buffer, p, + min_t(unsigned, SCSI_SENSE_BUFFERSIZE, len))) { + printk(KERN_ERR "Could not copy the sense buffer\n"); + return -EIO; + } + return 0; +} + +static int scsi_tgt_abort_cmd(struct Scsi_Host *host, struct scsi_cmnd *cmd) +{ + int err; + + err = host->hostt->eh_abort_handler(cmd); + if (err) + eprintk("fail to abort %p\n", cmd); + + scsi_tgt_cmd_destroy(cmd); + return err; +} + +static struct request *tgt_cmd_hash_lookup(struct request_queue *q, u32 cid) +{ + struct scsi_tgt_queuedata *qdata = q->queuedata; + struct request *rq = NULL; + struct list_head *head; + struct scsi_tgt_cmd *tcmd; + unsigned long flags; + + head = &qdata->cmd_hash[cmd_hashfn(cid)]; + spin_lock_irqsave(&qdata->cmd_hash_lock, flags); + list_for_each_entry(tcmd, head, hash_list) { + if (tcmd->rq->tag == cid) { + rq = tcmd->rq; + break; + } + } + spin_unlock_irqrestore(&qdata->cmd_hash_lock, flags); + + return rq; +} + +int scsi_tgt_kspace_exec(int host_no, u32 cid, int result, u32 len, + unsigned long uaddr, u8 rw) +{ + struct Scsi_Host *shost; + struct scsi_cmnd *cmd; + struct request *rq; + struct scsi_tgt_cmd *tcmd; + int err = 0; + + dprintk("%d %u %d %u %lx %u\n", host_no, cid, result, + len, uaddr, rw); + + /* TODO: replace with a O(1) alg */ + shost = scsi_host_lookup(host_no); + if (IS_ERR(shost)) { + printk(KERN_ERR "Could not find host no %d\n", host_no); + return -EINVAL; + } + + rq = tgt_cmd_hash_lookup(shost->uspace_req_q, cid); + if (!rq) { + printk(KERN_ERR "Could not find cid %u\n", cid); + err = -EINVAL; + goto done; + } + cmd = rq->special; + + dprintk("cmd %p result %d len %d bufflen %u %lu %x\n", cmd, + result, len, cmd->request_bufflen, rq_data_dir(rq), cmd->cmnd[0]); + + if (result == TASK_ABORTED) { + scsi_tgt_abort_cmd(shost, cmd); + goto done; + } + /* + * store the userspace values here, the working values are + * in the request_* values + */ + tcmd = cmd->request->end_io_data; + tcmd->buffer = (void *)uaddr; + tcmd->bufflen = len; + cmd->result = result; + + if (!tcmd->bufflen) { + err = __scsi_tgt_transfer_response(cmd); + goto done; + } + + /* + * TODO: Do we need to handle case where request does not + * align with LLD. + */ + err = scsi_map_user_pages(rq->end_io_data, cmd, rw); + if (err) { + eprintk("%p %d\n", cmd, err); + err = -EAGAIN; + goto done; + } + + /* userspace failure */ + if (cmd->result) { + if (status_byte(cmd->result) == CHECK_CONDITION) + scsi_tgt_copy_sense(cmd, uaddr, len); + err = __scsi_tgt_transfer_response(cmd); + goto done; + } + /* ask the target LLD to transfer the data to the buffer */ + err = scsi_tgt_transfer_data(cmd); + +done: + scsi_host_put(shost); + return err; +} + +int scsi_tgt_tsk_mgmt_request(struct Scsi_Host *shost, int function, u64 tag, + struct scsi_lun *scsilun, void *data) +{ + int err; + + /* TODO: need to retry if this fails. */ + err = scsi_tgt_uspace_send_tsk_mgmt(shost->host_no, function, + tag, scsilun, data); + if (err < 0) + eprintk("The task management request lost!\n"); + return err; +} +EXPORT_SYMBOL_GPL(scsi_tgt_tsk_mgmt_request); + +int scsi_tgt_kspace_tsk_mgmt(int host_no, u64 mid, int result) +{ + struct Scsi_Host *shost; + int err; + + dprintk("%d %d %llx\n", host_no, result, (unsigned long long) mid); + + shost = scsi_host_lookup(host_no); + if (IS_ERR(shost)) { + printk(KERN_ERR "Could not find host no %d\n", host_no); + return -EINVAL; + } + err = shost->hostt->tsk_mgmt_response(mid, result); + scsi_host_put(shost); + + return err; +} + +static int __init scsi_tgt_init(void) +{ + int err; + + scsi_tgt_cmd_cache = kmem_cache_create("scsi_tgt_cmd", + sizeof(struct scsi_tgt_cmd), + 0, 0, NULL, NULL); + if (!scsi_tgt_cmd_cache) + return -ENOMEM; + + scsi_tgtd = create_workqueue("scsi_tgtd"); + if (!scsi_tgtd) { + err = -ENOMEM; + goto free_kmemcache; + } + + err = scsi_tgt_if_init(); + if (err) + goto destroy_wq; + + return 0; + +destroy_wq: + destroy_workqueue(scsi_tgtd); +free_kmemcache: + kmem_cache_destroy(scsi_tgt_cmd_cache); + return err; +} + +static void __exit scsi_tgt_exit(void) +{ + destroy_workqueue(scsi_tgtd); + scsi_tgt_if_exit(); + kmem_cache_destroy(scsi_tgt_cmd_cache); +} + +module_init(scsi_tgt_init); +module_exit(scsi_tgt_exit); + +MODULE_DESCRIPTION("SCSI target core"); +MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/scsi_tgt_priv.h b/drivers/scsi/scsi_tgt_priv.h new file mode 100644 --- /dev/null +++ b/drivers/scsi/scsi_tgt_priv.h @@ -0,0 +1,24 @@ +struct scsi_cmnd; +struct scsi_lun; +struct Scsi_Host; +struct task_struct; + +/* tmp - will replace with SCSI logging stuff */ +#define eprintk(fmt, args...) \ +do { \ + printk("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ +} while (0) + +#define dprintk eprintk + +extern void scsi_tgt_if_exit(void); +extern int scsi_tgt_if_init(void); + +extern int scsi_tgt_uspace_send(struct scsi_cmnd *cmd, struct scsi_lun *lun, + u64 tag, gfp_t flags); +extern int scsi_tgt_uspace_send_status(struct scsi_cmnd *cmd, gfp_t flags); +extern int scsi_tgt_kspace_exec(int host_no, u32 cid, int result, u32 len, + unsigned long uaddr, u8 rw); +extern int scsi_tgt_uspace_send_tsk_mgmt(int host_no, int function, u64 tag, + struct scsi_lun *scsilun, void *data); +extern int scsi_tgt_kspace_tsk_mgmt(int host_no, u64 mid, int result); diff --git a/fs/bio.c b/fs/bio.c --- a/fs/bio.c +++ b/fs/bio.c @@ -620,10 +620,9 @@ static struct bio *__bio_map_user_iov(re nr_pages += end - start; /* - * transfer and buffer must be aligned to at least hardsector - * size for now, in the future we can relax this restriction + * buffer must be aligned to at least hardsector size for now */ - if ((uaddr & queue_dma_alignment(q)) || (len & queue_dma_alignment(q))) + if (uaddr & queue_dma_alignment(q)) return ERR_PTR(-EINVAL); } @@ -750,7 +749,6 @@ struct bio *bio_map_user_iov(request_que int write_to_vm) { struct bio *bio; - int len = 0, i; bio = __bio_map_user_iov(q, bdev, iov, iov_count, write_to_vm); @@ -765,18 +763,7 @@ struct bio *bio_map_user_iov(request_que */ bio_get(bio); - for (i = 0; i < iov_count; i++) - len += iov[i].iov_len; - - if (bio->bi_size == len) - return bio; - - /* - * don't support partial mappings - */ - bio_endio(bio, bio->bi_size, 0); - bio_unmap_user(bio); - return ERR_PTR(-EINVAL); + return bio; } static void __bio_unmap_user(struct bio *bio) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -611,7 +611,8 @@ extern void blk_queue_activity_fn(reques extern int blk_rq_map_user(request_queue_t *, struct request *, void __user *, unsigned int); extern int blk_rq_unmap_user(struct bio *, unsigned int); extern int blk_rq_map_kern(request_queue_t *, struct request *, void *, unsigned int, gfp_t); -extern int blk_rq_map_user_iov(request_queue_t *, struct request *, struct sg_iovec *, int); +extern int blk_rq_map_user_iov(request_queue_t *, struct request *, + struct sg_iovec *, int, unsigned int); extern int blk_execute_rq(request_queue_t *, struct gendisk *, struct request *, int); extern void blk_execute_rq_nowait(request_queue_t *, struct gendisk *, diff --git a/include/linux/netlink.h b/include/linux/netlink.h --- a/include/linux/netlink.h +++ b/include/linux/netlink.h @@ -21,6 +21,7 @@ #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ #define NETLINK_GENERIC 16 +#define NETLINK_TGT 17 /* SCSI target */ #define MAX_LINKS 32 diff --git a/include/scsi/libsrp.h b/include/scsi/libsrp.h new file mode 100644 --- /dev/null +++ b/include/scsi/libsrp.h @@ -0,0 +1,75 @@ +#ifndef __LIBSRP_H__ +#define __LIBSRP_H__ + +#include +#include +#include +#include + +enum iue_flags { + V_DIOVER, + V_WRITE, + V_LINKED, + V_FLYING, +}; + +struct srp_buf { + dma_addr_t dma; + void *buf; +}; + +struct srp_queue { + void *pool; + void *items; + struct kfifo *queue; + spinlock_t lock; +}; + +struct srp_target { + struct Scsi_Host *shost; + struct device *dev; + + spinlock_t lock; + struct list_head cmd_queue; + + size_t srp_iu_size; + struct srp_queue iu_queue; + size_t rx_ring_size; + struct srp_buf **rx_ring; + + /* IB needs tx_ring too */ + + void *ldata; +}; + +struct iu_entry { + struct srp_target *target; + struct scsi_cmnd *scmd; + + struct list_head ilist; + dma_addr_t remote_token; + unsigned long flags; + + struct srp_buf *sbuf; +}; + +typedef int (rdma_io_t) (struct iu_entry *, struct scatterlist *, int, + struct srp_direct_buf *, int, + enum dma_data_direction, unsigned int); + +static inline struct srp_target *host_to_target(struct Scsi_Host *host) +{ + return (struct srp_target *) host->hostdata; +} + +extern int srp_target_alloc(struct srp_target *, struct device *, size_t, size_t); +extern void srp_target_free(struct srp_target *); + +extern struct iu_entry *srp_iu_get(struct srp_target *); +extern void srp_iu_put(struct iu_entry *); + +extern int srp_cmd_perform(struct iu_entry *iue, struct srp_cmd *cmd); +extern int srp_transfer_data(struct scsi_cmnd *scmd, struct srp_cmd *cmd, + rdma_io_t rdma_io); + +#endif diff --git a/include/scsi/scsi_cmnd.h b/include/scsi/scsi_cmnd.h --- a/include/scsi/scsi_cmnd.h +++ b/include/scsi/scsi_cmnd.h @@ -8,6 +8,7 @@ struct request; struct scatterlist; +struct Scsi_Host; struct scsi_device; struct scsi_request; @@ -84,6 +85,8 @@ struct scsi_cmnd { unsigned short sglist_len; /* size of malloc'd scatter-gather list */ unsigned bufflen; /* Size of data buffer */ void *buffer; /* Data buffer */ + /* offset in cmd we are at (for multi-transfer tgt cmds) */ + unsigned offset; unsigned underflow; /* Return error if less than this amount is transferred */ @@ -147,9 +150,14 @@ struct scsi_cmnd { #define SCSI_STATE_MLQUEUE 0x100b +extern struct scsi_cmnd *scsi_host_get_command(struct Scsi_Host *, + enum dma_data_direction, gfp_t); extern struct scsi_cmnd *scsi_get_command(struct scsi_device *, gfp_t); +extern void scsi_host_put_command(struct Scsi_Host *, struct scsi_cmnd *); extern void scsi_put_command(struct scsi_cmnd *); extern void scsi_io_completion(struct scsi_cmnd *, unsigned int, unsigned int); extern void scsi_finish_command(struct scsi_cmnd *cmd); +extern struct scatterlist *scsi_alloc_sgtable(struct scsi_cmnd *, gfp_t); +extern void scsi_free_sgtable(struct scatterlist *, int); #endif /* _SCSI_SCSI_CMND_H */ diff --git a/include/scsi/scsi_host.h b/include/scsi/scsi_host.h --- a/include/scsi/scsi_host.h +++ b/include/scsi/scsi_host.h @@ -7,6 +7,7 @@ #include #include +struct request_queue; struct block_device; struct completion; struct module; @@ -123,6 +124,39 @@ struct scsi_host_template { void (*done)(struct scsi_cmnd *)); /* + * The transfer functions are used to queue a scsi command to + * the LLD. When the driver is finished processing the command + * the done callback is invoked. + * + * return values: see queuecommand + * + * If the LLD accepts the cmd, it should set the result to an + * appropriate value when completed before calling the done function. + * + * STATUS: REQUIRED FOR TARGET DRIVERS + */ + /* TODO: rename */ + int (* transfer_response)(struct scsi_cmnd *, + void (*done)(struct scsi_cmnd *)); + /* + * This is called to inform the LLD to transfer cmd->request_bufflen + * bytes of the cmd at cmd->offset in the cmd. The cmd->use_sg + * speciefies the number of scatterlist entried in the command + * and cmd->request_buffer contains the scatterlist. + * + * If the command cannot be processed in one transfer_data call + * becuase a scatterlist within the LLD's limits cannot be + * created then transfer_data will be called multiple times. + * It is initially called from process context, and later + * calls are from the interrup context. + */ + int (* transfer_data)(struct scsi_cmnd *, + void (*done)(struct scsi_cmnd *)); + + /* Used as callback for the completion of task management request. */ + int (* tsk_mgmt_response)(u64 mid, int result); + + /* * This is an error handling strategy routine. You don't need to * define one of these if you don't want to - there is a default * routine that is present that should work in most cases. For those @@ -572,6 +606,12 @@ struct Scsi_Host { */ unsigned int max_host_blocked; + /* + * q used for scsi_tgt msgs, async events or any other requests that + * need to be processed in userspace + */ + struct request_queue *uspace_req_q; + /* legacy crap */ unsigned long base; unsigned long io_port; @@ -674,6 +714,9 @@ extern void scsi_unblock_requests(struct extern void scsi_block_requests(struct Scsi_Host *); struct class_container; + +extern struct request_queue *__scsi_alloc_queue(struct Scsi_Host *shost, + void (*) (struct request_queue *)); /* * These two functions are used to allocate and free a pseudo device * which will connect to the host adapter itself rather than any diff --git a/include/scsi/scsi_tgt.h b/include/scsi/scsi_tgt.h new file mode 100644 --- /dev/null +++ b/include/scsi/scsi_tgt.h @@ -0,0 +1,13 @@ +/* + * SCSI target definitions + */ + +struct Scsi_Host; +struct scsi_cmnd; +struct scsi_lun; + +extern struct Scsi_Host *scsi_tgt_cmd_to_host(struct scsi_cmnd *); +extern int scsi_tgt_alloc_queue(struct Scsi_Host *); +extern int scsi_tgt_queue_command(struct scsi_cmnd *, struct scsi_lun *, u64); +extern int scsi_tgt_tsk_mgmt_request(struct Scsi_Host *, int, u64, struct scsi_lun *, + void *); diff --git a/include/scsi/scsi_tgt_if.h b/include/scsi/scsi_tgt_if.h new file mode 100644 --- /dev/null +++ b/include/scsi/scsi_tgt_if.h @@ -0,0 +1,89 @@ +/* + * SCSI target kernel/user interface + * + * Copyright (C) 2005 FUJITA Tomonori + * Copyright (C) 2005 Mike Christie + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that 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., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef __SCSI_TARGET_IF_H +#define __SCSI_TARGET_IF_H + +enum tgt_event_type { + /* user -> kernel */ + TGT_UEVENT_REQ, + TGT_UEVENT_CMD_RSP, + TGT_UEVENT_TSK_MGMT_RSP, + + /* kernel -> user */ + TGT_KEVENT_RSP, + TGT_KEVENT_CMD_REQ, + TGT_KEVENT_CMD_DONE, + TGT_KEVENT_TSK_MGMT_REQ, +}; + +struct tgt_event { + /* user-> kernel */ + union { + struct { + int type; + int host_no; + } event_req; + struct { + int host_no; + uint32_t cid; + uint32_t len; + int result; + uint64_t uaddr; + uint8_t rw; + } cmd_rsp; + struct { + int host_no; + uint64_t mid; + int result; + } tsk_mgmt_rsp; + } u; + + /* kernel -> user */ + union { + struct { + int err; + } event_rsp; + struct { + int host_no; + uint32_t cid; + uint32_t data_len; + uint8_t scb[16]; + uint8_t lun[8]; + int attribute; + uint64_t tag; + } cmd_req; + struct { + int host_no; + uint32_t cid; + int result; + } cmd_done; + struct { + int host_no; + int function; + uint64_t tag; + uint8_t lun[8]; + uint64_t mid; + } tsk_mgmt_req; + } k; + +} __attribute__ ((aligned (sizeof(uint64_t)))); +#endif