# HG changeset patch
# User Jun Kamada <kama@jp.fujitsu.com>
# Date 1183108159 -32400
# Node ID 0f169b36be81293df09f1c66b6284dc00270854f
# Parent  06db0e13731c650c5b2ddf6d8048f9c89d89a012
add scsifront driver

Signed-off-by: Tomonari Horikoshi <t.horikoshi@jp.fujitsu.com>
Signed-off-by: Tsunehisa Doi <Doi.Tsunehisa@jp.fujitsu.com>
Signed-off-by: Jun Kamada <kama@jp.fujitsu.com>
Signed-off-by: Akira Hayakawa <hayakawa.akira@jp.fujitsu.com>

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 <linux/version.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/scatterlist.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 <xen/evtchn.h>
+#include <xen/xenbus.h>
+#include <xen/interface/xen.h>
+#include <xen/interface/io/scsiif.h>
+#include <xen/interface/io/ring.h>
+#include <xen/gnttab.h>
+#include <asm/hypervisor.h>
+
+#ifdef HAVE_XEN_PLATFORM_COMPAT_H
+#include <xen/platform-compat.h>
+#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");
