/* * Copyright (c) 2006-2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2021-11-11 GuEe-GUI the first version */ #include #include #include #include #ifdef RT_USING_VIRTIO_NET #include static rt_err_t virtio_net_tx(rt_device_t dev, struct pbuf *p) { rt_uint16_t id; struct virtio_net_device *virtio_net_dev = (struct virtio_net_device *)dev; struct virtio_device *virtio_dev = &virtio_net_dev->virtio_dev; struct virtq *queue_tx = &virtio_dev->queues[VIRTIO_NET_QUEUE_TX]; id = (queue_tx->avail->idx * 2) % queue_tx->num; virtio_net_dev->info[id].hdr.flags = 0; virtio_net_dev->info[id].hdr.gso_type = 0; virtio_net_dev->info[id].hdr.hdr_len = 0; virtio_net_dev->info[id].hdr.gso_size = 0; virtio_net_dev->info[id].hdr.csum_start = 0; virtio_net_dev->info[id].hdr.csum_offset = 0; virtio_net_dev->info[id].hdr.num_buffers = 0; pbuf_copy_partial(p, virtio_net_dev->info[id].rx_buffer, p->tot_len, 0); virtio_free_desc(virtio_dev, VIRTIO_NET_QUEUE_TX, id); virtio_free_desc(virtio_dev, VIRTIO_NET_QUEUE_TX, id + 1); virtio_fill_desc(virtio_dev, VIRTIO_NET_QUEUE_TX, id, VIRTIO_VA2PA(&virtio_net_dev->info[id].hdr), VIRTIO_NET_HDR_SIZE, VIRTQ_DESC_F_NEXT, id + 1); virtio_fill_desc(virtio_dev, VIRTIO_NET_QUEUE_TX, id + 1, VIRTIO_VA2PA(virtio_net_dev->info[id].rx_buffer), p->tot_len, 0, 0); virtio_submit_chain(virtio_dev, VIRTIO_NET_QUEUE_TX, id); virtio_queue_notify(virtio_dev, VIRTIO_NET_QUEUE_TX); virtio_alloc_desc(virtio_dev, VIRTIO_NET_QUEUE_TX); virtio_alloc_desc(virtio_dev, VIRTIO_NET_QUEUE_TX); return RT_EOK; } static struct pbuf *virtio_net_rx(rt_device_t dev) { rt_uint16_t id; rt_uint32_t len; struct pbuf *p = RT_NULL; struct virtio_net_device *virtio_net_dev = (struct virtio_net_device *)dev; struct virtio_device *virtio_dev = &virtio_net_dev->virtio_dev; struct virtq *queue_rx = &virtio_dev->queues[VIRTIO_NET_QUEUE_RX]; if (queue_rx->used_idx != queue_rx->used->idx) { id = (queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].id + 1) % queue_rx->num; len = queue_rx->used->ring[queue_rx->used_idx % queue_rx->num].len - VIRTIO_NET_HDR_SIZE; if (len > VIRTIO_NET_PAYLOAD_MAX_SIZE) { rt_kprintf("%s: Receive buffer's size = %u is too big!\n", virtio_net_dev->parent.parent.parent.name, len); len = VIRTIO_NET_PAYLOAD_MAX_SIZE; } p = pbuf_alloc(PBUF_RAW, len, PBUF_RAM); if (p != RT_NULL) { rt_memcpy(p->payload, (void *)queue_rx->desc[id].addr - PV_OFFSET, len); queue_rx->used_idx++; virtio_submit_chain(virtio_dev, VIRTIO_NET_QUEUE_RX, id - 1); virtio_queue_notify(virtio_dev, VIRTIO_NET_QUEUE_RX); } } return p; } static rt_err_t virtio_net_init(rt_device_t dev) { int i; rt_uint16_t idx[VIRTIO_NET_RTX_QUEUE_SIZE]; struct virtio_net_device *virtio_net_dev = (struct virtio_net_device *)dev; struct virtio_device *virtio_dev = &virtio_net_dev->virtio_dev; struct virtq *queue_rx, *queue_tx; queue_rx = &virtio_dev->queues[VIRTIO_NET_QUEUE_RX]; queue_tx = &virtio_dev->queues[VIRTIO_NET_QUEUE_TX]; virtio_alloc_desc_chain(virtio_dev, VIRTIO_NET_QUEUE_RX, queue_rx->num, idx); virtio_alloc_desc_chain(virtio_dev, VIRTIO_NET_QUEUE_TX, queue_tx->num, idx); for (i = 0; i < queue_rx->num; ++i) { rt_uint16_t id = (i * 2) % queue_rx->num; void *addr = virtio_net_dev->info[i].tx_buffer; /* Descriptor for net_hdr */ virtio_fill_desc(virtio_dev, VIRTIO_NET_QUEUE_RX, id, VIRTIO_VA2PA(addr), VIRTIO_NET_HDR_SIZE, VIRTQ_DESC_F_NEXT | VIRTQ_DESC_F_WRITE, id + 1); /* Descriptor for data */ virtio_fill_desc(virtio_dev, VIRTIO_NET_QUEUE_RX, id + 1, VIRTIO_VA2PA(addr) + VIRTIO_NET_HDR_SIZE, VIRTIO_NET_MSS, VIRTQ_DESC_F_WRITE, 0); queue_rx->avail->ring[i] = id; } rt_hw_dsb(); queue_rx->avail->flags = 0; queue_rx->avail->idx = queue_rx->num; queue_rx->used_idx = queue_rx->used->idx; queue_tx->avail->flags = VIRTQ_AVAIL_F_NO_INTERRUPT; queue_tx->avail->idx = 0; virtio_queue_notify(virtio_dev, VIRTIO_NET_QUEUE_RX); return eth_device_linkchange(&virtio_net_dev->parent, RT_TRUE); } static rt_err_t virtio_net_control(rt_device_t dev, int cmd, void *args) { rt_err_t status = RT_EOK; struct virtio_net_device *virtio_net_dev = (struct virtio_net_device *)dev; switch (cmd) { case NIOCTL_GADDR: if (args == RT_NULL) { status = -RT_ERROR; break; } rt_memcpy(args, virtio_net_dev->config->mac, sizeof(virtio_net_dev->config->mac)); break; default: status = -RT_EINVAL; break; } return status; } #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops virtio_net_ops = { virtio_net_init, RT_NULL, RT_NULL, RT_NULL, RT_NULL, virtio_net_control }; #endif static void virtio_net_isr(int irqno, void *param) { struct virtio_net_device *virtio_net_dev = (struct virtio_net_device *)param; struct virtio_device *virtio_dev = &virtio_net_dev->virtio_dev; struct virtq *queue_rx = &virtio_dev->queues[VIRTIO_NET_QUEUE_RX]; virtio_interrupt_ack(virtio_dev); rt_hw_dsb(); if (queue_rx->used_idx != queue_rx->used->idx) { rt_hw_dsb(); eth_device_ready(&virtio_net_dev->parent); } } rt_err_t rt_virtio_net_init(rt_ubase_t *mmio_base, rt_uint32_t irq) { static int dev_no = 0; char dev_name[RT_NAME_MAX]; struct virtio_device *virtio_dev; struct virtio_net_device *virtio_net_dev; virtio_net_dev = rt_malloc(sizeof(struct virtio_net_device)); if (virtio_net_dev == RT_NULL) { goto _alloc_fail; } virtio_dev = &virtio_net_dev->virtio_dev; virtio_dev->irq = irq; virtio_dev->mmio_base = mmio_base; virtio_net_dev->config = (struct virtio_net_config *)virtio_dev->mmio_config->config; virtio_reset_device(virtio_dev); virtio_status_acknowledge_driver(virtio_dev); virtio_dev->mmio_config->driver_features = virtio_dev->mmio_config->device_features & ~( (1 << VIRTIO_NET_F_CTRL_VQ) | (1 << VIRTIO_F_RING_EVENT_IDX)); virtio_status_driver_ok(virtio_dev); if (virtio_queues_alloc(virtio_dev, 2) != RT_EOK) { goto _alloc_fail; } if (virtio_queue_init(virtio_dev, VIRTIO_NET_QUEUE_RX, VIRTIO_NET_RTX_QUEUE_SIZE) != RT_EOK) { goto _alloc_fail; } if (virtio_queue_init(virtio_dev, VIRTIO_NET_QUEUE_TX, VIRTIO_NET_RTX_QUEUE_SIZE) != RT_EOK) { virtio_queue_destroy(virtio_dev, VIRTIO_NET_QUEUE_RX); goto _alloc_fail; } virtio_net_dev->parent.parent.type = RT_Device_Class_NetIf; #ifdef RT_USING_DEVICE_OPS virtio_net_dev->parent.parent.ops = &virtio_net_ops; #else virtio_net_dev->parent.parent.init = virtio_net_init; virtio_net_dev->parent.parent.open = RT_NULL; virtio_net_dev->parent.parent.close = RT_NULL; virtio_net_dev->parent.parent.read = RT_NULL; virtio_net_dev->parent.parent.write = RT_NULL; virtio_net_dev->parent.parent.control = virtio_net_control; #endif virtio_net_dev->parent.eth_tx = virtio_net_tx; virtio_net_dev->parent.eth_rx = virtio_net_rx; rt_snprintf(dev_name, RT_NAME_MAX, "virtio-net%d", dev_no++); rt_hw_interrupt_install(irq, virtio_net_isr, virtio_net_dev, dev_name); rt_hw_interrupt_umask(irq); return eth_device_init(&virtio_net_dev->parent, dev_name); _alloc_fail: if (virtio_net_dev != RT_NULL) { virtio_queues_free(virtio_dev); rt_free(virtio_net_dev); } return -RT_ENOMEM; } #endif /* RT_USING_VIRTIO_NET */