From f371ad3064699f460711523db5e8177a0fc7b22e Mon Sep 17 00:00:00 2001 From: Tuomas Tynkkynen Date: Mon, 15 Oct 2018 02:21:03 -0700 Subject: [PATCH] virtio: Add net driver support This adds virtio net device driver support. Signed-off-by: Tuomas Tynkkynen Signed-off-by: Bin Meng Reviewed-by: Simon Glass --- drivers/virtio/Kconfig | 7 + drivers/virtio/Makefile | 1 + drivers/virtio/virtio_net.c | 218 +++++++++++++++++++++++++++++ drivers/virtio/virtio_net.h | 268 ++++++++++++++++++++++++++++++++++++ 4 files changed, 494 insertions(+) create mode 100644 drivers/virtio/virtio_net.c create mode 100644 drivers/virtio/virtio_net.h diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 4f9a11b6ef..e20dd69395 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -29,4 +29,11 @@ config VIRTIO_MMIO This driver provides support for memory mapped virtio platform device driver. +config VIRTIO_NET + bool "virtio net driver" + depends on VIRTIO + help + This is the virtual net driver for virtio. It can be used with + QEMU based targets. + endmenu diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 2e487854a2..b7764f161e 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -5,3 +5,4 @@ obj-y += virtio-uclass.o virtio_ring.o obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o +obj-$(CONFIG_VIRTIO_NET) += virtio_net.o diff --git a/drivers/virtio/virtio_net.c b/drivers/virtio/virtio_net.c new file mode 100644 index 0000000000..5bb6a9fcc9 --- /dev/null +++ b/drivers/virtio/virtio_net.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2018, Tuomas Tynkkynen + * Copyright (C) 2018, Bin Meng + */ + +#include +#include +#include +#include +#include +#include +#include "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; +}; + +/* + * 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 feature[] = { + VIRTIO_NET_F_MAC +}; + +static const u32 feature_legacy[] = { + VIRTIO_NET_F_MAC +}; + +static int virtio_net_start(struct udevice *dev) +{ + struct virtio_net_priv *priv = dev_get_priv(dev); + 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 udevice *dev, void *packet, int length) +{ + struct virtio_net_priv *priv = dev_get_priv(dev); + struct virtio_net_hdr hdr; + struct virtio_sg hdr_sg = { &hdr, sizeof(hdr) }; + struct virtio_sg data_sg = { packet, length }; + struct virtio_sg *sgs[] = { &hdr_sg, &data_sg }; + int ret; + + memset(&hdr, 0, sizeof(struct virtio_net_hdr)); + + 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 udevice *dev, int flags, uchar **packetp) +{ + struct virtio_net_priv *priv = dev_get_priv(dev); + unsigned int len; + void *buf; + + buf = virtqueue_get_buf(priv->rx_vq, &len); + if (!buf) + return -EAGAIN; + + *packetp = buf + sizeof(struct virtio_net_hdr); + return len - sizeof(struct virtio_net_hdr); +} + +static int virtio_net_free_pkt(struct udevice *dev, uchar *packet, int length) +{ + struct virtio_net_priv *priv = dev_get_priv(dev); + void *buf = packet - sizeof(struct virtio_net_hdr); + struct virtio_sg sg = { buf, VIRTIO_NET_RX_BUF_SIZE }; + struct virtio_sg *sgs[] = { &sg }; + + /* Put the buffer back to the rx ring */ + virtqueue_add(priv->rx_vq, sgs, 0, 1); + + return 0; +} + +static void virtio_net_stop(struct udevice *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 udevice *dev) +{ + struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent); + struct eth_pdata *pdata = dev_get_platdata(dev); + int i; + + /* + * v1.0 compliant device's MAC address is set through control channel, + * which we don't support for now. + */ + if (!uc_priv->legacy) + return -ENOSYS; + + for (i = 0; i < sizeof(pdata->enetaddr); i++) { + virtio_cwrite8(dev, + offsetof(struct virtio_net_config, mac) + i, + pdata->enetaddr[i]); + } + + return 0; +} + +static int virtio_net_read_rom_hwaddr(struct udevice *dev) +{ + struct eth_pdata *pdata = dev_get_platdata(dev); + + if (!pdata) + return -ENOSYS; + + if (virtio_has_feature(dev, VIRTIO_NET_F_MAC)) { + virtio_cread_bytes(dev, + offsetof(struct virtio_net_config, mac), + pdata->enetaddr, sizeof(pdata->enetaddr)); + } + + return 0; +} + +static int virtio_net_bind(struct udevice *dev) +{ + struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent); + + /* Indicate what driver features we support */ + virtio_driver_features_init(uc_priv, feature, ARRAY_SIZE(feature), + feature_legacy, ARRAY_SIZE(feature_legacy)); + + return 0; +} + +static int virtio_net_probe(struct udevice *dev) +{ + struct virtio_net_priv *priv = dev_get_priv(dev); + int ret; + + ret = virtio_find_vqs(dev, 2, priv->vqs); + if (ret < 0) + return ret; + + return 0; +} + +static const struct eth_ops virtio_net_ops = { + .start = virtio_net_start, + .send = virtio_net_send, + .recv = virtio_net_recv, + .free_pkt = virtio_net_free_pkt, + .stop = virtio_net_stop, + .write_hwaddr = virtio_net_write_hwaddr, + .read_rom_hwaddr = virtio_net_read_rom_hwaddr, +}; + +U_BOOT_DRIVER(virtio_net) = { + .name = VIRTIO_NET_DRV_NAME, + .id = UCLASS_ETH, + .bind = virtio_net_bind, + .probe = virtio_net_probe, + .remove = virtio_reset, + .ops = &virtio_net_ops, + .priv_auto_alloc_size = sizeof(struct virtio_net_priv), + .platdata_auto_alloc_size = sizeof(struct eth_pdata), + .flags = DM_FLAG_ACTIVE_DMA, +}; diff --git a/drivers/virtio/virtio_net.h b/drivers/virtio/virtio_net.h new file mode 100644 index 0000000000..c92bae5269 --- /dev/null +++ b/drivers/virtio/virtio_net.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* + * Copyright (C) 2018, Tuomas Tynkkynen + * Copyright (C) 2018, Bin Meng + * + * From Linux kernel include/uapi/linux/virtio_net.h + */ + +#ifndef _LINUX_VIRTIO_NET_H +#define _LINUX_VIRTIO_NET_H + +/* TODO: needs to be removed! */ +#define ETH_ALEN 6 + +/* The feature bitmap for virtio net */ + +/* Host handles pkts w/ partial csum */ +#define VIRTIO_NET_F_CSUM 0 +/* Guest handles pkts w/ partial csum */ +#define VIRTIO_NET_F_GUEST_CSUM 1 +/* Dynamic offload configuration */ +#define VIRTIO_NET_F_CTRL_GUEST_OFFLOADS 2 +/* Initial MTU advice */ +#define VIRTIO_NET_F_MTU 3 +/* Host has given MAC address */ +#define VIRTIO_NET_F_MAC 5 +/* Guest can handle TSOv4 in */ +#define VIRTIO_NET_F_GUEST_TSO4 7 +/* Guest can handle TSOv6 in */ +#define VIRTIO_NET_F_GUEST_TSO6 8 +/* Guest can handle TSO[6] w/ ECN in */ +#define VIRTIO_NET_F_GUEST_ECN 9 +/* Guest can handle UFO in */ +#define VIRTIO_NET_F_GUEST_UFO 10 +/* Host can handle TSOv4 in */ +#define VIRTIO_NET_F_HOST_TSO4 11 +/* Host can handle TSOv6 in */ +#define VIRTIO_NET_F_HOST_TSO6 12 +/* Host can handle TSO[6] w/ ECN in */ +#define VIRTIO_NET_F_HOST_ECN 13 +/* Host can handle UFO in */ +#define VIRTIO_NET_F_HOST_UFO 14 +/* Host can merge receive buffers */ +#define VIRTIO_NET_F_MRG_RXBUF 15 +/* virtio_net_config.status available */ +#define VIRTIO_NET_F_STATUS 16 +/* Control channel available */ +#define VIRTIO_NET_F_CTRL_VQ 17 +/* Control channel RX mode support */ +#define VIRTIO_NET_F_CTRL_RX 18 +/* Control channel VLAN filtering */ +#define VIRTIO_NET_F_CTRL_VLAN 19 +/* Extra RX mode control support */ +#define VIRTIO_NET_F_CTRL_RX_EXTRA 20 +/* Guest can announce device on the network */ +#define VIRTIO_NET_F_GUEST_ANNOUNCE 21 +/* Device supports receive flow steering */ +#define VIRTIO_NET_F_MQ 22 +/* Set MAC address */ +#define VIRTIO_NET_F_CTRL_MAC_ADDR 23 +/* Device set linkspeed and duplex */ +#define VIRTIO_NET_F_SPEED_DUPLEX 63 + +#ifndef VIRTIO_NET_NO_LEGACY +/* Host handles pkts w/ any GSO type */ +#define VIRTIO_NET_F_GSO 6 +#endif /* VIRTIO_NET_NO_LEGACY */ + +#define VIRTIO_NET_S_LINK_UP 1 /* Link is up */ +#define VIRTIO_NET_S_ANNOUNCE 2 /* Announcement is needed */ + +struct __packed virtio_net_config { + /* The config defining mac address (if VIRTIO_NET_F_MAC) */ + __u8 mac[ETH_ALEN]; + /* See VIRTIO_NET_F_STATUS and VIRTIO_NET_S_* above */ + __u16 status; + /* + * Maximum number of each of transmit and receive queues; + * see VIRTIO_NET_F_MQ and VIRTIO_NET_CTRL_MQ. + * Legal values are between 1 and 0x8000 + */ + __u16 max_virtqueue_pairs; + /* Default maximum transmit unit advice */ + __u16 mtu; + /* + * speed, in units of 1Mb. All values 0 to INT_MAX are legal. + * Any other value stands for unknown. + */ + __u32 speed; + /* + * 0x00 - half duplex + * 0x01 - full duplex + * Any other value stands for unknown. + */ + __u8 duplex; +}; + +/* + * This header comes first in the scatter-gather list. If you don't + * specify GSO or CSUM features, you can simply ignore the header. + * + * This is bitwise-equivalent to the legacy struct virtio_net_hdr_mrg_rxbuf, + * only flattened. + */ +struct virtio_net_hdr_v1 { +#define VIRTIO_NET_HDR_F_NEEDS_CSUM 0x01 /* Use csum_start, csum_offset */ +#define VIRTIO_NET_HDR_F_DATA_VALID 0x02 /* Csum is valid */ + __u8 flags; +#define VIRTIO_NET_HDR_GSO_NONE 0x00 /* Not a GSO frame */ +#define VIRTIO_NET_HDR_GSO_TCPV4 0x01 /* GSO frame, IPv4 TCP (TSO) */ +#define VIRTIO_NET_HDR_GSO_UDP 0x03 /* GSO frame, IPv4 UDP (UFO) */ +#define VIRTIO_NET_HDR_GSO_TCPV6 0x04 /* GSO frame, IPv6 TCP */ +#define VIRTIO_NET_HDR_GSO_ECN 0x80 /* TCP has ECN set */ + __u8 gso_type; + __virtio16 hdr_len; /* Ethernet + IP + tcp/udp hdrs */ + __virtio16 gso_size; /* Bytes to append to hdr_len per frame */ + __virtio16 csum_start; /* Position to start checksumming from */ + __virtio16 csum_offset; /* Offset after that to place checksum */ + __virtio16 num_buffers; /* Number of merged rx buffers */ +}; + +#ifndef VIRTIO_NET_NO_LEGACY +/* + * This header comes first in the scatter-gather list. + * + * For legacy virtio, if VIRTIO_F_ANY_LAYOUT is not negotiated, it must + * be the first element of the scatter-gather list. If you don't + * specify GSO or CSUM features, you can simply ignore the header. + */ +struct virtio_net_hdr { + /* See VIRTIO_NET_HDR_F_* */ + __u8 flags; + /* See VIRTIO_NET_HDR_GSO_* */ + __u8 gso_type; + __virtio16 hdr_len; /* Ethernet + IP + tcp/udp hdrs */ + __virtio16 gso_size; /* Bytes to append to hdr_len per frame */ + __virtio16 csum_start; /* Position to start checksumming from */ + __virtio16 csum_offset; /* Offset after that to place checksum */ +}; + +/* + * This is the version of the header to use when the MRG_RXBUF + * feature has been negotiated. + */ +struct virtio_net_hdr_mrg_rxbuf { + struct virtio_net_hdr hdr; + __virtio16 num_buffers; /* Number of merged rx buffers */ +}; +#endif /* ...VIRTIO_NET_NO_LEGACY */ + +/* + * Control virtqueue data structures + * + * The control virtqueue expects a header in the first sg entry + * and an ack/status response in the last entry. Data for the + * command goes in between. + */ +struct __packed virtio_net_ctrl_hdr { + __u8 class; + __u8 cmd; +}; + +typedef __u8 virtio_net_ctrl_ack; + +#define VIRTIO_NET_OK 0 +#define VIRTIO_NET_ERR 1 + +/* + * Control the RX mode, ie. promisucous, allmulti, etc... + * + * All commands require an "out" sg entry containing a 1 byte state value, + * zero = disable, non-zero = enable. + * + * Commands 0 and 1 are supported with the VIRTIO_NET_F_CTRL_RX feature. + * Commands 2-5 are added with VIRTIO_NET_F_CTRL_RX_EXTRA. + */ +#define VIRTIO_NET_CTRL_RX 0 +#define VIRTIO_NET_CTRL_RX_PROMISC 0 +#define VIRTIO_NET_CTRL_RX_ALLMULTI 1 +#define VIRTIO_NET_CTRL_RX_ALLUNI 2 +#define VIRTIO_NET_CTRL_RX_NOMULTI 3 +#define VIRTIO_NET_CTRL_RX_NOUNI 4 +#define VIRTIO_NET_CTRL_RX_NOBCAST 5 + +/* + * Control the MAC + * + * The MAC filter table is managed by the hypervisor, the guest should assume + * the size is infinite. Filtering should be considered non-perfect, ie. based + * on hypervisor resources, the guest may received packets from sources not + * specified in the filter list. + * + * In addition to the class/cmd header, the TABLE_SET command requires two + * out scatterlists. Each contains a 4 byte count of entries followed by a + * concatenated byte stream of the ETH_ALEN MAC addresses. The first sg list + * contains unicast addresses, the second is for multicast. This functionality + * is present if the VIRTIO_NET_F_CTRL_RX feature is available. + * + * The ADDR_SET command requests one out scatterlist, it contains a 6 bytes MAC + * address. This functionality is present if the VIRTIO_NET_F_CTRL_MAC_ADDR + * feature is available. + */ +struct __packed virtio_net_ctrl_mac { + __virtio32 entries; + __u8 macs[][ETH_ALEN]; +}; + +#define VIRTIO_NET_CTRL_MAC 1 +#define VIRTIO_NET_CTRL_MAC_TABLE_SET 0 +#define VIRTIO_NET_CTRL_MAC_ADDR_SET 1 + +/* + * Control VLAN filtering + * + * The VLAN filter table is controlled via a simple ADD/DEL interface. VLAN IDs + * not added may be filterd by the hypervisor. Del is the opposite of add. Both + * commands expect an out entry containing a 2 byte VLAN ID. VLAN filterting is + * available with the VIRTIO_NET_F_CTRL_VLAN feature bit. + */ +#define VIRTIO_NET_CTRL_VLAN 2 +#define VIRTIO_NET_CTRL_VLAN_ADD 0 +#define VIRTIO_NET_CTRL_VLAN_DEL 1 + +/* + * Control link announce acknowledgment + * + * The command VIRTIO_NET_CTRL_ANNOUNCE_ACK is used to indicate that driver has + * recevied the notification; device would clear the VIRTIO_NET_S_ANNOUNCE bit + * in the status field after it receives this command. + */ +#define VIRTIO_NET_CTRL_ANNOUNCE 3 +#define VIRTIO_NET_CTRL_ANNOUNCE_ACK 0 + +/* + * Control receive flow steering + * + * The command VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET enables receive flow steering, + * specifying the number of the transmit and receive queues that will be used. + * After the command is consumed and acked by the device, the device will not + * steer new packets on receive virtqueues other than specified nor read from + * transmit virtqueues other than specified. Accordingly, driver should not + * transmit new packets on virtqueues other than specified. + */ +struct virtio_net_ctrl_mq { + __virtio16 virtqueue_pairs; +}; + +#define VIRTIO_NET_CTRL_MQ 4 +#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_SET 0 +#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MIN 1 +#define VIRTIO_NET_CTRL_MQ_VQ_PAIRS_MAX 0x8000 + +/* + * Control network offloads + * + * Reconfigures the network offloads that guest can handle. + * + * Available with the VIRTIO_NET_F_CTRL_GUEST_OFFLOADS feature bit. + * + * Command data format matches the feature bit mask exactly. + * + * See VIRTIO_NET_F_GUEST_* for the list of offloads + * that can be enabled/disabled. + */ +#define VIRTIO_NET_CTRL_GUEST_OFFLOADS 5 +#define VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET 0 + +#endif /* _LINUX_VIRTIO_NET_H */