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>
43 #include <virtio-pci.h>
45 #define NAME "virtio-net"
47 #define VIRTIO_NET_NUM_QUEUES 3
53 #define BUFFER_SIZE 2048
54 #define RX_BUF_SIZE BUFFER_SIZE
55 #define TX_BUF_SIZE BUFFER_SIZE
56 #define CT_BUF_SIZE BUFFER_SIZE
58 static ddf_dev_ops_t virtio_net_dev_ops
;
60 static errno_t
virtio_net_dev_add(ddf_dev_t
*dev
);
62 static driver_ops_t virtio_net_driver_ops
= {
63 .dev_add
= virtio_net_dev_add
66 static driver_t virtio_net_driver
= {
68 .driver_ops
= &virtio_net_driver_ops
71 static errno_t
virtio_net_setup_bufs(unsigned int buffers
, size_t size
,
72 bool write
, void *buf
[], uintptr_t buf_p
[])
75 * Allocate all buffers at once in one large chung.
77 void *virt
= AS_AREA_ANY
;
79 errno_t rc
= dmamem_map_anonymous(buffers
* size
, 0,
80 write
? AS_AREA_WRITE
: AS_AREA_READ
, 0, &phys
, &virt
);
84 ddf_msg(LVL_NOTE
, "DMA buffers: %p-%p", virt
, virt
+ buffers
* size
);
87 * Calculate addresses of the individual buffers for easy access.
89 for (unsigned i
= 0; i
< buffers
; i
++) {
90 buf
[i
] = virt
+ i
* size
;
91 buf_p
[i
] = phys
+ i
* size
;
97 static void virtio_net_teardown_bufs(void *buf
[])
100 dmamem_unmap_anonymous(buf
[0]);
105 static void virtio_net_create_buf_free_list(virtio_dev_t
*vdev
, uint16_t num
,
106 uint16_t size
, uint16_t *head
)
108 for (unsigned i
= 0; i
< size
; i
++) {
109 virtio_virtq_desc_set(vdev
, num
, i
, 0, 0,
110 VIRTQ_DESC_F_NEXT
, (i
+ 1 == size
) ? -1U : i
+ 1);
115 static uint16_t virtio_net_alloc_buf(virtio_dev_t
*vdev
, uint16_t num
,
118 uint16_t descno
= *head
;
119 if (descno
!= (uint16_t) -1U)
120 *head
= virtio_virtq_desc_get_next(vdev
, num
, descno
);
124 static void virtio_net_free_buf(virtio_dev_t
*vdev
, uint16_t num
,
125 uint16_t *head
, uint16_t descno
)
127 virtio_virtq_desc_set(vdev
, num
, descno
, 0, 0, VIRTQ_DESC_F_NEXT
,
132 static void virtio_net_irq_handler(ipc_call_t
*icall
, ddf_dev_t
*dev
)
134 nic_t
*nic
= ddf_dev_data_get(dev
);
135 virtio_net_t
*virtio_net
= nic_get_specific(nic
);
136 virtio_dev_t
*vdev
= &virtio_net
->virtio_dev
;
140 while (virtio_virtq_consume_used(vdev
, RX_QUEUE_1
, &descno
, &len
)) {
141 virtio_net_hdr_t
*hdr
=
142 (virtio_net_hdr_t
*) virtio_net
->rx_buf
[descno
];
143 if (len
<= sizeof(*hdr
)) {
145 "RX data length too short, packet dropped");
146 virtio_virtq_produce_available(vdev
, RX_QUEUE_1
,
151 nic_frame_t
*frame
= nic_alloc_frame(nic
, len
- sizeof(*hdr
));
153 memcpy(frame
->data
, &hdr
[1], len
- sizeof(*hdr
));
154 nic_received_frame(nic
, frame
);
157 "Cannot allocate RX frame, packet dropped");
160 virtio_virtq_produce_available(vdev
, RX_QUEUE_1
, descno
);
163 while (virtio_virtq_consume_used(vdev
, TX_QUEUE_1
, &descno
, &len
)) {
164 virtio_net_free_buf(vdev
, TX_QUEUE_1
, &virtio_net
->tx_free_head
,
167 while (virtio_virtq_consume_used(vdev
, CT_QUEUE_1
, &descno
, &len
)) {
168 virtio_net_free_buf(vdev
, CT_QUEUE_1
, &virtio_net
->ct_free_head
,
173 static errno_t
virtio_net_register_interrupt(ddf_dev_t
*dev
)
175 nic_t
*nic
= ddf_dev_data_get(dev
);
176 virtio_net_t
*virtio_net
= nic_get_specific(nic
);
177 virtio_dev_t
*vdev
= &virtio_net
->virtio_dev
;
179 hw_res_list_parsed_t res
;
180 hw_res_list_parsed_init(&res
);
182 errno_t rc
= nic_get_resources(nic
, &res
);
186 if (res
.irqs
.count
< 1) {
187 hw_res_list_parsed_clean(&res
);
192 virtio_net
->irq
= res
.irqs
.irqs
[0];
193 hw_res_list_parsed_clean(&res
);
195 irq_pio_range_t pio_ranges
[] = {
197 .base
= vdev
->isr_phys
,
198 .size
= sizeof(vdev
->isr_phys
),
202 irq_cmd_t irq_commands
[] = {
204 .cmd
= CMD_PIO_READ_8
,
205 .addr
= (void *) vdev
->isr_phys
,
209 .cmd
= CMD_PREDICATE
,
218 irq_code_t irq_code
= {
219 .rangecount
= sizeof(pio_ranges
) / sizeof(irq_pio_range_t
),
220 .ranges
= pio_ranges
,
221 .cmdcount
= sizeof(irq_commands
) / sizeof(irq_cmd_t
),
225 return register_interrupt_handler(dev
, virtio_net
->irq
,
226 virtio_net_irq_handler
, &irq_code
, &virtio_net
->irq_handle
);
229 static errno_t
virtio_net_initialize(ddf_dev_t
*dev
)
231 nic_t
*nic
= nic_create_and_bind(dev
);
235 virtio_net_t
*virtio_net
= calloc(1, sizeof(virtio_net_t
));
237 nic_unbind_and_destroy(dev
);
241 nic_set_specific(nic
, virtio_net
);
243 errno_t rc
= virtio_pci_dev_initialize(dev
, &virtio_net
->virtio_dev
);
247 virtio_dev_t
*vdev
= &virtio_net
->virtio_dev
;
248 virtio_pci_common_cfg_t
*cfg
= virtio_net
->virtio_dev
.common_cfg
;
249 virtio_net_cfg_t
*netcfg
= virtio_net
->virtio_dev
.device_cfg
;
254 rc
= virtio_net_register_interrupt(dev
);
258 /* Reset the device and negotiate the feature bits */
259 rc
= virtio_device_setup_start(vdev
,
260 VIRTIO_NET_F_MAC
| VIRTIO_NET_F_CTRL_VQ
);
264 /* Perform device-specific setup */
267 * Discover and configure the virtqueues
269 uint16_t num_queues
= pio_read_le16(&cfg
->num_queues
);
270 if (num_queues
!= VIRTIO_NET_NUM_QUEUES
) {
271 ddf_msg(LVL_NOTE
, "Unsupported number of virtqueues: %u",
276 vdev
->queues
= calloc(sizeof(virtq_t
), num_queues
);
282 rc
= virtio_virtq_setup(vdev
, RX_QUEUE_1
, RX_BUFFERS
);
285 rc
= virtio_virtq_setup(vdev
, TX_QUEUE_1
, TX_BUFFERS
);
288 rc
= virtio_virtq_setup(vdev
, CT_QUEUE_1
, CT_BUFFERS
);
295 rc
= virtio_net_setup_bufs(RX_BUFFERS
, RX_BUF_SIZE
, false,
296 virtio_net
->rx_buf
, virtio_net
->rx_buf_p
);
299 rc
= virtio_net_setup_bufs(TX_BUFFERS
, TX_BUF_SIZE
, true,
300 virtio_net
->tx_buf
, virtio_net
->tx_buf_p
);
303 rc
= virtio_net_setup_bufs(CT_BUFFERS
, CT_BUF_SIZE
, true,
304 virtio_net
->ct_buf
, virtio_net
->ct_buf_p
);
309 * Give all RX buffers to the NIC
311 for (unsigned i
= 0; i
< RX_BUFFERS
; i
++) {
313 * Associtate the buffer with the descriptor, set length and
316 virtio_virtq_desc_set(vdev
, RX_QUEUE_1
, i
,
317 virtio_net
->rx_buf_p
[i
], RX_BUF_SIZE
, VIRTQ_DESC_F_WRITE
,
320 * Put the set descriptor into the available ring of the RX
323 virtio_virtq_produce_available(vdev
, RX_QUEUE_1
, i
);
327 * Put all TX and CT buffers on a free list
329 virtio_net_create_buf_free_list(vdev
, TX_QUEUE_1
, TX_BUFFERS
,
330 &virtio_net
->tx_free_head
);
331 virtio_net_create_buf_free_list(vdev
, CT_QUEUE_1
, CT_BUFFERS
,
332 &virtio_net
->ct_free_head
);
335 * Read the MAC address
337 nic_address_t nic_addr
;
338 for (unsigned i
= 0; i
< 6; i
++)
339 nic_addr
.address
[i
] = pio_read_8(&netcfg
->mac
[i
]);
340 rc
= nic_report_address(nic
, &nic_addr
);
344 ddf_msg(LVL_NOTE
, "MAC address: %02x:%02x:%02x:%02x:%02x:%02x",
345 nic_addr
.address
[0], nic_addr
.address
[1], nic_addr
.address
[2],
346 nic_addr
.address
[3], nic_addr
.address
[4], nic_addr
.address
[5]);
351 rc
= hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev
),
354 ddf_msg(LVL_NOTE
, "Failed to enable interrupt");
358 ddf_msg(LVL_NOTE
, "Registered IRQ %d", virtio_net
->irq
);
361 virtio_device_setup_finalize(vdev
);
366 virtio_net_teardown_bufs(virtio_net
->rx_buf
);
367 virtio_net_teardown_bufs(virtio_net
->tx_buf
);
368 virtio_net_teardown_bufs(virtio_net
->ct_buf
);
370 virtio_device_setup_fail(vdev
);
371 virtio_pci_dev_cleanup(vdev
);
375 static void virtio_net_uninitialize(ddf_dev_t
*dev
)
377 nic_t
*nic
= ddf_dev_data_get(dev
);
378 virtio_net_t
*virtio_net
= (virtio_net_t
*) nic_get_specific(nic
);
380 virtio_net_teardown_bufs(virtio_net
->rx_buf
);
381 virtio_net_teardown_bufs(virtio_net
->tx_buf
);
382 virtio_net_teardown_bufs(virtio_net
->ct_buf
);
384 virtio_device_setup_fail(&virtio_net
->virtio_dev
);
385 virtio_pci_dev_cleanup(&virtio_net
->virtio_dev
);
388 static void virtio_net_send(nic_t
*nic
, void *data
, size_t size
)
390 virtio_net_t
*virtio_net
= nic_get_specific(nic
);
391 virtio_dev_t
*vdev
= &virtio_net
->virtio_dev
;
393 if (size
> sizeof(virtio_net
) + TX_BUF_SIZE
) {
394 ddf_msg(LVL_WARN
, "TX data too big, frame dropped");
398 uint16_t descno
= virtio_net_alloc_buf(vdev
, TX_QUEUE_1
,
399 &virtio_net
->tx_free_head
);
400 if (descno
== (uint16_t) -1U) {
401 ddf_msg(LVL_WARN
, "No TX buffers available, frame dropped");
404 assert(descno
< TX_BUFFERS
);
406 /* Setup the packed header */
407 virtio_net_hdr_t
*hdr
= (virtio_net_hdr_t
*) virtio_net
->tx_buf
[descno
];
408 memset(hdr
, 0, sizeof(virtio_net_hdr_t
));
409 hdr
->gso_type
= VIRTIO_NET_HDR_GSO_NONE
;
411 /* Copy packet data into the buffer just past the header */
412 memcpy(&hdr
[1], data
, size
);
415 * Set the descriptor, put it into the virtqueue and notify the device
417 virtio_virtq_desc_set(vdev
, TX_QUEUE_1
, descno
,
418 virtio_net
->tx_buf_p
[descno
], TX_BUF_SIZE
, 0, 0);
419 virtio_virtq_produce_available(vdev
, TX_QUEUE_1
, descno
);
422 static errno_t
virtio_net_on_broadcast_mode_change(nic_t
*nic
,
423 nic_broadcast_mode_t new_mode
)
426 case NIC_BROADCAST_BLOCKED
:
428 case NIC_BROADCAST_ACCEPTED
:
435 static errno_t
virtio_net_dev_add(ddf_dev_t
*dev
)
437 ddf_msg(LVL_NOTE
, "%s %s (handle = %zu)", __func__
,
438 ddf_dev_get_name(dev
), ddf_dev_get_handle(dev
));
440 errno_t rc
= virtio_net_initialize(dev
);
444 ddf_fun_t
*fun
= ddf_fun_create(dev
, fun_exposed
, "port0");
449 nic_t
*nic
= ddf_dev_data_get(dev
);
450 nic_set_ddf_fun(nic
, fun
);
451 ddf_fun_set_ops(fun
, &virtio_net_dev_ops
);
453 nic_set_send_frame_handler(nic
, virtio_net_send
);
454 nic_set_filtering_change_handlers(nic
, NULL
, NULL
,
455 virtio_net_on_broadcast_mode_change
, NULL
, NULL
);
457 rc
= ddf_fun_bind(fun
);
459 ddf_msg(LVL_ERROR
, "Failed binding device function");
463 rc
= ddf_fun_add_to_category(fun
, DEVICE_CATEGORY_NIC
);
465 ddf_msg(LVL_ERROR
, "Failed adding function to category");
469 ddf_msg(LVL_NOTE
, "The %s device has been successfully initialized.",
470 ddf_dev_get_name(dev
));
477 virtio_net_uninitialize(dev
);
482 static errno_t
virtio_net_get_device_info(ddf_fun_t
*fun
,
483 nic_device_info_t
*info
)
485 nic_t
*nic
= nic_get_from_ddf_fun(fun
);
489 str_cpy(info
->vendor_name
, sizeof(info
->vendor_name
), "Red Hat, Inc.");
490 str_cpy(info
->model_name
, sizeof(info
->model_name
),
491 "Virtio network device");
496 static errno_t
virtio_net_get_cable_state(ddf_fun_t
*fun
,
497 nic_cable_state_t
*state
)
499 *state
= NIC_CS_PLUGGED
;
503 static errno_t
virtio_net_get_operation_mode(ddf_fun_t
*fun
, int *speed
,
504 nic_channel_mode_t
*duplex
, nic_role_t
*role
)
507 *duplex
= NIC_CM_FULL_DUPLEX
;
508 *role
= NIC_ROLE_UNKNOWN
;
512 static nic_iface_t virtio_net_nic_iface
= {
513 .get_device_info
= virtio_net_get_device_info
,
514 .get_cable_state
= virtio_net_get_cable_state
,
515 .get_operation_mode
= virtio_net_get_operation_mode
,
520 printf("%s: HelenOS virtio-net driver\n", NAME
);
522 if (nic_driver_init(NAME
) != EOK
)
525 nic_driver_implement(&virtio_net_driver_ops
, &virtio_net_dev_ops
,
526 &virtio_net_nic_iface
);
528 (void) ddf_log_init(NAME
);
529 return ddf_driver_main(&virtio_net_driver
);