# HG changeset patch # User Jun Kamada # Date 1183108159 -32400 # Node ID 0f169b36be81293df09f1c66b6284dc00270854f # Parent 06db0e13731c650c5b2ddf6d8048f9c89d89a012 add scsifront driver Signed-off-by: Tomonari Horikoshi Signed-off-by: Tsunehisa Doi Signed-off-by: Jun Kamada Signed-off-by: Akira Hayakawa diff -r 06db0e13731c -r 0f169b36be81 drivers/xen/scsifront/Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/scsifront/Makefile Fri Jun 29 18:09:19 2007 +0900 @@ -0,0 +1,4 @@ +obj-$(CONFIG_XEN_SCSI_FRONTEND) := xenscsi.o + +xenscsi-objs := scsifront.o + diff -r 06db0e13731c -r 0f169b36be81 drivers/xen/scsifront/scsifront.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/drivers/xen/scsifront/scsifront.c Fri Jun 29 18:09:19 2007 +0900 @@ -0,0 +1,524 @@ +/* + * Xen SCSI frontend driver + * + * Copyright (c) 2007, FUJITSU Limited + * + * Based on the scsifront driver code by FUJITA Tomonori + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation; or, when distributed + * separately from the Linux kernel or incorporated into other + * software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this source file (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_XEN_PLATFORM_COMPAT_H +#include +#endif + +#define GRANT_INVALID_REF 0 + + +#define DPRINTK(_f, _a...) \ + pr_debug("(file=%s, line=%d) " _f, \ + __FILE__ , __LINE__ , ## _a ) + + +struct scsi_shadow { + struct scsiif_request ring_req; + unsigned long req_scsi_cmnd; +}; + +#define SCSI_RING_SIZE __RING_SIZE((struct scsiif_sring *)0, PAGE_SIZE) + +struct scsifront_info { + struct xenbus_device *dev; + struct Scsi_Host *host; + unsigned int evtchn; + unsigned int irq; + unsigned long ring_ref; + struct scsiif_front_ring ring; + struct scsi_shadow shadow[SCSI_RING_SIZE]; + unsigned long shadow_free; +}; + + +static inline int GET_ID_FROM_FREELIST( + struct scsifront_info *info) +{ + unsigned long free = info->shadow_free; + BUG_ON(free > SCSI_RING_SIZE); + info->shadow_free = info->shadow[free].ring_req.rqid; + info->shadow[free].ring_req.rqid = 0x0fffffee; /* debug */ + return free; +} + +static inline void ADD_ID_TO_FREELIST( + struct scsifront_info *info, unsigned long id) +{ + info->shadow[id].ring_req.rqid = info->shadow_free; + info->shadow[id].req_scsi_cmnd = 0; + info->shadow_free = id; +} + +static void scsifront_free(struct scsifront_info *info) +{ + + scsi_remove_host(info->host); + scsi_host_put(info->host); + + flush_scheduled_work(); + + if (info->ring_ref != GRANT_INVALID_REF) { + gnttab_end_foreign_access(info->ring_ref, 0, + (unsigned long)info->ring.sring); + info->ring_ref = GRANT_INVALID_REF; + info->ring.sring = NULL; + } + + if (info->irq) + unbind_from_irqhandler(info->irq, info); + info->irq = 0; +} + +static int map_data_for_request(struct scsifront_info *info, + struct scsi_cmnd *sc, struct scsiif_request *ring_req) +{ + struct scatterlist *sg = sc->request_buffer; + + grant_ref_t gref_head; + int err, i, ref; + int write = (sc->sc_data_direction == DMA_TO_DEVICE); + + if (!sg || sc->sc_data_direction == DMA_NONE) + return 0; + + err = gnttab_alloc_grant_references(SG_TABLESIZE, &gref_head); + if (err) + return -ENOMEM; + + for (i = 0; i < sc->use_sg; i++, sg++) { + ref = gnttab_claim_grant_reference(&gref_head); + BUG_ON(ref == -ENOSPC); /*FIXME*/ + + gnttab_grant_foreign_access_ref(ref, info->dev->otherend_id, + (page_to_phys(sg->page) >> PAGE_SHIFT), write); + ring_req->seg[i].gref = ref; + ring_req->seg[i].offset = sg->offset; + ring_req->seg[i].length = sg->length; + } + + 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 scsiif_request *ring_req; + struct scsiif_front_ring *ring = &info->ring; + int err, notify; + unsigned long id; + + if (info->dev->state != XenbusStateConnected || RING_FULL(ring)) { + printk("busy %u!\n", info->dev->state); + return SCSI_MLQUEUE_HOST_BUSY; + } + sc->scsi_done = done; + sc->result = 0; + + ring_req = RING_GET_REQUEST(&info->ring, ring->req_prod_pvt); + + ring_req->id = sc->device->id; + ring_req->lun = sc->device->lun; + ring_req->channel = sc->device->channel; + ring_req->cmd_len = sc->cmd_len; + + BUG_ON(sc->cmd_len > MAX_COMMAND_SIZE); + + if ( sc->cmd_len ) + memcpy(ring_req->cmnd, sc->cmnd, sc->cmd_len); + else + memset(ring_req->cmnd, 0, MAX_COMMAND_SIZE); + + ring_req->use_sg = sc->use_sg; + ring_req->sc_data_direction = sc->sc_data_direction; + ring_req->request_bufflen = sc->request_bufflen; + ring_req->retries = sc->retries; + ring_req->timeout_per_command = sc->timeout_per_command; + + id = GET_ID_FROM_FREELIST(info); /* use id by response */ + info->shadow[id].req_scsi_cmnd = (unsigned long)sc; + ring_req->rqid = id; + err = map_data_for_request(info, sc, ring_req); + if (err) { + printk("%s error\n",__FUNCTION__); + return SCSI_MLQUEUE_HOST_BUSY; + } + + ring->req_prod_pvt++; + + info->shadow[id].ring_req = *ring_req; + + RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(ring, notify); + + if (notify) + notify_remote_via_irq(info->irq); + + return 0; +} + +static int scsifront_eh_abort_handler(struct scsi_cmnd *sc) +{ + /* not implemented */ + BUG(); + return 0; +} + +static void scsifront_cmd_done(struct scsi_shadow *s) +{ + int i; + + if (!s->ring_req.request_bufflen || + (s->ring_req.sc_data_direction != DMA_TO_DEVICE && + s->ring_req.sc_data_direction != DMA_FROM_DEVICE)) { + return; + } + + if (!s->ring_req.use_sg) + return; + + for (i = 0; i < s->ring_req.use_sg; i++ ) + gnttab_end_foreign_access(s->ring_req.seg[i].gref, 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 scsiif_front_ring *ring = &info->ring; + struct scsiif_response *ring_res; + + struct scsi_cmnd *sc; + int i, rp; + unsigned long id; + + 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); + + id = ring_res->rqid; + sc = (struct scsi_cmnd *)info->shadow[id].req_scsi_cmnd; + scsifront_cmd_done(&info->shadow[id]); + + ADD_ID_TO_FREELIST(info, id); + + sc->result = ring_res->result; + sc->resid = 0; + + BUG_ON(ring_res->sense_len > SCSI_SENSE_BUFFERSIZE); + + if (ring_res->sense_len) + memcpy(sc->sense_buffer, ring_res->sense_buffer, + ring_res->sense_len); + + 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 scsiif_sring *sring; + int err = -ENOMEM; + + info->ring_ref = GRANT_INVALID_REF; + + sring = (struct scsiif_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("0x%x\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 = bind_listening_port_to_irqhandler( + dev->otherend_id, scsifront_intr, + SA_SAMPLE_RANDOM, "scsifront", info); + + if (err <= 0) { + xenbus_dev_fatal(dev, err, "bind_listening_port_to_irqhandler"); + goto fail; + } + info->irq = err; + + return 0; +fail: + /* free resource */ +free_sring: + free_page((unsigned long) sring); + scsifront_free(info); + + 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; + } + +#ifdef XEN303 /*FJVMIO xen3.0.5*/ + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + info->evtchn); +#else + err = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + irq_to_evtchn_port(info->irq)); +#endif + 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 */ + scsifront_free(info); + + 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 = CAN_QUEUE, + .can_queue = CAN_QUEUE, + .this_id = -1, + .sg_tablesize = SG_TABLESIZE, + .use_clustering = DISABLE_CLUSTERING, + .proc_name = "scsifront", +}; + +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); + + /* FIXME */ + host->max_id = 1; + host->max_channel = 0; + + err = scsi_add_host(host, &dev->dev); + if (err) { + printk("fail to add scsi host %d\n", err); + return err; + } + + scsi_scan_host(host); + + return 0; +} + +static int scsifront_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + struct Scsi_Host *host; + struct scsifront_info *info; + int i, 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; + + for (i = 0; i < SCSI_RING_SIZE; i++) + info->shadow[i].ring_req.rqid = i + 1; + info->shadow[SCSI_RING_SIZE - 1].ring_req.rqid = 0x0fffffff; + + err = scsifront_init_ring(info); + if (err) { + scsi_host_put(host); + return err; + } + + return 0; +} + + +static int scsifront_remove(struct xenbus_device *dev) +{ + struct scsifront_info *info = dev->dev.driver_data; + + scsifront_free(info); + + 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[] = { + { "scsihost" }, + { "" } +}; + + +static struct xenbus_driver scsifront_driver = { + .name = "scsihost", + .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) +{ + int err; + + if (!is_running_on_xen()) + return -ENODEV; + + err = xenbus_register_frontend(&scsifront_driver); + + return err; +} + +static void scsifront_exit(void) +{ + xenbus_unregister_driver(&scsifront_driver); +} + +module_init(scsifront_init); +module_exit(scsifront_exit); + +MODULE_DESCRIPTION("Xen SCSI frontend driver"); +MODULE_LICENSE("GPL");