source: mainline/uspace/drv/nic/virtio-net/virtio-net.c@ 331d024

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 331d024 was 331d024, checked in by Jakub Jermar <jakub@…>, 7 years ago

Move desc free list functions to libvirtio

  • Property mode set to 100644
File size: 13.7 KB
Line 
1/*
2 * Copyright (c) 2018 Jakub Jermar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "virtio-net.h"
30
31#include <stdio.h>
32#include <stdint.h>
33
34#include <as.h>
35#include <ddf/driver.h>
36#include <ddf/interrupt.h>
37#include <ddf/log.h>
38#include <ops/nic.h>
39#include <pci_dev_iface.h>
40#include <nic/nic.h>
41
42#include <nic.h>
43
44#include <virtio-pci.h>
45
46#define NAME "virtio-net"
47
48#define VIRTIO_NET_NUM_QUEUES 3
49
50#define RX_QUEUE_1 0
51#define TX_QUEUE_1 1
52#define CT_QUEUE_1 2
53
54#define BUFFER_SIZE 2048
55#define RX_BUF_SIZE BUFFER_SIZE
56#define TX_BUF_SIZE BUFFER_SIZE
57#define CT_BUF_SIZE BUFFER_SIZE
58
59static ddf_dev_ops_t virtio_net_dev_ops;
60
61static errno_t virtio_net_dev_add(ddf_dev_t *dev);
62
63static driver_ops_t virtio_net_driver_ops = {
64 .dev_add = virtio_net_dev_add
65};
66
67static driver_t virtio_net_driver = {
68 .name = NAME,
69 .driver_ops = &virtio_net_driver_ops
70};
71
72/** Allocate DMA buffers
73 *
74 * @param buffers[in] Number of buffers to allocate.
75 * @param size[in] Size of each buffer.
76 * @param write[in] True if the buffers are writable by the driver, false
77 * otherwise.
78 * @param buf[out] Output array holding virtual addresses of the allocated
79 * buffers.
80 * @param buf_p[out] Output array holding physical addresses of the allocated
81 * buffers.
82 *
83 * The buffers can be deallocated by virtio_net_teardown_bufs().
84 *
85 * @return EOK on success or error code.
86 */
87static errno_t virtio_net_setup_bufs(unsigned int buffers, size_t size,
88 bool write, void *buf[], uintptr_t buf_p[])
89{
90 /*
91 * Allocate all buffers at once in one large chunk.
92 */
93 void *virt = AS_AREA_ANY;
94 uintptr_t phys;
95 errno_t rc = dmamem_map_anonymous(buffers * size, 0,
96 write ? AS_AREA_WRITE : AS_AREA_READ, 0, &phys, &virt);
97 if (rc != EOK)
98 return rc;
99
100 ddf_msg(LVL_NOTE, "DMA buffers: %p-%p", virt, virt + buffers * size);
101
102 /*
103 * Calculate addresses of the individual buffers for easy access.
104 */
105 for (unsigned i = 0; i < buffers; i++) {
106 buf[i] = virt + i * size;
107 buf_p[i] = phys + i * size;
108 }
109
110 return EOK;
111}
112
113/** Deallocate DMA buffers
114 *
115 * @param buf[in] Array holding the virtual addresses of the DMA buffers
116 * previously allocated by virtio_net_setup_bufs().
117 */
118static void virtio_net_teardown_bufs(void *buf[])
119{
120 if (buf[0]) {
121 dmamem_unmap_anonymous(buf[0]);
122 buf[0] = NULL;
123 }
124}
125
126static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
127{
128 nic_t *nic = ddf_dev_data_get(dev);
129 virtio_net_t *virtio_net = nic_get_specific(nic);
130 virtio_dev_t *vdev = &virtio_net->virtio_dev;
131
132 uint16_t descno;
133 uint32_t len;
134 while (virtio_virtq_consume_used(vdev, RX_QUEUE_1, &descno, &len)) {
135 virtio_net_hdr_t *hdr =
136 (virtio_net_hdr_t *) virtio_net->rx_buf[descno];
137 if (len <= sizeof(*hdr)) {
138 ddf_msg(LVL_WARN,
139 "RX data length too short, packet dropped");
140 virtio_virtq_produce_available(vdev, RX_QUEUE_1,
141 descno);
142 continue;
143 }
144
145 nic_frame_t *frame = nic_alloc_frame(nic, len - sizeof(*hdr));
146 if (frame) {
147 memcpy(frame->data, &hdr[1], len - sizeof(*hdr));
148 nic_received_frame(nic, frame);
149 } else {
150 ddf_msg(LVL_WARN,
151 "Cannot allocate RX frame, packet dropped");
152 }
153
154 virtio_virtq_produce_available(vdev, RX_QUEUE_1, descno);
155 }
156
157 while (virtio_virtq_consume_used(vdev, TX_QUEUE_1, &descno, &len)) {
158 virtio_free_desc(vdev, TX_QUEUE_1, &virtio_net->tx_free_head,
159 descno);
160 }
161 while (virtio_virtq_consume_used(vdev, CT_QUEUE_1, &descno, &len)) {
162 virtio_free_desc(vdev, CT_QUEUE_1, &virtio_net->ct_free_head,
163 descno);
164 }
165}
166
167static errno_t virtio_net_register_interrupt(ddf_dev_t *dev)
168{
169 nic_t *nic = ddf_dev_data_get(dev);
170 virtio_net_t *virtio_net = nic_get_specific(nic);
171 virtio_dev_t *vdev = &virtio_net->virtio_dev;
172
173 hw_res_list_parsed_t res;
174 hw_res_list_parsed_init(&res);
175
176 errno_t rc = nic_get_resources(nic, &res);
177 if (rc != EOK)
178 return rc;
179
180 if (res.irqs.count < 1) {
181 hw_res_list_parsed_clean(&res);
182 rc = EINVAL;
183 return rc;
184 }
185
186 virtio_net->irq = res.irqs.irqs[0];
187 hw_res_list_parsed_clean(&res);
188
189 irq_pio_range_t pio_ranges[] = {
190 {
191 .base = vdev->isr_phys,
192 .size = sizeof(vdev->isr_phys),
193 }
194 };
195
196 irq_cmd_t irq_commands[] = {
197 {
198 .cmd = CMD_PIO_READ_8,
199 .addr = (void *) vdev->isr_phys,
200 .dstarg = 2
201 },
202 {
203 .cmd = CMD_PREDICATE,
204 .value = 1,
205 .srcarg = 2
206 },
207 {
208 .cmd = CMD_ACCEPT
209 }
210 };
211
212 irq_code_t irq_code = {
213 .rangecount = sizeof(pio_ranges) / sizeof(irq_pio_range_t),
214 .ranges = pio_ranges,
215 .cmdcount = sizeof(irq_commands) / sizeof(irq_cmd_t),
216 .cmds = irq_commands
217 };
218
219 return register_interrupt_handler(dev, virtio_net->irq,
220 virtio_net_irq_handler, &irq_code, &virtio_net->irq_handle);
221}
222
223static errno_t virtio_net_initialize(ddf_dev_t *dev)
224{
225 nic_t *nic = nic_create_and_bind(dev);
226 if (!nic)
227 return ENOMEM;
228
229 virtio_net_t *virtio_net = calloc(1, sizeof(virtio_net_t));
230 if (!virtio_net) {
231 nic_unbind_and_destroy(dev);
232 return ENOMEM;
233 }
234
235 nic_set_specific(nic, virtio_net);
236
237 errno_t rc = virtio_pci_dev_initialize(dev, &virtio_net->virtio_dev);
238 if (rc != EOK)
239 return rc;
240
241 virtio_dev_t *vdev = &virtio_net->virtio_dev;
242 virtio_pci_common_cfg_t *cfg = virtio_net->virtio_dev.common_cfg;
243 virtio_net_cfg_t *netcfg = virtio_net->virtio_dev.device_cfg;
244
245 /*
246 * Register IRQ
247 */
248 rc = virtio_net_register_interrupt(dev);
249 if (rc != EOK)
250 goto fail;
251
252 /* Reset the device and negotiate the feature bits */
253 rc = virtio_device_setup_start(vdev,
254 VIRTIO_NET_F_MAC | VIRTIO_NET_F_CTRL_VQ);
255 if (rc != EOK)
256 goto fail;
257
258 /* Perform device-specific setup */
259
260 /*
261 * Discover and configure the virtqueues
262 */
263 uint16_t num_queues = pio_read_le16(&cfg->num_queues);
264 if (num_queues != VIRTIO_NET_NUM_QUEUES) {
265 ddf_msg(LVL_NOTE, "Unsupported number of virtqueues: %u",
266 num_queues);
267 goto fail;
268 }
269
270 vdev->queues = calloc(sizeof(virtq_t), num_queues);
271 if (!vdev->queues) {
272 rc = ENOMEM;
273 goto fail;
274 }
275
276 rc = virtio_virtq_setup(vdev, RX_QUEUE_1, RX_BUFFERS);
277 if (rc != EOK)
278 goto fail;
279 rc = virtio_virtq_setup(vdev, TX_QUEUE_1, TX_BUFFERS);
280 if (rc != EOK)
281 goto fail;
282 rc = virtio_virtq_setup(vdev, CT_QUEUE_1, CT_BUFFERS);
283 if (rc != EOK)
284 goto fail;
285
286 /*
287 * Setup DMA buffers
288 */
289 rc = virtio_net_setup_bufs(RX_BUFFERS, RX_BUF_SIZE, false,
290 virtio_net->rx_buf, virtio_net->rx_buf_p);
291 if (rc != EOK)
292 goto fail;
293 rc = virtio_net_setup_bufs(TX_BUFFERS, TX_BUF_SIZE, true,
294 virtio_net->tx_buf, virtio_net->tx_buf_p);
295 if (rc != EOK)
296 goto fail;
297 rc = virtio_net_setup_bufs(CT_BUFFERS, CT_BUF_SIZE, true,
298 virtio_net->ct_buf, virtio_net->ct_buf_p);
299 if (rc != EOK)
300 goto fail;
301
302 /*
303 * Give all RX buffers to the NIC
304 */
305 for (unsigned i = 0; i < RX_BUFFERS; i++) {
306 /*
307 * Associtate the buffer with the descriptor, set length and
308 * flags.
309 */
310 virtio_virtq_desc_set(vdev, RX_QUEUE_1, i,
311 virtio_net->rx_buf_p[i], RX_BUF_SIZE, VIRTQ_DESC_F_WRITE,
312 0);
313 /*
314 * Put the set descriptor into the available ring of the RX
315 * queue.
316 */
317 virtio_virtq_produce_available(vdev, RX_QUEUE_1, i);
318 }
319
320 /*
321 * Put all TX and CT buffers on a free list
322 */
323 virtio_create_desc_free_list(vdev, TX_QUEUE_1, TX_BUFFERS,
324 &virtio_net->tx_free_head);
325 virtio_create_desc_free_list(vdev, CT_QUEUE_1, CT_BUFFERS,
326 &virtio_net->ct_free_head);
327
328 /*
329 * Read the MAC address
330 */
331 nic_address_t nic_addr;
332 for (unsigned i = 0; i < ETH_ADDR; i++)
333 nic_addr.address[i] = pio_read_8(&netcfg->mac[i]);
334 rc = nic_report_address(nic, &nic_addr);
335 if (rc != EOK)
336 goto fail;
337
338 ddf_msg(LVL_NOTE, "MAC address: " PRIMAC, ARGSMAC(nic_addr.address));
339
340 /*
341 * Enable IRQ
342 */
343 rc = hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev),
344 virtio_net->irq);
345 if (rc != EOK) {
346 ddf_msg(LVL_NOTE, "Failed to enable interrupt");
347 goto fail;
348 }
349
350 ddf_msg(LVL_NOTE, "Registered IRQ %d", virtio_net->irq);
351
352 /* Go live */
353 virtio_device_setup_finalize(vdev);
354
355 return EOK;
356
357fail:
358 virtio_net_teardown_bufs(virtio_net->rx_buf);
359 virtio_net_teardown_bufs(virtio_net->tx_buf);
360 virtio_net_teardown_bufs(virtio_net->ct_buf);
361
362 virtio_device_setup_fail(vdev);
363 virtio_pci_dev_cleanup(vdev);
364 return rc;
365}
366
367static void virtio_net_uninitialize(ddf_dev_t *dev)
368{
369 nic_t *nic = ddf_dev_data_get(dev);
370 virtio_net_t *virtio_net = (virtio_net_t *) nic_get_specific(nic);
371
372 virtio_net_teardown_bufs(virtio_net->rx_buf);
373 virtio_net_teardown_bufs(virtio_net->tx_buf);
374 virtio_net_teardown_bufs(virtio_net->ct_buf);
375
376 virtio_device_setup_fail(&virtio_net->virtio_dev);
377 virtio_pci_dev_cleanup(&virtio_net->virtio_dev);
378}
379
380static void virtio_net_send(nic_t *nic, void *data, size_t size)
381{
382 virtio_net_t *virtio_net = nic_get_specific(nic);
383 virtio_dev_t *vdev = &virtio_net->virtio_dev;
384
385 if (size > sizeof(virtio_net) + TX_BUF_SIZE) {
386 ddf_msg(LVL_WARN, "TX data too big, frame dropped");
387 return;
388 }
389
390 uint16_t descno = virtio_alloc_desc(vdev, TX_QUEUE_1,
391 &virtio_net->tx_free_head);
392 if (descno == (uint16_t) -1U) {
393 ddf_msg(LVL_WARN, "No TX buffers available, frame dropped");
394 return;
395 }
396 assert(descno < TX_BUFFERS);
397
398 /* Setup the packed header */
399 virtio_net_hdr_t *hdr = (virtio_net_hdr_t *) virtio_net->tx_buf[descno];
400 memset(hdr, 0, sizeof(virtio_net_hdr_t));
401 hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
402
403 /* Copy packet data into the buffer just past the header */
404 memcpy(&hdr[1], data, size);
405
406 /*
407 * Set the descriptor, put it into the virtqueue and notify the device
408 */
409 virtio_virtq_desc_set(vdev, TX_QUEUE_1, descno,
410 virtio_net->tx_buf_p[descno], sizeof(virtio_net_hdr_t) + size, 0, 0);
411 virtio_virtq_produce_available(vdev, TX_QUEUE_1, descno);
412}
413
414
415static errno_t virtio_net_on_multicast_mode_change(nic_t *nic,
416 nic_multicast_mode_t new_mode, const nic_address_t *address_list,
417 size_t address_count)
418{
419 switch (new_mode) {
420 case NIC_MULTICAST_BLOCKED:
421 nic_report_hw_filtering(nic, -1, 0, -1);
422 return EOK;
423 case NIC_MULTICAST_LIST:
424 nic_report_hw_filtering(nic, -1, 0, -1);
425 return EOK;
426 case NIC_MULTICAST_PROMISC:
427 nic_report_hw_filtering(nic, -1, 0, -1);
428 return EOK;
429 default:
430 return ENOTSUP;
431 }
432 return EOK;
433}
434
435static errno_t virtio_net_on_broadcast_mode_change(nic_t *nic,
436 nic_broadcast_mode_t new_mode)
437{
438 switch (new_mode) {
439 case NIC_BROADCAST_BLOCKED:
440 return ENOTSUP;
441 case NIC_BROADCAST_ACCEPTED:
442 return EOK;
443 default:
444 return ENOTSUP;
445 }
446}
447
448static errno_t virtio_net_dev_add(ddf_dev_t *dev)
449{
450 ddf_msg(LVL_NOTE, "%s %s (handle = %zu)", __func__,
451 ddf_dev_get_name(dev), ddf_dev_get_handle(dev));
452
453 errno_t rc = virtio_net_initialize(dev);
454 if (rc != EOK)
455 return rc;
456
457 ddf_fun_t *fun = ddf_fun_create(dev, fun_exposed, "port0");
458 if (fun == NULL) {
459 rc = ENOMEM;
460 goto error;
461 }
462 nic_t *nic = ddf_dev_data_get(dev);
463 nic_set_ddf_fun(nic, fun);
464 ddf_fun_set_ops(fun, &virtio_net_dev_ops);
465
466 nic_set_send_frame_handler(nic, virtio_net_send);
467 nic_set_filtering_change_handlers(nic, NULL,
468 virtio_net_on_multicast_mode_change,
469 virtio_net_on_broadcast_mode_change, NULL, NULL);
470
471 rc = ddf_fun_bind(fun);
472 if (rc != EOK) {
473 ddf_msg(LVL_ERROR, "Failed binding device function");
474 goto uninitialize;
475 }
476
477 rc = ddf_fun_add_to_category(fun, DEVICE_CATEGORY_NIC);
478 if (rc != EOK) {
479 ddf_msg(LVL_ERROR, "Failed adding function to category");
480 goto unbind;
481 }
482
483 ddf_msg(LVL_NOTE, "The %s device has been successfully initialized.",
484 ddf_dev_get_name(dev));
485
486 return EOK;
487
488unbind:
489 ddf_fun_unbind(fun);
490uninitialize:
491 virtio_net_uninitialize(dev);
492error:
493 return rc;
494}
495
496static errno_t virtio_net_get_device_info(ddf_fun_t *fun,
497 nic_device_info_t *info)
498{
499 nic_t *nic = nic_get_from_ddf_fun(fun);
500 if (!nic)
501 return ENOENT;
502
503 str_cpy(info->vendor_name, sizeof(info->vendor_name), "Red Hat, Inc.");
504 str_cpy(info->model_name, sizeof(info->model_name),
505 "Virtio network device");
506
507 return EOK;
508}
509
510static errno_t virtio_net_get_cable_state(ddf_fun_t *fun,
511 nic_cable_state_t *state)
512{
513 *state = NIC_CS_PLUGGED;
514 return EOK;
515}
516
517static errno_t virtio_net_get_operation_mode(ddf_fun_t *fun, int *speed,
518 nic_channel_mode_t *duplex, nic_role_t *role)
519{
520 *speed = 1000;
521 *duplex = NIC_CM_FULL_DUPLEX;
522 *role = NIC_ROLE_UNKNOWN;
523 return EOK;
524}
525
526static nic_iface_t virtio_net_nic_iface = {
527 .get_device_info = virtio_net_get_device_info,
528 .get_cable_state = virtio_net_get_cable_state,
529 .get_operation_mode = virtio_net_get_operation_mode,
530};
531
532int main(void)
533{
534 printf("%s: HelenOS virtio-net driver\n", NAME);
535
536 if (nic_driver_init(NAME) != EOK)
537 return 1;
538
539 nic_driver_implement(&virtio_net_driver_ops, &virtio_net_dev_ops,
540 &virtio_net_nic_iface);
541
542 (void) ddf_log_init(NAME);
543 return ddf_driver_main(&virtio_net_driver);
544}
Note: See TracBrowser for help on using the repository browser.