|
@@ -0,0 +1,492 @@
|
|
1
|
+/* virtio-net.c - etherboot driver for virtio network interface
|
|
2
|
+ *
|
|
3
|
+ * (c) Copyright 2008 Bull S.A.S.
|
|
4
|
+ *
|
|
5
|
+ * Author: Laurent Vivier <Laurent.Vivier@bull.net>
|
|
6
|
+ *
|
|
7
|
+ * some parts from Linux Virtio PCI driver
|
|
8
|
+ *
|
|
9
|
+ * Copyright IBM Corp. 2007
|
|
10
|
+ * Authors: Anthony Liguori <aliguori@us.ibm.com>
|
|
11
|
+ *
|
|
12
|
+ * some parts from Linux Virtio Ring
|
|
13
|
+ *
|
|
14
|
+ * Copyright Rusty Russell IBM Corporation 2007
|
|
15
|
+ *
|
|
16
|
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
|
|
17
|
+ * See the COPYING file in the top-level directory.
|
|
18
|
+ *
|
|
19
|
+ *
|
|
20
|
+ */
|
|
21
|
+
|
|
22
|
+#include "etherboot.h"
|
|
23
|
+#include "nic.h"
|
|
24
|
+#include "virtio-ring.h"
|
|
25
|
+#include "virtio-pci.h"
|
|
26
|
+#include "virtio-net.h"
|
|
27
|
+
|
|
28
|
+#define BUG() do { \
|
|
29
|
+ printf("BUG: failure at %s:%d/%s()!\n", \
|
|
30
|
+ __FILE__, __LINE__, __FUNCTION__); \
|
|
31
|
+ while(1); \
|
|
32
|
+} while (0)
|
|
33
|
+#define BUG_ON(condition) do { if (condition) BUG(); } while (0)
|
|
34
|
+
|
|
35
|
+/* Ethernet header */
|
|
36
|
+
|
|
37
|
+struct eth_hdr {
|
|
38
|
+ unsigned char dst_addr[ETH_ALEN];
|
|
39
|
+ unsigned char src_addr[ETH_ALEN];
|
|
40
|
+ unsigned short type;
|
|
41
|
+};
|
|
42
|
+
|
|
43
|
+struct eth_frame {
|
|
44
|
+ struct eth_hdr hdr;
|
|
45
|
+ unsigned char data[ETH_FRAME_LEN];
|
|
46
|
+};
|
|
47
|
+
|
|
48
|
+typedef unsigned char virtio_queue_t[PAGE_MASK + vring_size(MAX_QUEUE_NUM)];
|
|
49
|
+
|
|
50
|
+/* TX: virtio header and eth buffer */
|
|
51
|
+
|
|
52
|
+static struct virtio_net_hdr tx_virtio_hdr;
|
|
53
|
+static struct eth_frame tx_eth_frame;
|
|
54
|
+
|
|
55
|
+/* RX: virtio headers and buffers */
|
|
56
|
+
|
|
57
|
+#define RX_BUF_NB 6
|
|
58
|
+static struct virtio_net_hdr rx_hdr[RX_BUF_NB];
|
|
59
|
+static unsigned char rx_buffer[RX_BUF_NB][ETH_FRAME_LEN];
|
|
60
|
+
|
|
61
|
+/* virtio queues and vrings */
|
|
62
|
+
|
|
63
|
+enum {
|
|
64
|
+ RX_INDEX = 0,
|
|
65
|
+ TX_INDEX,
|
|
66
|
+ QUEUE_NB
|
|
67
|
+};
|
|
68
|
+
|
|
69
|
+static virtio_queue_t queue[QUEUE_NB];
|
|
70
|
+static struct vring vring[QUEUE_NB];
|
|
71
|
+static u16 free_head[QUEUE_NB];
|
|
72
|
+static u16 last_used_idx[QUEUE_NB];
|
|
73
|
+static u16 vdata[QUEUE_NB][MAX_QUEUE_NUM];
|
|
74
|
+
|
|
75
|
+/*
|
|
76
|
+ * Virtio PCI interface
|
|
77
|
+ *
|
|
78
|
+ */
|
|
79
|
+
|
|
80
|
+static int vp_find_vq(struct nic *nic, int queue_index)
|
|
81
|
+{
|
|
82
|
+ struct vring * vr = &vring[queue_index];
|
|
83
|
+ u16 num;
|
|
84
|
+
|
|
85
|
+ /* select the queue */
|
|
86
|
+
|
|
87
|
+ outw(queue_index, nic->ioaddr + VIRTIO_PCI_QUEUE_SEL);
|
|
88
|
+
|
|
89
|
+ /* check if the queue is available */
|
|
90
|
+
|
|
91
|
+ num = inw(nic->ioaddr + VIRTIO_PCI_QUEUE_NUM);
|
|
92
|
+ if (!num) {
|
|
93
|
+ printf("ERROR: queue size is 0\n");
|
|
94
|
+ return -1;
|
|
95
|
+ }
|
|
96
|
+
|
|
97
|
+ if (num > MAX_QUEUE_NUM) {
|
|
98
|
+ printf("ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM);
|
|
99
|
+ return -1;
|
|
100
|
+ }
|
|
101
|
+
|
|
102
|
+ /* check if the queue is already active */
|
|
103
|
+
|
|
104
|
+ if (inl(nic->ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
|
|
105
|
+ printf("ERROR: queue already active\n");
|
|
106
|
+ return -1;
|
|
107
|
+ }
|
|
108
|
+
|
|
109
|
+ /* initialize the queue */
|
|
110
|
+
|
|
111
|
+ vring_init(vr, num, (unsigned char*)&queue[queue_index]);
|
|
112
|
+
|
|
113
|
+ /* activate the queue
|
|
114
|
+ *
|
|
115
|
+ * NOTE: vr->desc is initialized by vring_init()
|
|
116
|
+ */
|
|
117
|
+
|
|
118
|
+ outl((unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT,
|
|
119
|
+ nic->ioaddr + VIRTIO_PCI_QUEUE_PFN);
|
|
120
|
+
|
|
121
|
+ return num;
|
|
122
|
+}
|
|
123
|
+
|
|
124
|
+/*
|
|
125
|
+ * Virtual ring management
|
|
126
|
+ *
|
|
127
|
+ */
|
|
128
|
+
|
|
129
|
+static void vring_enable_cb(int queue_index)
|
|
130
|
+{
|
|
131
|
+ vring[queue_index].avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
|
|
132
|
+}
|
|
133
|
+
|
|
134
|
+static void vring_disable_cb(int queue_index)
|
|
135
|
+{
|
|
136
|
+ vring[queue_index].avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
|
|
137
|
+}
|
|
138
|
+
|
|
139
|
+/*
|
|
140
|
+ * vring_free
|
|
141
|
+ *
|
|
142
|
+ * put at the begin of the free list the current desc[head]
|
|
143
|
+ */
|
|
144
|
+
|
|
145
|
+static void vring_detach(int queue_index, unsigned int head)
|
|
146
|
+{
|
|
147
|
+ struct vring *vr = &vring[queue_index];
|
|
148
|
+ unsigned int i;
|
|
149
|
+
|
|
150
|
+ /* find end of given descriptor */
|
|
151
|
+
|
|
152
|
+ i = head;
|
|
153
|
+ while (vr->desc[i].flags & VRING_DESC_F_NEXT)
|
|
154
|
+ i = vr->desc[i].next;
|
|
155
|
+
|
|
156
|
+ /* link it with free list and point to it */
|
|
157
|
+
|
|
158
|
+ vr->desc[i].next = free_head[queue_index];
|
|
159
|
+ wmb();
|
|
160
|
+ free_head[queue_index] = head;
|
|
161
|
+}
|
|
162
|
+
|
|
163
|
+/*
|
|
164
|
+ * vring_more_used
|
|
165
|
+ *
|
|
166
|
+ * is there some used buffers ?
|
|
167
|
+ *
|
|
168
|
+ */
|
|
169
|
+
|
|
170
|
+static inline int vring_more_used(int queue_index)
|
|
171
|
+{
|
|
172
|
+ wmb();
|
|
173
|
+ return last_used_idx[queue_index] != vring[queue_index].used->idx;
|
|
174
|
+}
|
|
175
|
+
|
|
176
|
+/*
|
|
177
|
+ * vring_get_buf
|
|
178
|
+ *
|
|
179
|
+ * get a buffer from the used list
|
|
180
|
+ *
|
|
181
|
+ */
|
|
182
|
+
|
|
183
|
+static int vring_get_buf(int queue_index, unsigned int *len)
|
|
184
|
+{
|
|
185
|
+ struct vring *vr = &vring[queue_index];
|
|
186
|
+ struct vring_used_elem *elem;
|
|
187
|
+ u32 id;
|
|
188
|
+ int ret;
|
|
189
|
+
|
|
190
|
+ elem = &vr->used->ring[last_used_idx[queue_index] % vr->num];
|
|
191
|
+ wmb();
|
|
192
|
+ id = elem->id;
|
|
193
|
+ if (len != NULL)
|
|
194
|
+ *len = elem->len;
|
|
195
|
+
|
|
196
|
+ ret = vdata[queue_index][id];
|
|
197
|
+
|
|
198
|
+ vring_detach(queue_index, id);
|
|
199
|
+
|
|
200
|
+ last_used_idx[queue_index]++;
|
|
201
|
+
|
|
202
|
+ return ret;
|
|
203
|
+}
|
|
204
|
+
|
|
205
|
+static void vring_add_buf(int queue_index, int index, int num_added)
|
|
206
|
+{
|
|
207
|
+ struct vring *vr = &vring[queue_index];
|
|
208
|
+ int i, avail, head;
|
|
209
|
+
|
|
210
|
+ BUG_ON(queue_index >= QUEUE_NB);
|
|
211
|
+
|
|
212
|
+ head = free_head[queue_index];
|
|
213
|
+ i = head;
|
|
214
|
+
|
|
215
|
+ if (queue_index == TX_INDEX) {
|
|
216
|
+
|
|
217
|
+ BUG_ON(index != 0);
|
|
218
|
+
|
|
219
|
+ /* add header into vring */
|
|
220
|
+
|
|
221
|
+ vr->desc[i].flags = VRING_DESC_F_NEXT;
|
|
222
|
+ vr->desc[i].addr = (u64)virt_to_phys(&tx_virtio_hdr);
|
|
223
|
+ vr->desc[i].len = sizeof(struct virtio_net_hdr);
|
|
224
|
+ i = vr->desc[i].next;
|
|
225
|
+
|
|
226
|
+ /* add frame buffer into vring */
|
|
227
|
+
|
|
228
|
+ vr->desc[i].flags = 0;
|
|
229
|
+ vr->desc[i].addr = (u64)virt_to_phys(&tx_eth_frame);
|
|
230
|
+ vr->desc[i].len = ETH_FRAME_LEN;
|
|
231
|
+ i = vr->desc[i].next;
|
|
232
|
+
|
|
233
|
+ } else if (queue_index == RX_INDEX) {
|
|
234
|
+
|
|
235
|
+ BUG_ON(index >= RX_BUF_NB);
|
|
236
|
+
|
|
237
|
+ /* add header into vring */
|
|
238
|
+
|
|
239
|
+ vr->desc[i].flags = VRING_DESC_F_NEXT|VRING_DESC_F_WRITE;
|
|
240
|
+ vr->desc[i].addr = (u64)virt_to_phys(&rx_hdr[index]);
|
|
241
|
+ vr->desc[i].len = sizeof(struct virtio_net_hdr);
|
|
242
|
+ i = vr->desc[i].next;
|
|
243
|
+
|
|
244
|
+ /* add frame buffer into vring */
|
|
245
|
+
|
|
246
|
+ vr->desc[i].flags = VRING_DESC_F_WRITE;
|
|
247
|
+ vr->desc[i].addr = (u64)virt_to_phys(&rx_buffer[index]);
|
|
248
|
+ vr->desc[i].len = ETH_FRAME_LEN;
|
|
249
|
+ i = vr->desc[i].next;
|
|
250
|
+ }
|
|
251
|
+
|
|
252
|
+ free_head[queue_index] = i;
|
|
253
|
+
|
|
254
|
+ vdata[queue_index][head] = index;
|
|
255
|
+
|
|
256
|
+ avail = (vr->avail->idx + num_added) % vr->num;
|
|
257
|
+ vr->avail->ring[avail] = head;
|
|
258
|
+ wmb();
|
|
259
|
+}
|
|
260
|
+
|
|
261
|
+static void vring_kick(struct nic *nic, int queue_index, int num_added)
|
|
262
|
+{
|
|
263
|
+ struct vring *vr = &vring[queue_index];
|
|
264
|
+
|
|
265
|
+ wmb();
|
|
266
|
+ vr->avail->idx += num_added;
|
|
267
|
+
|
|
268
|
+ mb();
|
|
269
|
+ if (!(vr->used->flags & VRING_USED_F_NO_NOTIFY))
|
|
270
|
+ vp_notify(nic, queue_index);
|
|
271
|
+}
|
|
272
|
+
|
|
273
|
+/*
|
|
274
|
+ * virtnet_disable
|
|
275
|
+ *
|
|
276
|
+ * Turn off ethernet interface
|
|
277
|
+ *
|
|
278
|
+ */
|
|
279
|
+
|
|
280
|
+static void virtnet_disable(struct nic *nic)
|
|
281
|
+{
|
|
282
|
+ int i;
|
|
283
|
+
|
|
284
|
+ for (i = 0; i < QUEUE_NB; i++) {
|
|
285
|
+ vring_disable_cb(i);
|
|
286
|
+ vp_del_vq(nic, i);
|
|
287
|
+ }
|
|
288
|
+ vp_reset(nic);
|
|
289
|
+}
|
|
290
|
+
|
|
291
|
+/*
|
|
292
|
+ * virtnet_poll
|
|
293
|
+ *
|
|
294
|
+ * Wait for a frame
|
|
295
|
+ *
|
|
296
|
+ * return true if there is a packet ready to read
|
|
297
|
+ *
|
|
298
|
+ * nic->packet should contain data on return
|
|
299
|
+ * nic->packetlen should contain length of data
|
|
300
|
+ *
|
|
301
|
+ */
|
|
302
|
+static int virtnet_poll(struct nic *nic, int retrieve)
|
|
303
|
+{
|
|
304
|
+ unsigned int len;
|
|
305
|
+ u16 token;
|
|
306
|
+ struct virtio_net_hdr *hdr;
|
|
307
|
+
|
|
308
|
+ if (!vring_more_used(RX_INDEX))
|
|
309
|
+ return 0;
|
|
310
|
+
|
|
311
|
+ if (!retrieve)
|
|
312
|
+ return 1;
|
|
313
|
+
|
|
314
|
+ token = vring_get_buf(RX_INDEX, &len);
|
|
315
|
+
|
|
316
|
+ BUG_ON(len > sizeof(struct virtio_net_hdr) + ETH_FRAME_LEN);
|
|
317
|
+
|
|
318
|
+ hdr = &rx_hdr[token]; /* FIXME: check flags */
|
|
319
|
+ len -= sizeof(struct virtio_net_hdr);
|
|
320
|
+
|
|
321
|
+ nic->packetlen = len;
|
|
322
|
+ memcpy(nic->packet, (char *)rx_buffer[token], nic->packetlen);
|
|
323
|
+
|
|
324
|
+ /* add buffer to desc */
|
|
325
|
+
|
|
326
|
+ vring_add_buf(RX_INDEX, token, 0);
|
|
327
|
+ vring_kick(nic, RX_INDEX, 1);
|
|
328
|
+
|
|
329
|
+ return 1;
|
|
330
|
+}
|
|
331
|
+
|
|
332
|
+/*
|
|
333
|
+ *
|
|
334
|
+ * virtnet_transmit
|
|
335
|
+ *
|
|
336
|
+ * Transmit a frame
|
|
337
|
+ *
|
|
338
|
+ */
|
|
339
|
+
|
|
340
|
+static void virtnet_transmit(struct nic *nic, const char *destaddr,
|
|
341
|
+ unsigned int type, unsigned int len, const char *data)
|
|
342
|
+{
|
|
343
|
+ /*
|
|
344
|
+ * from http://www.etherboot.org/wiki/dev/devmanual :
|
|
345
|
+ * "You do not need more than one transmit buffer."
|
|
346
|
+ */
|
|
347
|
+
|
|
348
|
+ /* FIXME: initialize header according to vp_get_features() */
|
|
349
|
+
|
|
350
|
+ tx_virtio_hdr.flags = 0;
|
|
351
|
+ tx_virtio_hdr.csum_offset = 0;
|
|
352
|
+ tx_virtio_hdr.csum_start = 0;
|
|
353
|
+ tx_virtio_hdr.gso_type = VIRTIO_NET_HDR_GSO_NONE;
|
|
354
|
+ tx_virtio_hdr.gso_size = 0;
|
|
355
|
+ tx_virtio_hdr.hdr_len = 0;
|
|
356
|
+
|
|
357
|
+ /* add ethernet frame into vring */
|
|
358
|
+
|
|
359
|
+ BUG_ON(len > sizeof(tx_eth_frame.data));
|
|
360
|
+
|
|
361
|
+ memcpy(tx_eth_frame.hdr.dst_addr, destaddr, ETH_ALEN);
|
|
362
|
+ memcpy(tx_eth_frame.hdr.src_addr, nic->node_addr, ETH_ALEN);
|
|
363
|
+ tx_eth_frame.hdr.type = htons(type);
|
|
364
|
+ memcpy(tx_eth_frame.data, data, len);
|
|
365
|
+
|
|
366
|
+ vring_add_buf(TX_INDEX, 0, 0);
|
|
367
|
+
|
|
368
|
+ /*
|
|
369
|
+ * http://www.etherboot.org/wiki/dev/devmanual
|
|
370
|
+ *
|
|
371
|
+ * "You should ensure the packet is fully transmitted
|
|
372
|
+ * before returning from this routine"
|
|
373
|
+ */
|
|
374
|
+
|
|
375
|
+ while (vring_more_used(TX_INDEX)) {
|
|
376
|
+ mb();
|
|
377
|
+ udelay(10);
|
|
378
|
+ }
|
|
379
|
+
|
|
380
|
+ vring_kick(nic, TX_INDEX, 1);
|
|
381
|
+
|
|
382
|
+ /* free desc */
|
|
383
|
+
|
|
384
|
+ (void)vring_get_buf(TX_INDEX, NULL);
|
|
385
|
+}
|
|
386
|
+
|
|
387
|
+static void virtnet_irq(struct nic *nic __unused, irq_action_t action)
|
|
388
|
+{
|
|
389
|
+ switch ( action ) {
|
|
390
|
+ case DISABLE :
|
|
391
|
+ vring_disable_cb(RX_INDEX);
|
|
392
|
+ vring_disable_cb(TX_INDEX);
|
|
393
|
+ break;
|
|
394
|
+ case ENABLE :
|
|
395
|
+ vring_enable_cb(RX_INDEX);
|
|
396
|
+ vring_enable_cb(TX_INDEX);
|
|
397
|
+ break;
|
|
398
|
+ case FORCE :
|
|
399
|
+ break;
|
|
400
|
+ }
|
|
401
|
+}
|
|
402
|
+
|
|
403
|
+static void provide_buffers(struct nic *nic)
|
|
404
|
+{
|
|
405
|
+ int i;
|
|
406
|
+
|
|
407
|
+ for (i = 0; i < RX_BUF_NB; i++)
|
|
408
|
+ vring_add_buf(RX_INDEX, i, i);
|
|
409
|
+
|
|
410
|
+ /* nofify */
|
|
411
|
+
|
|
412
|
+ vring_kick(nic, RX_INDEX, i);
|
|
413
|
+}
|
|
414
|
+
|
|
415
|
+static struct nic_operations virtnet_operations = {
|
|
416
|
+ .connect = dummy_connect,
|
|
417
|
+ .poll = virtnet_poll,
|
|
418
|
+ .transmit = virtnet_transmit,
|
|
419
|
+ .irq = virtnet_irq,
|
|
420
|
+};
|
|
421
|
+
|
|
422
|
+/*
|
|
423
|
+ * virtnet_probe
|
|
424
|
+ *
|
|
425
|
+ * Look for a virtio network adapter
|
|
426
|
+ *
|
|
427
|
+ */
|
|
428
|
+
|
|
429
|
+static int virtnet_probe(struct nic *nic, struct pci_device *pci)
|
|
430
|
+{
|
|
431
|
+ u32 features;
|
|
432
|
+ int i;
|
|
433
|
+
|
|
434
|
+ /* Mask the bit that says "this is an io addr" */
|
|
435
|
+
|
|
436
|
+ nic->ioaddr = pci->ioaddr & ~3;
|
|
437
|
+
|
|
438
|
+ /* Copy IRQ from PCI information */
|
|
439
|
+
|
|
440
|
+ nic->irqno = pci->irq;
|
|
441
|
+
|
|
442
|
+ printf("I/O address 0x%08x, IRQ #%d\n", nic->ioaddr, nic->irqno);
|
|
443
|
+
|
|
444
|
+ adjust_pci_device(pci);
|
|
445
|
+
|
|
446
|
+ vp_reset(nic);
|
|
447
|
+
|
|
448
|
+ features = vp_get_features(nic);
|
|
449
|
+ if (features & (1 << VIRTIO_NET_F_MAC)) {
|
|
450
|
+ vp_get(nic, offsetof(struct virtio_net_config, mac),
|
|
451
|
+ nic->node_addr, ETH_ALEN);
|
|
452
|
+ printf("MAC address ");
|
|
453
|
+ for (i = 0; i < ETH_ALEN; i++) {
|
|
454
|
+ printf("%02x%c", nic->node_addr[i],
|
|
455
|
+ (i == ETH_ALEN - 1) ? '\n' : ':');
|
|
456
|
+ }
|
|
457
|
+ }
|
|
458
|
+
|
|
459
|
+ /* initialize emit/receive queue */
|
|
460
|
+
|
|
461
|
+ for (i = 0; i < QUEUE_NB; i++) {
|
|
462
|
+ free_head[i] = 0;
|
|
463
|
+ last_used_idx[i] = 0;
|
|
464
|
+ memset((char*)&queue[i], 0, sizeof(queue[i]));
|
|
465
|
+ if (vp_find_vq(nic, i) == -1)
|
|
466
|
+ printf("Cannot register queue #%d\n", i);
|
|
467
|
+ }
|
|
468
|
+
|
|
469
|
+ /* provide some receive buffers */
|
|
470
|
+
|
|
471
|
+ provide_buffers(nic);
|
|
472
|
+
|
|
473
|
+ /* define NIC interface */
|
|
474
|
+
|
|
475
|
+ nic->nic_op = &virtnet_operations;
|
|
476
|
+
|
|
477
|
+ /* driver is ready */
|
|
478
|
+
|
|
479
|
+ vp_set_features(nic, features & (1 << VIRTIO_NET_F_MAC));
|
|
480
|
+ vp_set_status(nic, VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK);
|
|
481
|
+
|
|
482
|
+ return 1;
|
|
483
|
+}
|
|
484
|
+
|
|
485
|
+static struct pci_device_id virtnet_nics[] = {
|
|
486
|
+PCI_ROM(0x1af4, 0x1000, "virtio-net", "Virtio Network Interface"),
|
|
487
|
+};
|
|
488
|
+
|
|
489
|
+PCI_DRIVER ( virtnet_driver, virtnet_nics, PCI_NO_CLASS );
|
|
490
|
+
|
|
491
|
+DRIVER ( "VIRTIO-NET", nic_driver, pci_driver, virtnet_driver,
|
|
492
|
+ virtnet_probe, virtnet_disable );
|