2 * Copyright (c) 2018 Jakub Jermar
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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"
35 #include <ddf/driver.h>
36 #include <ddf/interrupt.h>
39 #include <pci_dev_iface.h>
44 #include <virtio-pci.h>
46 #define NAME "virtio-net"
48 #define VIRTIO_NET_NUM_QUEUES 3
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
= {
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
78 * @param buf[out] Output array holding virtual addresses of the allocated
80 * @param buf_p[out] Output array holding physical addresses of the allocated
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
;
95 errno_t rc
= dmamem_map_anonymous(buffers
* size
, 0,
96 write
? AS_AREA_WRITE
: AS_AREA_READ
, 0, &phys
, &virt
);
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
;
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
[])
121 dmamem_unmap_anonymous(buf
[0]);
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
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
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);
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
,
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
);
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
,
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
;
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
)) {
197 "RX data length too short, packet dropped");
198 virtio_virtq_produce_available(vdev
, RX_QUEUE_1
,
203 nic_frame_t
*frame
= nic_alloc_frame(nic
, len
- sizeof(*hdr
));
205 memcpy(frame
->data
, &hdr
[1], len
- sizeof(*hdr
));
206 nic_received_frame(nic
, frame
);
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
);
238 if (res
.irqs
.count
< 1) {
239 hw_res_list_parsed_clean(&res
);
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
,
261 .cmd
= CMD_PREDICATE
,
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
),
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
);
287 virtio_net_t
*virtio_net
= calloc(1, sizeof(virtio_net_t
));
289 nic_unbind_and_destroy(dev
);
293 nic_set_specific(nic
, virtio_net
);
295 errno_t rc
= virtio_pci_dev_initialize(dev
, &virtio_net
->virtio_dev
);
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
;
306 rc
= virtio_net_register_interrupt(dev
);
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
);
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",
328 vdev
->queues
= calloc(sizeof(virtq_t
), num_queues
);
334 rc
= virtio_virtq_setup(vdev
, RX_QUEUE_1
, RX_BUFFERS
);
337 rc
= virtio_virtq_setup(vdev
, TX_QUEUE_1
, TX_BUFFERS
);
340 rc
= virtio_virtq_setup(vdev
, CT_QUEUE_1
, CT_BUFFERS
);
347 rc
= virtio_net_setup_bufs(RX_BUFFERS
, RX_BUF_SIZE
, false,
348 virtio_net
->rx_buf
, virtio_net
->rx_buf_p
);
351 rc
= virtio_net_setup_bufs(TX_BUFFERS
, TX_BUF_SIZE
, true,
352 virtio_net
->tx_buf
, virtio_net
->tx_buf_p
);
355 rc
= virtio_net_setup_bufs(CT_BUFFERS
, CT_BUF_SIZE
, true,
356 virtio_net
->ct_buf
, virtio_net
->ct_buf_p
);
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
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
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
);
396 ddf_msg(LVL_NOTE
, "MAC address: " PRIMAC
, ARGSMAC(nic_addr
.address
));
401 rc
= hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev
),
404 ddf_msg(LVL_NOTE
, "Failed to enable interrupt");
408 ddf_msg(LVL_NOTE
, "Registered IRQ %d", virtio_net
->irq
);
411 virtio_device_setup_finalize(vdev
);
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
);
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");
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");
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
)
478 case NIC_MULTICAST_BLOCKED
:
479 nic_report_hw_filtering(nic
, -1, 0, -1);
481 case NIC_MULTICAST_LIST
:
482 nic_report_hw_filtering(nic
, -1, 0, -1);
484 case NIC_MULTICAST_PROMISC
:
485 nic_report_hw_filtering(nic
, -1, 0, -1);
493 static errno_t
virtio_net_on_broadcast_mode_change(nic_t
*nic
,
494 nic_broadcast_mode_t new_mode
)
497 case NIC_BROADCAST_BLOCKED
:
499 case NIC_BROADCAST_ACCEPTED
:
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
);
515 ddf_fun_t
*fun
= ddf_fun_create(dev
, fun_exposed
, "port0");
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
);
531 ddf_msg(LVL_ERROR
, "Failed binding device function");
535 rc
= ddf_fun_add_to_category(fun
, DEVICE_CATEGORY_NIC
);
537 ddf_msg(LVL_ERROR
, "Failed adding function to category");
541 ddf_msg(LVL_NOTE
, "The %s device has been successfully initialized.",
542 ddf_dev_get_name(dev
));
549 virtio_net_uninitialize(dev
);
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
);
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");
568 static errno_t
virtio_net_get_cable_state(ddf_fun_t
*fun
,
569 nic_cable_state_t
*state
)
571 *state
= NIC_CS_PLUGGED
;
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
)
579 *duplex
= NIC_CM_FULL_DUPLEX
;
580 *role
= NIC_ROLE_UNKNOWN
;
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
,
592 printf("%s: HelenOS virtio-net driver\n", NAME
);
594 if (nic_driver_init(NAME
) != EOK
)
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
);