Protect the virtqueue with a mutex
[helenos.git] / uspace / drv / nic / virtio-net / virtio-net.c
blob92b0da171975cd3786b8e18a34957714bbde94c7
1 /*
2 * Copyright (c) 2018 Jakub Jermar
3 * All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
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.
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.
29 #include "virtio-net.h"
31 #include <stdio.h>
32 #include <stdint.h>
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>
42 #include <nic.h>
44 #include <virtio-pci.h>
46 #define NAME "virtio-net"
48 #define VIRTIO_NET_NUM_QUEUES 3
50 #define RX_QUEUE_1 0
51 #define TX_QUEUE_1 1
52 #define CT_QUEUE_1 2
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
59 static ddf_dev_ops_t virtio_net_dev_ops;
61 static errno_t virtio_net_dev_add(ddf_dev_t *dev);
63 static driver_ops_t virtio_net_driver_ops = {
64 .dev_add = virtio_net_dev_add
67 static driver_t virtio_net_driver = {
68 .name = NAME,
69 .driver_ops = &virtio_net_driver_ops
72 /** Allocate DMA buffers
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.
83 * The buffers can be deallocated by virtio_net_teardown_bufs().
85 * @return EOK on success or error code.
87 static errno_t virtio_net_setup_bufs(unsigned int buffers, size_t size,
88 bool write, void *buf[], uintptr_t buf_p[])
91 * Allocate all buffers at once in one large chunk.
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;
100 ddf_msg(LVL_NOTE, "DMA buffers: %p-%p", virt, virt + buffers * size);
103 * Calculate addresses of the individual buffers for easy access.
105 for (unsigned i = 0; i < buffers; i++) {
106 buf[i] = virt + i * size;
107 buf_p[i] = phys + i * size;
110 return EOK;
113 /** Deallocate DMA buffers
115 * @param buf[in] Array holding the virtual addresses of the DMA buffers
116 * previously allocated by virtio_net_setup_bufs().
118 static void virtio_net_teardown_bufs(void *buf[])
120 if (buf[0]) {
121 dmamem_unmap_anonymous(buf[0]);
122 buf[0] = NULL;
126 /** Create free descriptor list from the unused VIRTIO descriptors
128 * @param vdev[in] VIRTIO device for which the free list will be created.
129 * @param num[in] Index of the virtqueue for which the free list will be
130 * created.
131 * @param size[in] Number of descriptors on the free list. The free list will
132 * contain descriptors starting from 0 to \a size - 1.
133 * @param head[out] Variable that will hold the VIRTIO descriptor at the head
134 * of the free list.
136 static void virtio_net_create_desc_free_list(virtio_dev_t *vdev, uint16_t num,
137 uint16_t size, uint16_t *head)
139 for (unsigned i = 0; i < size; i++) {
140 virtio_virtq_desc_set(vdev, num, i, 0, 0,
141 VIRTQ_DESC_F_NEXT, (i + 1 == size) ? -1U : i + 1);
143 *head = 0;
146 /** Allocate a descriptor from the free list
148 * @param vdev[in] VIRTIO device with the free list.
149 * @param num[in] Index of the virtqueue with free list.
150 * @param head[in,out] Head of the free list.
152 * @return Allocated descriptor or 0xFFFF if the list is empty.
154 static uint16_t virtio_net_alloc_desc(virtio_dev_t *vdev, uint16_t num,
155 uint16_t *head)
157 virtq_t *q = &vdev->queues[num];
158 fibril_mutex_lock(&q->lock);
159 uint16_t descno = *head;
160 if (descno != (uint16_t) -1U)
161 *head = virtio_virtq_desc_get_next(vdev, num, descno);
162 fibril_mutex_unlock(&q->lock);
163 return descno;
166 /** Free a descriptor into the free list
168 * @param vdev[in] VIRTIO device with the free list.
169 * @param num[in] Index of the virtqueue with free list.
170 * @param head[in,out] Head of the free list.
171 * @param descno[in] The freed descriptor.
173 static void virtio_net_free_desc(virtio_dev_t *vdev, uint16_t num,
174 uint16_t *head, uint16_t descno)
176 virtq_t *q = &vdev->queues[num];
177 fibril_mutex_lock(&q->lock);
178 virtio_virtq_desc_set(vdev, num, descno, 0, 0, VIRTQ_DESC_F_NEXT,
179 *head);
180 *head = descno;
181 fibril_mutex_unlock(&q->lock);
184 static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
186 nic_t *nic = ddf_dev_data_get(dev);
187 virtio_net_t *virtio_net = nic_get_specific(nic);
188 virtio_dev_t *vdev = &virtio_net->virtio_dev;
190 uint16_t descno;
191 uint32_t len;
192 while (virtio_virtq_consume_used(vdev, RX_QUEUE_1, &descno, &len)) {
193 virtio_net_hdr_t *hdr =
194 (virtio_net_hdr_t *) virtio_net->rx_buf[descno];
195 if (len <= sizeof(*hdr)) {
196 ddf_msg(LVL_WARN,
197 "RX data length too short, packet dropped");
198 virtio_virtq_produce_available(vdev, RX_QUEUE_1,
199 descno);
200 continue;
203 nic_frame_t *frame = nic_alloc_frame(nic, len - sizeof(*hdr));
204 if (frame) {
205 memcpy(frame->data, &hdr[1], len - sizeof(*hdr));
206 nic_received_frame(nic, frame);
207 } else {
208 ddf_msg(LVL_WARN,
209 "Cannot allocate RX frame, packet dropped");
212 virtio_virtq_produce_available(vdev, RX_QUEUE_1, descno);
215 while (virtio_virtq_consume_used(vdev, TX_QUEUE_1, &descno, &len)) {
216 virtio_net_free_desc(vdev, TX_QUEUE_1,
217 &virtio_net->tx_free_head, descno);
219 while (virtio_virtq_consume_used(vdev, CT_QUEUE_1, &descno, &len)) {
220 virtio_net_free_desc(vdev, CT_QUEUE_1,
221 &virtio_net->ct_free_head, descno);
225 static errno_t virtio_net_register_interrupt(ddf_dev_t *dev)
227 nic_t *nic = ddf_dev_data_get(dev);
228 virtio_net_t *virtio_net = nic_get_specific(nic);
229 virtio_dev_t *vdev = &virtio_net->virtio_dev;
231 hw_res_list_parsed_t res;
232 hw_res_list_parsed_init(&res);
234 errno_t rc = nic_get_resources(nic, &res);
235 if (rc != EOK)
236 return rc;
238 if (res.irqs.count < 1) {
239 hw_res_list_parsed_clean(&res);
240 rc = EINVAL;
241 return rc;
244 virtio_net->irq = res.irqs.irqs[0];
245 hw_res_list_parsed_clean(&res);
247 irq_pio_range_t pio_ranges[] = {
249 .base = vdev->isr_phys,
250 .size = sizeof(vdev->isr_phys),
254 irq_cmd_t irq_commands[] = {
256 .cmd = CMD_PIO_READ_8,
257 .addr = (void *) vdev->isr_phys,
258 .dstarg = 2
261 .cmd = CMD_PREDICATE,
262 .value = 1,
263 .srcarg = 2
266 .cmd = CMD_ACCEPT
270 irq_code_t irq_code = {
271 .rangecount = sizeof(pio_ranges) / sizeof(irq_pio_range_t),
272 .ranges = pio_ranges,
273 .cmdcount = sizeof(irq_commands) / sizeof(irq_cmd_t),
274 .cmds = irq_commands
277 return register_interrupt_handler(dev, virtio_net->irq,
278 virtio_net_irq_handler, &irq_code, &virtio_net->irq_handle);
281 static errno_t virtio_net_initialize(ddf_dev_t *dev)
283 nic_t *nic = nic_create_and_bind(dev);
284 if (!nic)
285 return ENOMEM;
287 virtio_net_t *virtio_net = calloc(1, sizeof(virtio_net_t));
288 if (!virtio_net) {
289 nic_unbind_and_destroy(dev);
290 return ENOMEM;
293 nic_set_specific(nic, virtio_net);
295 errno_t rc = virtio_pci_dev_initialize(dev, &virtio_net->virtio_dev);
296 if (rc != EOK)
297 return rc;
299 virtio_dev_t *vdev = &virtio_net->virtio_dev;
300 virtio_pci_common_cfg_t *cfg = virtio_net->virtio_dev.common_cfg;
301 virtio_net_cfg_t *netcfg = virtio_net->virtio_dev.device_cfg;
304 * Register IRQ
306 rc = virtio_net_register_interrupt(dev);
307 if (rc != EOK)
308 goto fail;
310 /* Reset the device and negotiate the feature bits */
311 rc = virtio_device_setup_start(vdev,
312 VIRTIO_NET_F_MAC | VIRTIO_NET_F_CTRL_VQ);
313 if (rc != EOK)
314 goto fail;
316 /* Perform device-specific setup */
319 * Discover and configure the virtqueues
321 uint16_t num_queues = pio_read_le16(&cfg->num_queues);
322 if (num_queues != VIRTIO_NET_NUM_QUEUES) {
323 ddf_msg(LVL_NOTE, "Unsupported number of virtqueues: %u",
324 num_queues);
325 goto fail;
328 vdev->queues = calloc(sizeof(virtq_t), num_queues);
329 if (!vdev->queues) {
330 rc = ENOMEM;
331 goto fail;
334 rc = virtio_virtq_setup(vdev, RX_QUEUE_1, RX_BUFFERS);
335 if (rc != EOK)
336 goto fail;
337 rc = virtio_virtq_setup(vdev, TX_QUEUE_1, TX_BUFFERS);
338 if (rc != EOK)
339 goto fail;
340 rc = virtio_virtq_setup(vdev, CT_QUEUE_1, CT_BUFFERS);
341 if (rc != EOK)
342 goto fail;
345 * Setup DMA buffers
347 rc = virtio_net_setup_bufs(RX_BUFFERS, RX_BUF_SIZE, false,
348 virtio_net->rx_buf, virtio_net->rx_buf_p);
349 if (rc != EOK)
350 goto fail;
351 rc = virtio_net_setup_bufs(TX_BUFFERS, TX_BUF_SIZE, true,
352 virtio_net->tx_buf, virtio_net->tx_buf_p);
353 if (rc != EOK)
354 goto fail;
355 rc = virtio_net_setup_bufs(CT_BUFFERS, CT_BUF_SIZE, true,
356 virtio_net->ct_buf, virtio_net->ct_buf_p);
357 if (rc != EOK)
358 goto fail;
361 * Give all RX buffers to the NIC
363 for (unsigned i = 0; i < RX_BUFFERS; i++) {
365 * Associtate the buffer with the descriptor, set length and
366 * flags.
368 virtio_virtq_desc_set(vdev, RX_QUEUE_1, i,
369 virtio_net->rx_buf_p[i], RX_BUF_SIZE, VIRTQ_DESC_F_WRITE,
372 * Put the set descriptor into the available ring of the RX
373 * queue.
375 virtio_virtq_produce_available(vdev, RX_QUEUE_1, i);
379 * Put all TX and CT buffers on a free list
381 virtio_net_create_desc_free_list(vdev, TX_QUEUE_1, TX_BUFFERS,
382 &virtio_net->tx_free_head);
383 virtio_net_create_desc_free_list(vdev, CT_QUEUE_1, CT_BUFFERS,
384 &virtio_net->ct_free_head);
387 * Read the MAC address
389 nic_address_t nic_addr;
390 for (unsigned i = 0; i < ETH_ADDR; i++)
391 nic_addr.address[i] = pio_read_8(&netcfg->mac[i]);
392 rc = nic_report_address(nic, &nic_addr);
393 if (rc != EOK)
394 goto fail;
396 ddf_msg(LVL_NOTE, "MAC address: " PRIMAC, ARGSMAC(nic_addr.address));
399 * Enable IRQ
401 rc = hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev),
402 virtio_net->irq);
403 if (rc != EOK) {
404 ddf_msg(LVL_NOTE, "Failed to enable interrupt");
405 goto fail;
408 ddf_msg(LVL_NOTE, "Registered IRQ %d", virtio_net->irq);
410 /* Go live */
411 virtio_device_setup_finalize(vdev);
413 return EOK;
415 fail:
416 virtio_net_teardown_bufs(virtio_net->rx_buf);
417 virtio_net_teardown_bufs(virtio_net->tx_buf);
418 virtio_net_teardown_bufs(virtio_net->ct_buf);
420 virtio_device_setup_fail(vdev);
421 virtio_pci_dev_cleanup(vdev);
422 return rc;
425 static void virtio_net_uninitialize(ddf_dev_t *dev)
427 nic_t *nic = ddf_dev_data_get(dev);
428 virtio_net_t *virtio_net = (virtio_net_t *) nic_get_specific(nic);
430 virtio_net_teardown_bufs(virtio_net->rx_buf);
431 virtio_net_teardown_bufs(virtio_net->tx_buf);
432 virtio_net_teardown_bufs(virtio_net->ct_buf);
434 virtio_device_setup_fail(&virtio_net->virtio_dev);
435 virtio_pci_dev_cleanup(&virtio_net->virtio_dev);
438 static void virtio_net_send(nic_t *nic, void *data, size_t size)
440 virtio_net_t *virtio_net = nic_get_specific(nic);
441 virtio_dev_t *vdev = &virtio_net->virtio_dev;
443 if (size > sizeof(virtio_net) + TX_BUF_SIZE) {
444 ddf_msg(LVL_WARN, "TX data too big, frame dropped");
445 return;
448 uint16_t descno = virtio_net_alloc_desc(vdev, TX_QUEUE_1,
449 &virtio_net->tx_free_head);
450 if (descno == (uint16_t) -1U) {
451 ddf_msg(LVL_WARN, "No TX buffers available, frame dropped");
452 return;
454 assert(descno < TX_BUFFERS);
456 /* Setup the packed header */
457 virtio_net_hdr_t *hdr = (virtio_net_hdr_t *) virtio_net->tx_buf[descno];
458 memset(hdr, 0, sizeof(virtio_net_hdr_t));
459 hdr->gso_type = VIRTIO_NET_HDR_GSO_NONE;
461 /* Copy packet data into the buffer just past the header */
462 memcpy(&hdr[1], data, size);
465 * Set the descriptor, put it into the virtqueue and notify the device
467 virtio_virtq_desc_set(vdev, TX_QUEUE_1, descno,
468 virtio_net->tx_buf_p[descno], sizeof(virtio_net_hdr_t) + size, 0, 0);
469 virtio_virtq_produce_available(vdev, TX_QUEUE_1, descno);
473 static errno_t virtio_net_on_multicast_mode_change(nic_t *nic,
474 nic_multicast_mode_t new_mode, const nic_address_t *address_list,
475 size_t address_count)
477 switch (new_mode) {
478 case NIC_MULTICAST_BLOCKED:
479 nic_report_hw_filtering(nic, -1, 0, -1);
480 return EOK;
481 case NIC_MULTICAST_LIST:
482 nic_report_hw_filtering(nic, -1, 0, -1);
483 return EOK;
484 case NIC_MULTICAST_PROMISC:
485 nic_report_hw_filtering(nic, -1, 0, -1);
486 return EOK;
487 default:
488 return ENOTSUP;
490 return EOK;
493 static errno_t virtio_net_on_broadcast_mode_change(nic_t *nic,
494 nic_broadcast_mode_t new_mode)
496 switch (new_mode) {
497 case NIC_BROADCAST_BLOCKED:
498 return ENOTSUP;
499 case NIC_BROADCAST_ACCEPTED:
500 return EOK;
501 default:
502 return ENOTSUP;
506 static errno_t virtio_net_dev_add(ddf_dev_t *dev)
508 ddf_msg(LVL_NOTE, "%s %s (handle = %zu)", __func__,
509 ddf_dev_get_name(dev), ddf_dev_get_handle(dev));
511 errno_t rc = virtio_net_initialize(dev);
512 if (rc != EOK)
513 return rc;
515 ddf_fun_t *fun = ddf_fun_create(dev, fun_exposed, "port0");
516 if (fun == NULL) {
517 rc = ENOMEM;
518 goto error;
520 nic_t *nic = ddf_dev_data_get(dev);
521 nic_set_ddf_fun(nic, fun);
522 ddf_fun_set_ops(fun, &virtio_net_dev_ops);
524 nic_set_send_frame_handler(nic, virtio_net_send);
525 nic_set_filtering_change_handlers(nic, NULL,
526 virtio_net_on_multicast_mode_change,
527 virtio_net_on_broadcast_mode_change, NULL, NULL);
529 rc = ddf_fun_bind(fun);
530 if (rc != EOK) {
531 ddf_msg(LVL_ERROR, "Failed binding device function");
532 goto uninitialize;
535 rc = ddf_fun_add_to_category(fun, DEVICE_CATEGORY_NIC);
536 if (rc != EOK) {
537 ddf_msg(LVL_ERROR, "Failed adding function to category");
538 goto unbind;
541 ddf_msg(LVL_NOTE, "The %s device has been successfully initialized.",
542 ddf_dev_get_name(dev));
544 return EOK;
546 unbind:
547 ddf_fun_unbind(fun);
548 uninitialize:
549 virtio_net_uninitialize(dev);
550 error:
551 return rc;
554 static errno_t virtio_net_get_device_info(ddf_fun_t *fun,
555 nic_device_info_t *info)
557 nic_t *nic = nic_get_from_ddf_fun(fun);
558 if (!nic)
559 return ENOENT;
561 str_cpy(info->vendor_name, sizeof(info->vendor_name), "Red Hat, Inc.");
562 str_cpy(info->model_name, sizeof(info->model_name),
563 "Virtio network device");
565 return EOK;
568 static errno_t virtio_net_get_cable_state(ddf_fun_t *fun,
569 nic_cable_state_t *state)
571 *state = NIC_CS_PLUGGED;
572 return EOK;
575 static errno_t virtio_net_get_operation_mode(ddf_fun_t *fun, int *speed,
576 nic_channel_mode_t *duplex, nic_role_t *role)
578 *speed = 1000;
579 *duplex = NIC_CM_FULL_DUPLEX;
580 *role = NIC_ROLE_UNKNOWN;
581 return EOK;
584 static nic_iface_t virtio_net_nic_iface = {
585 .get_device_info = virtio_net_get_device_info,
586 .get_cable_state = virtio_net_get_cable_state,
587 .get_operation_mode = virtio_net_get_operation_mode,
590 int main(void)
592 printf("%s: HelenOS virtio-net driver\n", NAME);
594 if (nic_driver_init(NAME) != EOK)
595 return 1;
597 nic_driver_implement(&virtio_net_driver_ops, &virtio_net_dev_ops,
598 &virtio_net_nic_iface);
600 (void) ddf_log_init(NAME);
601 return ddf_driver_main(&virtio_net_driver);