# HG changeset patch # User fujita.tomonori@lab.ntt.co.jp # Node ID c84cd764b0d7f8bcf911b0e44cc29742b8c760e9 # Parent 4e1a4618df3a66d8100b3f50c96fafe523858440 SCSI frontend driver Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> diff -r 4e1a4618df3a -r c84cd764b0d7 linux-2.6-xen-sparse/drivers/xen/scsifront/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/linux-2.6-xen-sparse/drivers/xen/scsifront/Makefile Wed Aug 02 15:15:36 2006 +0900 @@ -0,0 +1,1 @@ +obj-$(CONFIG_XEN_SCSI_FRONTEND) += scsifront.o diff -r 4e1a4618df3a -r c84cd764b0d7 linux-2.6-xen-sparse/drivers/xen/scsifront/scsifront.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/linux-2.6-xen-sparse/drivers/xen/scsifront/scsifront.c Wed Aug 02 15:15:36 2006 +0900 @@ -0,0 +1,473 @@ +/* + * Xen SCSI frontend driver + * + * Copyright (C) 2006 FUJITA Tomonori <tomof@acm.org> + * + * 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 <linux/version.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/uio.h> +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/srp.h> +#include <xen/evtchn.h> +#include <xen/xenbus.h> +#include <xen/interface/xen.h> +#include <xen/interface/io/scsi.h> +#include <xen/interface/io/ring.h> +#include <xen/interface/grant_table.h> +#include <xen/gnttab.h> +#include <asm/hypervisor.h> + +#define eprintk(fmt, args...) \ +do { \ + printk("%s(%d) " fmt, __FUNCTION__, __LINE__, ##args); \ +} while (0) + +#define dprintk eprintk + +static unsigned int debug = 0; +module_param(debug, int, 0644); + +struct scsifront_info { + struct xenbus_device *dev; + struct Scsi_Host *host; + unsigned int evtchn; + unsigned int irq; + unsigned long ring_ref; + struct scsi_front_ring ring; +}; + +static int map_data_for_srp_cmd(struct scsifront_info *info, + struct scsi_cmnd *sc, struct srp_cmd *cmd) +{ + struct scatterlist *sg = sc->request_buffer; + struct srp_direct_buf *buf; + grant_ref_t gref_head; + int err, i, ref; + u8 fmt; + + if (!sg || sc->sc_data_direction == DMA_NONE) + return 0; + + err = gnttab_alloc_grant_references(SRP_MAX_INDIRECT, &gref_head); + if (err) + return -ENOMEM; + + if (sc->use_sg == 1) { + buf = (void *) cmd->add_data; + fmt = SRP_DATA_DESC_DIRECT; + + ref = gnttab_claim_grant_reference(&gref_head); + gnttab_grant_foreign_access_ref(ref, info->dev->otherend_id, + page_to_phys(sg->page) >> PAGE_SHIFT, 0); + + buf->va = sg->offset; + buf->key = ref; + buf->len = sg->length; + } else { + struct srp_indirect_buf *ind = (void *) cmd->add_data; + int total = 0; + fmt = SRP_DATA_DESC_INDIRECT; + + if (sc->sc_data_direction == DMA_TO_DEVICE) + cmd->data_out_desc_cnt = sc->use_sg; + else + cmd->data_in_desc_cnt = sc->use_sg; + + ind->table_desc.va = (u64) (unsigned long)ind->desc_list; + ind->table_desc.key = 0; + ind->table_desc.len = sizeof(*buf) * sc->use_sg; + + buf = (struct srp_direct_buf *) ind->desc_list; + for (i = 0; i < sc->use_sg; i++, sg++) { + ref = gnttab_claim_grant_reference(&gref_head); + gnttab_grant_foreign_access_ref(ref, info->dev->otherend_id, + page_to_phys(sg->page) >> PAGE_SHIFT, + 0); + buf[i].va = sg->offset; + buf[i].key = ref; + buf[i].len = sg->length; + total += sg->length; + } + + ind->len = total; + } + + if (sc->sc_data_direction == DMA_TO_DEVICE) + cmd->buf_fmt = fmt << 4; + else + cmd->buf_fmt = fmt; + + gnttab_free_grant_references(gref_head); + + return 0; +} + +static int scsifront_queuecommand(struct scsi_cmnd *sc, + void (*done)(struct scsi_cmnd *)) +{ + struct Scsi_Host *host = sc->device->host; + struct scsifront_info *info = (struct scsifront_info *) host->hostdata; + struct scsi_request *ring_req; + struct scsi_front_ring *ring = &info->ring; + struct srp_cmd *cmd; + int err, notify; + + if (info->dev->state != XenbusStateConnected || RING_FULL(ring)) { + eprintk("busy %u!\n", info->dev->state); + return SCSI_MLQUEUE_HOST_BUSY; + } + sc->scsi_done = done; + sc->result = 0; + + ring_req = RING_GET_REQUEST(ring, ring->req_prod_pvt); + cmd = (struct srp_cmd *) ring_req->buf; + + memset(cmd, 0, SRP_MAX_IU_LEN); + cmd->opcode = SRP_CMD; + int_to_scsilun(sc->device->lun, (struct scsi_lun *) &cmd->lun); + cmd->tag = (long) sc; + memcpy(cmd->cdb, sc->cmnd, sc->cmd_len); + + err = map_data_for_srp_cmd(info, sc, cmd); + if (err) + return SCSI_MLQUEUE_HOST_BUSY; + + ring->req_prod_pvt++; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(ring, notify); + notify_remote_via_irq(info->irq); + + return 0; +} + +static int scsifront_eh_abort_handler(struct scsi_cmnd *sc) +{ + BUG(); + return 0; +} + +static void scsifront_cmd_done(struct scsifront_info *info, int idx) +{ + struct scsi_front_ring *ring = &info->ring; + struct scsi_request *ring_req; + struct srp_cmd *cmd; + struct scsi_cmnd *sc; + struct srp_direct_buf *buf; + int i; + + ring_req = RING_GET_REQUEST(ring, idx); + cmd = (struct srp_cmd *) ring_req->buf; + sc = (struct scsi_cmnd *) (long) cmd->tag; + + if (!sc->request_buffer || (sc->sc_data_direction != DMA_TO_DEVICE && + sc->sc_data_direction != DMA_FROM_DEVICE)) + return; + + if (sc->use_sg == 1) { + buf = (void *) cmd->add_data; + gnttab_end_foreign_access(buf->key, 0, 0UL); + } else { + struct srp_indirect_buf *ind = (void *) cmd->add_data; + buf = (struct srp_direct_buf *) ind->desc_list; + + for (i = 0; i < sc->use_sg; i++, buf++) + gnttab_end_foreign_access(buf->key, 0, 0UL); + } +} + +static irqreturn_t scsifront_intr(int irq, void *dev_id, + struct pt_regs *ptregs) +{ + struct scsifront_info *info = (struct scsifront_info *) dev_id; + struct scsi_front_ring *ring = &info->ring; + struct scsi_response *ring_res; + struct scsi_cmnd *sc; + struct srp_rsp *rsp; + int i, rp; + + if (info->dev->state != XenbusStateConnected) + return IRQ_HANDLED; + +again: + rp = info->ring.sring->rsp_prod; + rmb(); + + for (i = info->ring.rsp_cons; i != rp; i++) { + ring_res = RING_GET_RESPONSE(ring, i); + + rsp = (struct srp_rsp *) ring_res->buf; + sc = ((void *) (unsigned long) rsp->tag); + sc->result = rsp->status; + + scsifront_cmd_done(info, i); + + if (rsp->flags & SRP_RSP_FLAG_SNSVALID) { + memcpy(sc->sense_buffer, rsp->data + + be32_to_cpu(rsp->resp_data_len), + min_t(int, be32_to_cpu(rsp->sense_data_len), + SCSI_SENSE_BUFFERSIZE)); + } + + if (rsp->flags & (SRP_RSP_FLAG_DOOVER | SRP_RSP_FLAG_DOUNDER)) + sc->resid = be32_to_cpu(rsp->data_out_res_cnt); + else if (rsp->flags & (SRP_RSP_FLAG_DIOVER | SRP_RSP_FLAG_DIUNDER)) + sc->resid = be32_to_cpu(rsp->data_in_res_cnt); + + sc->scsi_done(sc); + } + + info->ring.rsp_cons = i; + if (i != info->ring.req_prod_pvt) { + int more_to_do; + RING_FINAL_CHECK_FOR_RESPONSES(ring, more_to_do); + if (more_to_do) + goto again; + } else + ring->sring->rsp_event = i + 1; + + return IRQ_HANDLED; +} + +static int scsifront_alloc_ring(struct scsifront_info *info) +{ + struct xenbus_device *dev = info->dev; + struct scsi_sring *sring; + int err = -ENOMEM; + + sring = (struct scsi_sring *) __get_free_page(GFP_KERNEL); + if (!sring) { + xenbus_dev_fatal(dev, err, "fail to allocate shared ring"); + return err; + } + + SHARED_RING_INIT(sring); + FRONT_RING_INIT(&info->ring, sring, PAGE_SIZE); + dprintk("%u\n", RING_SIZE(&info->ring)); + + err = xenbus_grant_ring(dev, virt_to_mfn(info->ring.sring)); + if (err < 0) { + xenbus_dev_fatal(dev, err, "fail to grant shared ring"); + goto free_sring; + } + info->ring_ref = err; + + err = xenbus_alloc_evtchn(dev, &info->evtchn); + if (err) { + xenbus_dev_fatal(dev, err, "fail to allocate evtchn"); + return err; + } + + err = bind_evtchn_to_irqhandler(info->evtchn, scsifront_intr, + SA_SAMPLE_RANDOM, "scsifront", info); + if (err <= 0) { + xenbus_dev_fatal(dev, err, "bind_evtchn_to_irqhandler failed"); + goto fail; + } + info->irq = err; + + return 0; +fail: + /* free resource */ +free_sring: + free_page((unsigned long) sring); + + return err; +} + +static int scsifront_init_ring(struct scsifront_info *info) +{ + struct xenbus_device *dev = info->dev; + struct xenbus_transaction xbt; + int err; + + dprintk(""); + + err = scsifront_alloc_ring(info); + if (err) + return err; + dprintk("%lu %u\n", info->ring_ref, info->evtchn); + +again: + err = xenbus_transaction_start(&xbt); + if (err) { + xenbus_dev_fatal(dev, err, "starting transaction"); + } + + err = xenbus_printf(xbt, dev->nodename, "ring-ref", "%lu", + info->ring_ref); + if (err) { + xenbus_dev_fatal(dev, err, "%s", "writing ring-ref"); + goto fail; + } + + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + info->evtchn); + if (err) { + xenbus_dev_fatal(dev, err, "%s", "writing event-channel"); + goto fail; + } + + err = xenbus_transaction_end(xbt, 0); + if (err) { + if (err == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, err, "completing transaction"); + } else + xenbus_switch_state(dev, XenbusStateInitialised); + + return 0; +fail: + xenbus_transaction_end(xbt, 1); + /* free resource */ + return err; +} + +static struct scsi_host_template scsifront_sht = { + .module = THIS_MODULE, + .name = "Xen SCSI frontend driver", + .queuecommand = scsifront_queuecommand, + .eh_abort_handler = scsifront_eh_abort_handler, + .cmd_per_lun = SRP_CAN_QUEUE, + .can_queue = SRP_CAN_QUEUE, + .this_id = -1, + .sg_tablesize = SRP_MAX_INDIRECT, + .use_clustering = DISABLE_CLUSTERING, + .proc_name = "scsifront", +}; + +static int scsifront_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + struct Scsi_Host *host; + struct scsifront_info *info; + int err = -ENOMEM; + + host = scsi_host_alloc(&scsifront_sht, sizeof(*info)); + if (!host) { + xenbus_dev_fatal(dev, err, "fail to allocate scsi host"); + return err; + } + info = (struct scsifront_info *) host->hostdata; + dev->dev.driver_data = info; + info->dev = dev; + info->host = host; + + err = scsifront_init_ring(info); + if (err) + scsi_host_put(host); + + return err; +} + +static int scsifront_connect(struct scsifront_info *info) +{ + struct xenbus_device *dev = info->dev; + struct Scsi_Host *host = info->host; + int err = -ENOMEM; + + dprintk("%u\n", dev->state); + if (dev->state == XenbusStateConnected) + return 0; + + xenbus_switch_state(dev, XenbusStateConnected); + + host->max_id = 1; + host->max_channel = 0; + + err = scsi_add_host(host, &dev->dev); + if (err) { + eprintk("fail to add scsi host %d\n", err); + return err; + } + scsi_scan_host(host); + + return 0; +} + +static int scsifront_remove(struct xenbus_device *dev) +{ + struct scsifront_info *info = dev->dev.driver_data; + + scsi_remove_host(info->host); + scsi_host_put(info->host); + + return 0; +} + +static void scsifront_backend_changed(struct xenbus_device *dev, + XenbusState backend_state) +{ + struct scsifront_info *info = dev->dev.driver_data; + + dprintk("%p %u %u\n", dev, dev->state, backend_state); + + switch (backend_state) { + case XenbusStateUnknown: + case XenbusStateInitialising: + case XenbusStateInitWait: + case XenbusStateInitialised: + case XenbusStateClosed: + break; + + case XenbusStateConnected: + scsifront_connect(info); + break; + + case XenbusStateClosing: + break; + } +} + +static struct xenbus_device_id scsifront_ids[] = { + { "scsi" }, + { "" } +}; + +static struct xenbus_driver scsifront = { + .name = "scsi", + .owner = THIS_MODULE, + .ids = scsifront_ids, + .probe = scsifront_probe, + .remove = scsifront_remove, +/* .resume = scsifront_resume, */ + .otherend_changed = scsifront_backend_changed, +}; + +static int __init scsifront_init(void) +{ + if (!is_running_on_xen()) + return -ENODEV; + + return xenbus_register_frontend(&scsifront); +} +static void scsifront_exit(void) +{ + return xenbus_unregister_driver(&scsifront); +} + +module_init(scsifront_init); +module_exit(scsifront_exit); + +MODULE_AUTHOR("FUJITA Tomonori"); +MODULE_DESCRIPTION("Xen SCSI frontend driver"); +MODULE_LICENSE("GPL"); _______________________________________________ Xen-devel mailing list Xen-devel@lists.xensource.com http://lists.xensource.com/xen-devel