Move desc free list functions to libvirtio
[helenos.git] / uspace / drv / nic / virtio-net / virtio-net.c
blob2f67c147fa1889ab2b483e2ff88d1aa3bcd0fa44
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 static void virtio_net_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
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;
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;
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");
154 virtio_virtq_produce_available(vdev, RX_QUEUE_1, descno);
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);
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);
167 static errno_t virtio_net_register_interrupt(ddf_dev_t *dev)
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;
173 hw_res_list_parsed_t res;
174 hw_res_list_parsed_init(&res);
176 errno_t rc = nic_get_resources(nic, &res);
177 if (rc != EOK)
178 return rc;
180 if (res.irqs.count < 1) {
181 hw_res_list_parsed_clean(&res);
182 rc = EINVAL;
183 return rc;
186 virtio_net->irq = res.irqs.irqs[0];
187 hw_res_list_parsed_clean(&res);
189 irq_pio_range_t pio_ranges[] = {
191 .base = vdev->isr_phys,
192 .size = sizeof(vdev->isr_phys),
196 irq_cmd_t irq_commands[] = {
198 .cmd = CMD_PIO_READ_8,
199 .addr = (void *) vdev->isr_phys,
200 .dstarg = 2
203 .cmd = CMD_PREDICATE,
204 .value = 1,
205 .srcarg = 2
208 .cmd = CMD_ACCEPT
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
219 return register_interrupt_handler(dev, virtio_net->irq,
220 virtio_net_irq_handler, &irq_code, &virtio_net->irq_handle);
223 static errno_t virtio_net_initialize(ddf_dev_t *dev)
225 nic_t *nic = nic_create_and_bind(dev);
226 if (!nic)
227 return ENOMEM;
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;
235 nic_set_specific(nic, virtio_net);
237 errno_t rc = virtio_pci_dev_initialize(dev, &virtio_net->virtio_dev);
238 if (rc != EOK)
239 return rc;
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;
246 * Register IRQ
248 rc = virtio_net_register_interrupt(dev);
249 if (rc != EOK)
250 goto fail;
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;
258 /* Perform device-specific setup */
261 * Discover and configure the virtqueues
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;
270 vdev->queues = calloc(sizeof(virtq_t), num_queues);
271 if (!vdev->queues) {
272 rc = ENOMEM;
273 goto fail;
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;
287 * Setup DMA buffers
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;
303 * Give all RX buffers to the NIC
305 for (unsigned i = 0; i < RX_BUFFERS; i++) {
307 * Associtate the buffer with the descriptor, set length and
308 * flags.
310 virtio_virtq_desc_set(vdev, RX_QUEUE_1, i,
311 virtio_net->rx_buf_p[i], RX_BUF_SIZE, VIRTQ_DESC_F_WRITE,
314 * Put the set descriptor into the available ring of the RX
315 * queue.
317 virtio_virtq_produce_available(vdev, RX_QUEUE_1, i);
321 * Put all TX and CT buffers on a free list
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);
329 * Read the MAC address
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;
338 ddf_msg(LVL_NOTE, "MAC address: " PRIMAC, ARGSMAC(nic_addr.address));
341 * Enable IRQ
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;
350 ddf_msg(LVL_NOTE, "Registered IRQ %d", virtio_net->irq);
352 /* Go live */
353 virtio_device_setup_finalize(vdev);
355 return EOK;
357 fail:
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);
362 virtio_device_setup_fail(vdev);
363 virtio_pci_dev_cleanup(vdev);
364 return rc;
367 static void virtio_net_uninitialize(ddf_dev_t *dev)
369 nic_t *nic = ddf_dev_data_get(dev);
370 virtio_net_t *virtio_net = (virtio_net_t *) nic_get_specific(nic);
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);
376 virtio_device_setup_fail(&virtio_net->virtio_dev);
377 virtio_pci_dev_cleanup(&virtio_net->virtio_dev);
380 static void virtio_net_send(nic_t *nic, void *data, size_t size)
382 virtio_net_t *virtio_net = nic_get_specific(nic);
383 virtio_dev_t *vdev = &virtio_net->virtio_dev;
385 if (size > sizeof(virtio_net) + TX_BUF_SIZE) {
386 ddf_msg(LVL_WARN, "TX data too big, frame dropped");
387 return;
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;
396 assert(descno < TX_BUFFERS);
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;
403 /* Copy packet data into the buffer just past the header */
404 memcpy(&hdr[1], data, size);
407 * Set the descriptor, put it into the virtqueue and notify the device
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);
415 static 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)
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;
432 return EOK;
435 static errno_t virtio_net_on_broadcast_mode_change(nic_t *nic,
436 nic_broadcast_mode_t new_mode)
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;
448 static errno_t virtio_net_dev_add(ddf_dev_t *dev)
450 ddf_msg(LVL_NOTE, "%s %s (handle = %zu)", __func__,
451 ddf_dev_get_name(dev), ddf_dev_get_handle(dev));
453 errno_t rc = virtio_net_initialize(dev);
454 if (rc != EOK)
455 return rc;
457 ddf_fun_t *fun = ddf_fun_create(dev, fun_exposed, "port0");
458 if (fun == NULL) {
459 rc = ENOMEM;
460 goto error;
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);
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);
471 rc = ddf_fun_bind(fun);
472 if (rc != EOK) {
473 ddf_msg(LVL_ERROR, "Failed binding device function");
474 goto uninitialize;
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;
483 ddf_msg(LVL_NOTE, "The %s device has been successfully initialized.",
484 ddf_dev_get_name(dev));
486 return EOK;
488 unbind:
489 ddf_fun_unbind(fun);
490 uninitialize:
491 virtio_net_uninitialize(dev);
492 error:
493 return rc;
496 static errno_t virtio_net_get_device_info(ddf_fun_t *fun,
497 nic_device_info_t *info)
499 nic_t *nic = nic_get_from_ddf_fun(fun);
500 if (!nic)
501 return ENOENT;
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");
507 return EOK;
510 static errno_t virtio_net_get_cable_state(ddf_fun_t *fun,
511 nic_cable_state_t *state)
513 *state = NIC_CS_PLUGGED;
514 return EOK;
517 static errno_t virtio_net_get_operation_mode(ddf_fun_t *fun, int *speed,
518 nic_channel_mode_t *duplex, nic_role_t *role)
520 *speed = 1000;
521 *duplex = NIC_CM_FULL_DUPLEX;
522 *role = NIC_ROLE_UNKNOWN;
523 return EOK;
526 static 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,
532 int main(void)
534 printf("%s: HelenOS virtio-net driver\n", NAME);
536 if (nic_driver_init(NAME) != EOK)
537 return 1;
539 nic_driver_implement(&virtio_net_driver_ops, &virtio_net_dev_ops,
540 &virtio_net_nic_iface);
542 (void) ddf_log_init(NAME);
543 return ddf_driver_main(&virtio_net_driver);