diff options
Diffstat (limited to 'drivers/net')
-rw-r--r-- | drivers/net/Kconfig | 7 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/virtio.c | 243 |
3 files changed, 251 insertions, 0 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 397164f3f1..4947296f27 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -258,6 +258,13 @@ config DRIVER_NET_EFI_SNP bool "EFI SNP ethernet driver" depends on EFI_BOOTUP +config DRIVER_NET_VIRTIO + bool "virtio net driver" + depends on VIRTIO + help + This is the virtual net driver for virtio. It can be used with + QEMU based targets. + config DRIVER_NET_AG71XX bool "Atheros AG71xx ethernet driver" depends on MACH_MIPS_ATH79 diff --git a/drivers/net/Makefile b/drivers/net/Makefile index b1aa9571fc..1921d0d9f9 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -36,4 +36,5 @@ obj-$(CONFIG_DRIVER_NET_SMC91111) += smc91111.o obj-$(CONFIG_DRIVER_NET_TAP) += tap.o obj-$(CONFIG_DRIVER_NET_TSE) += altera_tse.o obj-$(CONFIG_DRIVER_NET_EFI_SNP) += efi-snp.o +obj-$(CONFIG_DRIVER_NET_VIRTIO) += virtio.o obj-$(CONFIG_DRIVER_NET_AG71XX) += ag71xx.o diff --git a/drivers/net/virtio.c b/drivers/net/virtio.c new file mode 100644 index 0000000000..ea4d552903 --- /dev/null +++ b/drivers/net/virtio.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi> + * Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com> + */ + +#include <common.h> +#include <driver.h> +#include <malloc.h> +#include <net.h> +#include <init.h> +#include <net.h> +#include <linux/virtio.h> +#include <linux/virtio_ring.h> +#include <uapi/linux/virtio_net.h> + +/* Amount of buffers to keep in the RX virtqueue */ +#define VIRTIO_NET_NUM_RX_BUFS 32 + +/* + * This value comes from the VirtIO spec: 1500 for maximum packet size, + * 14 for the Ethernet header, 12 for virtio_net_hdr. In total 1526 bytes. + */ +#define VIRTIO_NET_RX_BUF_SIZE 1526 + +struct virtio_net_priv { + union { + struct virtqueue *vqs[2]; + struct { + struct virtqueue *rx_vq; + struct virtqueue *tx_vq; + }; + }; + + char rx_buff[VIRTIO_NET_NUM_RX_BUFS][VIRTIO_NET_RX_BUF_SIZE]; + bool rx_running; + int net_hdr_len; + struct eth_device edev; + struct virtio_device *vdev; +}; + +static inline struct virtio_net_priv *to_priv(struct eth_device *edev) +{ + return container_of(edev, struct virtio_net_priv, edev); +} + +static int virtio_net_start(struct eth_device *edev) +{ + struct virtio_net_priv *priv = to_priv(edev); + struct virtio_sg sg; + struct virtio_sg *sgs[] = { &sg }; + int i; + + if (!priv->rx_running) { + /* receive buffer length is always 1526 */ + sg.length = VIRTIO_NET_RX_BUF_SIZE; + + /* setup the receive buffer address */ + for (i = 0; i < VIRTIO_NET_NUM_RX_BUFS; i++) { + sg.addr = priv->rx_buff[i]; + virtqueue_add(priv->rx_vq, sgs, 0, 1); + } + + virtqueue_kick(priv->rx_vq); + + /* setup the receive queue only once */ + priv->rx_running = true; + } + + return 0; +} + +static int virtio_net_send(struct eth_device *edev, void *packet, int length) +{ + struct virtio_net_priv *priv = to_priv(edev); + struct virtio_net_hdr_v1 hdr_v1; + struct virtio_net_hdr hdr; + struct virtio_sg hdr_sg; + struct virtio_sg data_sg = { packet, length }; + struct virtio_sg *sgs[] = { &hdr_sg, &data_sg }; + int ret; + + if (priv->net_hdr_len == sizeof(struct virtio_net_hdr)) + hdr_sg.addr = &hdr; + else + hdr_sg.addr = &hdr_v1; + + hdr_sg.length = priv->net_hdr_len; + memset(hdr_sg.addr, 0, priv->net_hdr_len); + + ret = virtqueue_add(priv->tx_vq, sgs, 2, 0); + if (ret) + return ret; + + virtqueue_kick(priv->tx_vq); + + while (1) { + if (virtqueue_get_buf(priv->tx_vq, NULL)) + break; + } + + return 0; +} + +static int virtio_net_recv(struct eth_device *edev) +{ + struct virtio_net_priv *priv = to_priv(edev); + struct virtio_sg sg; + struct virtio_sg *sgs[] = { &sg }; + unsigned int len; + void *buf; + + sg.addr = virtqueue_get_buf(priv->rx_vq, &len); + if (!sg.addr) + return -EAGAIN; + + sg.length = VIRTIO_NET_RX_BUF_SIZE; + + buf = sg.addr + priv->net_hdr_len; + len -= priv->net_hdr_len; + + net_receive(edev, buf, len); + + /* Put the buffer back to the rx ring */ + virtqueue_add(priv->rx_vq, sgs, 0, 1); + + return 0; +} + +static void virtio_net_stop(struct eth_device *dev) +{ + /* + * There is no way to stop the queue from running, unless we issue + * a reset to the virtio device, and re-do the queue initialization + * from the beginning. + */ +} + +static int virtio_net_write_hwaddr(struct eth_device *edev, const unsigned char *adr) +{ + struct virtio_net_priv *priv = to_priv(edev); + int i; + + /* + * v1.0 compliant device's MAC address is set through control channel, + * which we don't support for now. + */ + if (virtio_has_feature(priv->vdev, VIRTIO_F_VERSION_1)) + return -ENOSYS; + + for (i = 0; i < 6; i++) + virtio_cwrite8(priv->vdev, offsetof(struct virtio_net_config, mac) + i, adr[i]); + + return 0; +} + +static int virtio_net_read_rom_hwaddr(struct eth_device *edev, unsigned char *adr) +{ + struct virtio_net_priv *priv = to_priv(edev); + + virtio_cread_bytes(priv->vdev, offsetof(struct virtio_net_config, mac), adr, 6); + + return 0; +} + +static int virtio_net_probe(struct virtio_device *vdev) +{ + struct virtio_net_priv *priv; + struct eth_device *edev; + int ret; + + priv = xzalloc(sizeof(*priv)); + + vdev->priv = priv; + + /* + * For v1.0 compliant device, it always assumes the member + * 'num_buffers' exists in the struct virtio_net_hdr while + * the legacy driver only presented 'num_buffers' when + * VIRTIO_NET_F_MRG_RXBUF was negotiated. Without that feature + * the structure was 2 bytes shorter. + */ + if (virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) + priv->net_hdr_len = sizeof(struct virtio_net_hdr_v1); + else + priv->net_hdr_len = sizeof(struct virtio_net_hdr); + + ret = virtio_find_vqs(vdev, 2, priv->vqs); + if (ret < 0) + return ret; + + priv->vdev = vdev; + + edev = &priv->edev; + edev->priv = priv; + edev->parent = &vdev->dev; + + edev->open = virtio_net_start; + edev->send = virtio_net_send; + edev->recv = virtio_net_recv; + edev->halt = virtio_net_stop; + edev->get_ethaddr = virtio_net_read_rom_hwaddr; + edev->set_ethaddr = virtio_net_write_hwaddr; + + return eth_register(edev); +} + +static void virtio_net_remove(struct virtio_device *vdev) +{ + struct virtio_net_priv *priv = vdev->priv; + + vdev->config->reset(vdev); + eth_unregister(&priv->edev); + vdev->config->del_vqs(vdev); + + free(priv); +} + +/* + * For simplicity, the driver only negotiates the VIRTIO_NET_F_MAC feature. + * For the VIRTIO_NET_F_STATUS feature, we don't negotiate it, hence per spec + * we should assume the link is always active. + */ +static const u32 features[] = { + VIRTIO_NET_F_MAC +}; + +static const struct virtio_device_id id_table[] = { + { VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static struct virtio_driver virtio_net = { + .driver.name = "virtio_net", + .id_table = id_table, + .probe = virtio_net_probe, + .remove = virtio_net_remove, + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .feature_table_legacy = features, + .feature_table_size_legacy = ARRAY_SIZE(features), +}; +device_virtio_driver(virtio_net); |