Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added virtio network device support. Signed-off-by: Dan Milea <dan.milea@windriver.com>
- Loading branch information
Showing
3 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 */ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |