trans_virtio.c 9.1 KB
Newer Older
E
Eric Van Hensbergen 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
/*
 * The Guest 9p transport driver
 *
 * This is a trivial pipe-based transport driver based on the lguest console
 * code: we use lguest's DMA mechanism to send bytes out, and register a
 * DMA buffer to receive bytes in.  It is assumed to be present and available
 * from the very beginning of boot.
 *
 * This may be have been done by just instaniating another HVC console,
 * but HVC's blocksize of 16 bytes is annoying and painful to performance.
 *
 * A more efficient transport could be built based on the virtio block driver
 * but it requires some changes in the 9p transport model (which are in
 * progress)
 *
 */
/*
 *  Copyright (C) 2007 Eric Van Hensbergen, IBM Corporation
 *
 *  Based on virtio console driver
 *  Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation
 *
 *  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.
 *
 *  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:
 *  Free Software Foundation
 *  51 Franklin Street, Fifth Floor
 *  Boston, MA  02111-1301  USA
 *
 */

#include <linux/in.h>
#include <linux/module.h>
#include <linux/net.h>
#include <linux/ipv6.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/un.h>
#include <linux/uaccess.h>
#include <linux/inet.h>
#include <linux/idr.h>
#include <linux/file.h>
#include <net/9p/9p.h>
#include <linux/parser.h>
#include <net/9p/transport.h>
#include <linux/scatterlist.h>
#include <linux/virtio.h>
#include <linux/virtio_9p.h>

/* a single mutex to manage channel initialization and attachment */
static DECLARE_MUTEX(virtio_9p_lock);
/* global which tracks highest initialized channel */
static int chan_index;

/* We keep all per-channel information in a structure.
 * This structure is allocated within the devices dev->mem space.
 * A pointer to the structure will get put in the transport private.
 */
static struct virtio_chan {
	bool initialized;		/* channel is initialized */
	bool inuse;			/* channel is in use */

	struct virtqueue *in_vq, *out_vq;
	struct virtio_device *vdev;

	/* This is our input buffer, and how much data is left in it. */
	unsigned int in_len;
	char *in, *inbuf;

	wait_queue_head_t wq;		/* waitq for buffer */
} channels[MAX_9P_CHAN];

/* How many bytes left in this page. */
static unsigned int rest_of_page(void *data)
{
	return PAGE_SIZE - ((unsigned long)data % PAGE_SIZE);
}

static int p9_virtio_write(struct p9_trans *trans, void *buf, int count)
{
	struct virtio_chan *chan = (struct virtio_chan *) trans->priv;
	struct virtqueue *out_vq = chan->out_vq;
	struct scatterlist sg[1];
	unsigned int len;

	P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio write (%d)\n", count);

	/* keep it simple - make sure we don't overflow a page */
	if (rest_of_page(buf) < count)
		count = rest_of_page(buf);

	sg_init_one(sg, buf, count);

	/* add_buf wants a token to identify this buffer: we hand it any
	 * non-NULL pointer, since there's only ever one buffer. */
	if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) == 0) {
		/* Tell Host to go! */
		out_vq->vq_ops->kick(out_vq);
		/* Chill out until it's done with the buffer. */
		while (!out_vq->vq_ops->get_buf(out_vq, &len))
			cpu_relax();
	}

	P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio wrote (%d)\n", count);

	/* We're expected to return the amount of data we wrote: all of it. */
	return count;
}

/* Create a scatter-gather list representing our input buffer and put it in the
 * queue. */
static void add_inbuf(struct virtio_chan *chan)
{
	struct scatterlist sg[1];

	sg_init_one(sg, chan->inbuf, PAGE_SIZE);

	/* We should always be able to add one buffer to an empty queue. */
	if (chan->in_vq->vq_ops->add_buf(chan->in_vq, sg, 0, 1, chan->inbuf))
		BUG();
	chan->in_vq->vq_ops->kick(chan->in_vq);
}

static int p9_virtio_read(struct p9_trans *trans, void *buf, int count)
{
	struct virtio_chan *chan = (struct virtio_chan *) trans->priv;
	struct virtqueue *in_vq = chan->in_vq;

	P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio read (%d)\n", count);

	/* If we don't have an input queue yet, we can't get input. */
	BUG_ON(!in_vq);

	/* No buffer?  Try to get one. */
	if (!chan->in_len) {
		chan->in = in_vq->vq_ops->get_buf(in_vq, &chan->in_len);
		if (!chan->in)
			return 0;
	}

	/* You want more than we have to give?  Well, try wanting less! */
	if (chan->in_len < count)
		count = chan->in_len;

	/* Copy across to their buffer and increment offset. */
	memcpy(buf, chan->in, count);
	chan->in += count;
	chan->in_len -= count;

	/* Finished?  Re-register buffer so Host will use it again. */
	if (chan->in_len == 0)
		add_inbuf(chan);

	P9_DPRINTK(P9_DEBUG_TRANS, "9p debug: virtio finished read (%d)\n",
									count);

	return count;
}

/* The poll function is used by 9p transports to determine if there
 * is there is activity available on a particular channel.  In our case
 * we use it to wait for a callback from the input routines.
 */
static unsigned int
p9_virtio_poll(struct p9_trans *trans, struct poll_table_struct *pt)
{
	struct virtio_chan *chan = (struct virtio_chan *)trans->priv;
	struct virtqueue *in_vq = chan->in_vq;
	int ret = POLLOUT; /* we can always handle more output */

	poll_wait(NULL, &chan->wq, pt);

	/* No buffer?  Try to get one. */
	if (!chan->in_len)
		chan->in = in_vq->vq_ops->get_buf(in_vq, &chan->in_len);

	if (chan->in_len)
		ret |= POLLIN;

	return ret;
}

static void p9_virtio_close(struct p9_trans *trans)
{
	struct virtio_chan *chan = trans->priv;

	down(&virtio_9p_lock);
	chan->inuse = false;
	up(&virtio_9p_lock);

	kfree(trans);
}

202
static void p9_virtio_intr(struct virtqueue *q)
E
Eric Van Hensbergen 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
{
	struct virtio_chan *chan = q->vdev->priv;

	P9_DPRINTK(P9_DEBUG_TRANS, "9p poll_wakeup: %p\n", &chan->wq);
	wake_up_interruptible(&chan->wq);
}

static int p9_virtio_probe(struct virtio_device *dev)
{
	int err;
	struct virtio_chan *chan;
	int index;

	down(&virtio_9p_lock);
	index = chan_index++;
	chan = &channels[index];
	up(&virtio_9p_lock);

	if (chan_index > MAX_9P_CHAN) {
		printk(KERN_ERR "9p: virtio: Maximum channels exceeded\n");
		BUG();
	}

	chan->vdev = dev;

	/* This is the scratch page we use to receive console input */
	chan->inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
	if (!chan->inbuf) {
		err = -ENOMEM;
		goto fail;
	}

	/* Find the input queue. */
	dev->priv = chan;
237
	chan->in_vq = dev->config->find_vq(dev, 0, p9_virtio_intr);
E
Eric Van Hensbergen 已提交
238 239 240 241 242
	if (IS_ERR(chan->in_vq)) {
		err = PTR_ERR(chan->in_vq);
		goto free;
	}

243
	chan->out_vq = dev->config->find_vq(dev, 1, NULL);
E
Eric Van Hensbergen 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
	if (IS_ERR(chan->out_vq)) {
		err = PTR_ERR(chan->out_vq);
		goto free_in_vq;
	}

	init_waitqueue_head(&chan->wq);

	/* Register the input buffer the first time. */
	add_inbuf(chan);
	chan->inuse = false;
	chan->initialized = true;

	return 0;

free_in_vq:
	dev->config->del_vq(chan->in_vq);
free:
	kfree(chan->inbuf);
fail:
	down(&virtio_9p_lock);
	chan_index--;
	up(&virtio_9p_lock);
	return err;
}

/* This sets up a transport channel for 9p communication.  Right now
 * we only match the first available channel, but eventually we couldlook up
 * alternate channels by matching devname versus a virtio_config entry.
 * We use a simple reference count mechanism to ensure that only a single
 * mount has a channel open at a time. */
static struct p9_trans *p9_virtio_create(const char *devname, char *args)
{
	struct p9_trans *trans;
	int index = 0;
	struct virtio_chan *chan = channels;

	down(&virtio_9p_lock);
	while (index < MAX_9P_CHAN) {
		if (chan->initialized && !chan->inuse) {
			chan->inuse = true;
			break;
		} else {
			index++;
			chan = &channels[index];
		}
	}
	up(&virtio_9p_lock);

	if (index >= MAX_9P_CHAN) {
		printk(KERN_ERR "9p: virtio: couldn't find a free channel\n");
		return NULL;
	}

	trans = kmalloc(sizeof(struct p9_trans), GFP_KERNEL);
	if (!trans) {
		printk(KERN_ERR "9p: couldn't allocate transport\n");
		return ERR_PTR(-ENOMEM);
	}

	trans->write = p9_virtio_write;
	trans->read = p9_virtio_read;
	trans->close = p9_virtio_close;
	trans->poll = p9_virtio_poll;
	trans->priv = chan;

	return trans;
}

#define VIRTIO_ID_9P 9

static struct virtio_device_id id_table[] = {
	{ VIRTIO_ID_9P, VIRTIO_DEV_ANY_ID },
	{ 0 },
};

/* The standard "struct lguest_driver": */
static struct virtio_driver p9_virtio_drv = {
	.driver.name = 	KBUILD_MODNAME,
	.driver.owner = THIS_MODULE,
	.id_table =	id_table,
	.probe = 	p9_virtio_probe,
};

static struct p9_trans_module p9_virtio_trans = {
	.name = "virtio",
	.create = p9_virtio_create,
	.maxsize = PAGE_SIZE,
	.def = 0,
};

/* The standard init function */
static int __init p9_virtio_init(void)
{
	int count;

	for (count = 0; count < MAX_9P_CHAN; count++)
		channels[count].initialized = false;

	v9fs_register_trans(&p9_virtio_trans);
	return register_virtio_driver(&p9_virtio_drv);
}

module_init(p9_virtio_init);

MODULE_DEVICE_TABLE(virtio, id_table);
MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
MODULE_DESCRIPTION("Virtio 9p Transport");
MODULE_LICENSE("GPL");