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 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
;
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
)) {
139 "RX data length too short, packet dropped");
140 virtio_virtq_produce_available(vdev
, RX_QUEUE_1
,
145 nic_frame_t
*frame
= nic_alloc_frame(nic
, len
- sizeof(*hdr
));
147 memcpy(frame
->data
, &hdr
[1], len
- sizeof(*hdr
));
148 nic_received_frame(nic
, frame
);
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
,
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
,
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
);
180 if (res
.irqs
.count
< 1) {
181 hw_res_list_parsed_clean(&res
);
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
,
203 .cmd
= CMD_PREDICATE
,
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
),
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
);
229 virtio_net_t
*virtio_net
= calloc(1, sizeof(virtio_net_t
));
231 nic_unbind_and_destroy(dev
);
235 nic_set_specific(nic
, virtio_net
);
237 errno_t rc
= virtio_pci_dev_initialize(dev
, &virtio_net
->virtio_dev
);
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
;
248 rc
= virtio_net_register_interrupt(dev
);
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
);
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",
270 vdev
->queues
= calloc(sizeof(virtq_t
), num_queues
);
276 rc
= virtio_virtq_setup(vdev
, RX_QUEUE_1
, RX_BUFFERS
);
279 rc
= virtio_virtq_setup(vdev
, TX_QUEUE_1
, TX_BUFFERS
);
282 rc
= virtio_virtq_setup(vdev
, CT_QUEUE_1
, CT_BUFFERS
);
289 rc
= virtio_net_setup_bufs(RX_BUFFERS
, RX_BUF_SIZE
, false,
290 virtio_net
->rx_buf
, virtio_net
->rx_buf_p
);
293 rc
= virtio_net_setup_bufs(TX_BUFFERS
, TX_BUF_SIZE
, true,
294 virtio_net
->tx_buf
, virtio_net
->tx_buf_p
);
297 rc
= virtio_net_setup_bufs(CT_BUFFERS
, CT_BUF_SIZE
, true,
298 virtio_net
->ct_buf
, virtio_net
->ct_buf_p
);
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
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
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
);
338 ddf_msg(LVL_NOTE
, "MAC address: " PRIMAC
, ARGSMAC(nic_addr
.address
));
343 rc
= hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev
),
346 ddf_msg(LVL_NOTE
, "Failed to enable interrupt");
350 ddf_msg(LVL_NOTE
, "Registered IRQ %d", virtio_net
->irq
);
353 virtio_device_setup_finalize(vdev
);
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
);
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");
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");
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
)
420 case NIC_MULTICAST_BLOCKED
:
421 nic_report_hw_filtering(nic
, -1, 0, -1);
423 case NIC_MULTICAST_LIST
:
424 nic_report_hw_filtering(nic
, -1, 0, -1);
426 case NIC_MULTICAST_PROMISC
:
427 nic_report_hw_filtering(nic
, -1, 0, -1);
435 static errno_t
virtio_net_on_broadcast_mode_change(nic_t
*nic
,
436 nic_broadcast_mode_t new_mode
)
439 case NIC_BROADCAST_BLOCKED
:
441 case NIC_BROADCAST_ACCEPTED
:
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
);
457 ddf_fun_t
*fun
= ddf_fun_create(dev
, fun_exposed
, "port0");
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
);
473 ddf_msg(LVL_ERROR
, "Failed binding device function");
477 rc
= ddf_fun_add_to_category(fun
, DEVICE_CATEGORY_NIC
);
479 ddf_msg(LVL_ERROR
, "Failed adding function to category");
483 ddf_msg(LVL_NOTE
, "The %s device has been successfully initialized.",
484 ddf_dev_get_name(dev
));
491 virtio_net_uninitialize(dev
);
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
);
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");
510 static errno_t
virtio_net_get_cable_state(ddf_fun_t
*fun
,
511 nic_cable_state_t
*state
)
513 *state
= NIC_CS_PLUGGED
;
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
)
521 *duplex
= NIC_CM_FULL_DUPLEX
;
522 *role
= NIC_ROLE_UNKNOWN
;
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
,
534 printf("%s: HelenOS virtio-net driver\n", NAME
);
536 if (nic_driver_init(NAME
) != EOK
)
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
);