summaryrefslogtreecommitdiffstats
path: root/net/9p/trans_virtio.c
blob: 42eea5fe262895587522ac689b1f06d2cf7ad7a1 (plain)
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
202
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
237
238
239
240
241
242
243
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
/*
 * 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);
}

static void p9_virtio_intr(struct virtqueue *q)
{
	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;
	chan->in_vq = dev->config->find_vq(dev, 0, p9_virtio_intr);
	if (IS_ERR(chan->in_vq)) {
		err = PTR_ERR(chan->in_vq);
		goto free;
	}

	chan->out_vq = dev->config->find_vq(dev, 1, NULL);
	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");
OpenPOWER on IntegriCloud