Skip to content

Commit

Permalink
virtio-mmio: network device
Browse files Browse the repository at this point in the history
Added virtio network device support.

Signed-off-by: Dan Milea <dan.milea@windriver.com>
  • Loading branch information
Dan Milea authored and arnopo committed Nov 7, 2022
1 parent fb61aed commit 9a574c5
Show file tree
Hide file tree
Showing 3 changed files with 363 additions and 0 deletions.
127 changes: 127 additions & 0 deletions lib/include/openamp/virtio_net.h
@@ -0,0 +1,127 @@
/*
* Copyright (c) 2022 Wind River Systems, Inc.
*
* SPDX-License-Identifier: BSD-3-Clause
*/

#ifndef VIRTIO_NET_H
#define VIRTIO_NET_H

#include <openamp/virtqueue.h>
#include <openamp/virtio.h>
#include <metal/device.h>

#define VQIN_SIZE 4
#define RXDESC_COUNT 4
#define RXPOOL_SIZE 6

#define VQOUT_SIZE 4
#define TXDESC_COUNT 4

#define VIRTIO_NET_F_MAC (1 << 5)
#define VIRTIO_NET_F_MRG_RXBUF (1 << 15)

struct _net_eth_addr {
uint8_t addr[6];
};

struct _net_eth_hdr {
struct _net_eth_addr dst;
struct _net_eth_addr src;
uint16_t type;
} __packed;

#ifndef NET_ETH_MTU
#define NET_ETH_MTU 1500
#endif

#ifndef NET_ETH_MAX_FRAME_SIZE
#define NET_ETH_MAX_FRAME_SIZE (NET_ETH_MTU + sizeof(struct _net_eth_hdr))
#endif
#ifndef NET_ETH_MAX_HDR_SIZE
#define NET_ETH_MAX_HDR_SIZE (sizeof(struct _net_eth_hdr))
#endif

struct virtio_net_hdr {
uint8_t flags;
uint8_t gso_type;
uint16_t hdr_len;
uint16_t gso_size;
uint16_t csum_start;
uint16_t csum_offset;
uint16_t num_buffers;
};

struct virtio_net_rx_pkt {
struct virtio_net_hdr hdr;
uint8_t pkt[NET_ETH_MAX_FRAME_SIZE];
};

struct virtio_net_tx_pkt {
struct virtio_net_hdr hdr;
uint8_t pkt[NET_ETH_MAX_FRAME_SIZE];
};

struct virtio_net_rx_desc {
struct metal_list node;
struct virtio_net_rx_pkt *pkt;
uint8_t *data;
};

struct virtio_net_tx_desc {
struct metal_list node;
struct virtio_net_tx_pkt *pkt;
uint8_t *data;
};

struct virtio_net_data {
struct net_if *iface;
/* Incoming packet processing hook */
void (*net_pkt_in_cb)(uint8_t *data, int length, void *arg);
void *net_pkt_in_cb_arg;
uint8_t mac_addr[6];
struct virtqueue *vqin, *vqout;
int hdrsize;
struct metal_list tx_free_list;
struct metal_list rx_free_list;
struct virtio_net_tx_pkt *txbuf;
struct virtio_net_tx_desc *txdesc;
struct virtio_net_rx_pkt *rxbuf;
struct virtio_net_rx_desc *rxdesc;
};

void virtio_net_vqin_cb(void *arg);
void virtio_net_vqout_cb(void *arg);

typedef void (*net_funcptr)(void *);

/**
* @brief Initialize a VIRTIO network device.
*
* @param[in] vdev Pointer to virtio_device structure.
* @param[in] vqs Array of pointers to the virtqueues used by the device.
* @param[in] vq_names Array of pointers to the virtqueues names.
* @param[in] cbs Array of function pointers to call on virtqueue kick.
* @param[in] cb_args Array of pointers to parameters for kick callbacks.
* @param[in] vq_count Number of virtqueues the device uses.
*
* @return int 0 for success.
*/

int virtio_net_init(struct virtio_device *vdev, struct virtqueue **vqs, char **vq_names,
net_funcptr *cbs, void **cb_args, int vq_count);

/**
* @brief Send packet over VIRTIO network device
*
* @param[in] vdev Pointer to virtio_device structure.
* @param[in] data Data buffer.
* @param[in] length Data buffer length.
*
* @return int 0 for success.
*/

int virtio_net_send(const struct virtio_device *vdev, uint8_t *data, uint16_t length);

#endif /* VIRTIO_NET_H */

1 change: 1 addition & 0 deletions lib/virtio_mmio/CMakeLists.txt
@@ -1,2 +1,3 @@
collect (PROJECT_LIB_SOURCES virtio_mmio.c)
collect (PROJECT_LIB_SOURCES virtio_rng.c)
collect (PROJECT_LIB_SOURCES virtio_net.c)
235 changes: 235 additions & 0 deletions lib/virtio_mmio/virtio_net.c
@@ -0,0 +1,235 @@
/*
* Copyright (c) 2022 Wind River Systems, Inc.
*
* SPDX-License-Identifier: BSD-3-Clause
*
* $FreeBSD$
*/

#include <openamp/open_amp.h>
#include <openamp/virtqueue.h>
#include <openamp/virtio.h>
#include <openamp/virtio_mmio.h>
#include <openamp/virtio_net.h>
#include <metal/device.h>
#include <metal/irq.h>

#if !defined(WITH_VIRTIO_MMIO)
#error Only VIRTIO-MMIO transport layer supported
#endif

#define LOG_MODULE_NAME "virtio_net"

static int virtio_net_rx_refill(struct virtio_net_data *pdata)
{
struct metal_list *node = metal_list_first(&pdata->rx_free_list);
struct virtio_net_rx_desc *desc = NULL;
struct virtqueue_buf vb[1] = {0};
int status = 0;

while (!virtqueue_full(pdata->vqin)) {
node = metal_list_first(&pdata->rx_free_list);
if (!node) {
VIRTIO_ASSERT(0, "should have one descriptor per VQ buffer");
status = -ENOMEM;
break;
}

desc = metal_container_of(node, struct virtio_net_rx_desc, node);
memset(&desc->pkt->hdr, 0, sizeof(desc->pkt->hdr));

vb[0].buf = desc->pkt;
vb[0].len = pdata->hdrsize + NET_ETH_MTU;

status = virtqueue_add_buffer(pdata->vqin, vb, 0, 1, (void *)desc);
if (status != 0) {
VIRTIO_ASSERT(0, "Should have one descriptor per VQ buffer.");
metal_list_add_tail(&pdata->rx_free_list, &desc->node);
break;
}
}

return status;
}

int virtio_net_init(struct virtio_device *vdev, struct virtqueue **vqs, char **vq_names,
void (**cbs)(void *), void **cb_args, int vq_count)
{
uint32_t devid, features;
struct virtqueue *vq = NULL;
struct virtio_net_data *dev_data = NULL;
int ret;
int i;

if (!vdev || !vdev->priv || !vqs || !vq_names) {
return -EINVAL;
}

dev_data = vdev->priv;

vdev->vrings_info = metal_allocate_memory(sizeof(struct virtio_vring_info) * vq_count);
if (!vdev->vrings_info) {
return -ENOMEM;
}

devid = virtio_get_devid(vdev);
if (devid != VIRTIO_ID_NETWORK) {
metal_log(METAL_LOG_ERROR, "%s: Expected devid %04x, got %04x\n",
LOG_MODULE_NAME, VIRTIO_ID_NETWORK, devid);
metal_free_memory(vdev->vrings_info);
return -ENODEV;
}

virtio_device_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER);
virtio_device_set_features(vdev, VIRTIO_NET_F_MAC/*VIRTIO_F_NOTIFY_ON_EMPTY*/);
features = virtio_device_get_features(vdev);
metal_log(METAL_LOG_DEBUG, "features: %08x\n", features);

for (i = 0; i < vq_count; i++) {
/* TODO: update API for compatibility with other transports like
* remoteproc virtio
*/
vq = virtio_mmio_setup_virtqueue(
vdev,
i,
vqs[i],
cbs[i],
cb_args[i],
vq_names[i]
);
if (!vq) {
return -1;
}
}

virtio_device_set_status(vdev, VIRTIO_CONFIG_STATUS_DRIVER_OK);
dev_data->vqin = vdev->vrings_info[0].vq;
dev_data->vqout = vdev->vrings_info[1].vq;

dev_data->hdrsize = sizeof(struct virtio_net_hdr);
if (!(features & VIRTIO_NET_F_MRG_RXBUF)) {
dev_data->hdrsize -= 2;
}

metal_list_init(&dev_data->rx_free_list);

for (i = 0; i < RXDESC_COUNT; i++) {
metal_log(METAL_LOG_DEBUG, "%s: rx %d at %p\n", "virtio_net", i,
&dev_data->rxdesc[i]);

dev_data->rxdesc[i].pkt = &dev_data->rxbuf[i];
if (features & VIRTIO_NET_F_MRG_RXBUF) {
dev_data->rxdesc[i].data = dev_data->rxbuf[i].pkt;
} else {
dev_data->rxdesc[i].data = (uint8_t *)&dev_data->rxbuf[i].hdr.num_buffers;
}
metal_list_add_tail(&dev_data->rx_free_list, &dev_data->rxdesc[i].node);
}

ret = virtio_net_rx_refill(dev_data);
if (ret != 0) {
return ret;
}

metal_list_init(&dev_data->tx_free_list);

for (i = 0; i < TXDESC_COUNT; i++) {
metal_log(METAL_LOG_DEBUG, "%s: tx %d at %p\n", "virtio_net", i,
&dev_data->txdesc[i]);

dev_data->txdesc[i].pkt = &dev_data->txbuf[i];
if (features & VIRTIO_NET_F_MRG_RXBUF) {
dev_data->txdesc[i].data = dev_data->txbuf[i].pkt;
} else {
dev_data->txdesc[i].data = (uint8_t *)&dev_data->txbuf[i].hdr.num_buffers;
}
metal_list_add_tail(&dev_data->tx_free_list, &dev_data->txdesc[i].node);
}

virtqueue_kick(dev_data->vqin);
virtqueue_kick(dev_data->vqout);

if (VIRTIO_NET_F_MAC & features) {
virtio_device_read_config(vdev, 0, dev_data->mac_addr, 6);
} else {
VIRTIO_ASSERT(0, "should generate a MAC address");
}

return 0;
}

void virtio_net_vqin_cb(void *arg)
{
struct virtio_device *vdev = arg;
struct virtio_net_rx_desc *desc;
void (*net_pkt_in_cb)(uint8_t *data, int length, void *arg);
struct virtio_net_data *dev_data = vdev->priv;
uint32_t length;

net_pkt_in_cb = dev_data->net_pkt_in_cb;

while ((desc = virtqueue_get_buffer(dev_data->vqin, &length, NULL))) {
length -= dev_data->hdrsize;
if (net_pkt_in_cb != NULL) {
net_pkt_in_cb(desc->data, length, dev_data->net_pkt_in_cb_arg);
}
metal_list_add_tail(&dev_data->rx_free_list, &desc->node);
}
(void)virtio_net_rx_refill(dev_data);
}

void virtio_net_vqout_cb(void *arg)
{
struct virtio_net_data *pdata = arg;
struct virtio_net_tx_desc *desc;

while ((desc = virtqueue_get_buffer(pdata->vqout, NULL, NULL))) {
metal_list_add_tail(&pdata->tx_free_list, &desc->node);
}
}

int virtio_net_send(const struct virtio_device *vdev, uint8_t *data, uint16_t length)
{
struct metal_list *node;
struct virtio_net_tx_desc *desc;
uint16_t total_len;
int key;
int ret = -EIO;
struct virtio_net_data *dev_data = vdev->priv;
struct virtqueue_buf vb[1] = {0};

total_len = length;
if ((total_len > NET_ETH_MAX_FRAME_SIZE) || (total_len == 0)) {
return -EINVAL;
}

key = metal_irq_save_disable();
node = metal_list_first(&dev_data->tx_free_list);
metal_irq_restore_enable(key);
if (!node) {
return ret;
}

desc = metal_container_of(node, struct virtio_net_tx_desc, node);

memset(&desc->pkt->hdr, 0, sizeof(desc->pkt->hdr));
memcpy(desc->data, data, length);

vb[0].buf = desc->pkt;
vb[0].len = total_len + dev_data->hdrsize;

if (virtqueue_add_buffer(dev_data->vqout, vb, 1, 0, (void *)desc)) {
goto recycle;
}

virtqueue_kick(dev_data->vqout);

return 0;

recycle:
key = metal_irq_save_disable();
metal_list_add_tail(&dev_data->tx_free_list, &desc->node);
metal_irq_restore_enable(key);

return ret;
}

0 comments on commit 9a574c5

Please sign in to comment.