| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * VDPA simulator for networking device.
- *
- * Copyright (c) 2020, Red Hat Inc. All rights reserved.
- * Author: Jason Wang <jasowang@redhat.com>
- *
- */
- #include <linux/init.h>
- #include <linux/module.h>
- #include <linux/device.h>
- #include <linux/kernel.h>
- #include <linux/etherdevice.h>
- #include <linux/vringh.h>
- #include <linux/vdpa.h>
- #include <net/netlink.h>
- #include <uapi/linux/virtio_net.h>
- #include <uapi/linux/vdpa.h>
- #include "vdpa_sim.h"
- #define DRV_VERSION "0.1"
- #define DRV_AUTHOR "Jason Wang <jasowang@redhat.com>"
- #define DRV_DESC "vDPA Device Simulator for networking device"
- #define DRV_LICENSE "GPL v2"
- #define VDPASIM_NET_FEATURES (VDPASIM_FEATURES | \
- (1ULL << VIRTIO_NET_F_MAC) | \
- (1ULL << VIRTIO_NET_F_STATUS) | \
- (1ULL << VIRTIO_NET_F_MTU) | \
- (1ULL << VIRTIO_NET_F_CTRL_VQ) | \
- (1ULL << VIRTIO_NET_F_CTRL_MAC_ADDR))
- /* 3 virtqueues, 2 address spaces, 2 virtqueue groups */
- #define VDPASIM_NET_VQ_NUM 3
- #define VDPASIM_NET_AS_NUM 2
- #define VDPASIM_NET_GROUP_NUM 2
- struct vdpasim_dataq_stats {
- struct u64_stats_sync syncp;
- u64 pkts;
- u64 bytes;
- u64 drops;
- u64 errors;
- u64 overruns;
- };
- struct vdpasim_cq_stats {
- struct u64_stats_sync syncp;
- u64 requests;
- u64 successes;
- u64 errors;
- };
- struct vdpasim_net{
- struct vdpasim vdpasim;
- struct vdpasim_dataq_stats tx_stats;
- struct vdpasim_dataq_stats rx_stats;
- struct vdpasim_cq_stats cq_stats;
- void *buffer;
- };
- static struct vdpasim_net *sim_to_net(struct vdpasim *vdpasim)
- {
- return container_of(vdpasim, struct vdpasim_net, vdpasim);
- }
- static void vdpasim_net_complete(struct vdpasim_virtqueue *vq, size_t len)
- {
- /* Make sure data is wrote before advancing index */
- smp_wmb();
- vringh_complete_iotlb(&vq->vring, vq->head, len);
- /* Make sure used is visible before rasing the interrupt. */
- smp_wmb();
- local_bh_disable();
- if (vringh_need_notify_iotlb(&vq->vring) > 0)
- vringh_notify(&vq->vring);
- local_bh_enable();
- }
- static bool receive_filter(struct vdpasim *vdpasim, size_t len)
- {
- bool modern = vdpasim->features & (1ULL << VIRTIO_F_VERSION_1);
- size_t hdr_len = modern ? sizeof(struct virtio_net_hdr_v1) :
- sizeof(struct virtio_net_hdr);
- struct virtio_net_config *vio_config = vdpasim->config;
- struct vdpasim_net *net = sim_to_net(vdpasim);
- if (len < ETH_ALEN + hdr_len)
- return false;
- if (is_broadcast_ether_addr(net->buffer + hdr_len) ||
- is_multicast_ether_addr(net->buffer + hdr_len))
- return true;
- if (!strncmp(net->buffer + hdr_len, vio_config->mac, ETH_ALEN))
- return true;
- return false;
- }
- static virtio_net_ctrl_ack vdpasim_handle_ctrl_mac(struct vdpasim *vdpasim,
- u8 cmd)
- {
- struct virtio_net_config *vio_config = vdpasim->config;
- struct vdpasim_virtqueue *cvq = &vdpasim->vqs[2];
- virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
- size_t read;
- switch (cmd) {
- case VIRTIO_NET_CTRL_MAC_ADDR_SET:
- read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->in_iov,
- vio_config->mac, ETH_ALEN);
- if (read == ETH_ALEN)
- status = VIRTIO_NET_OK;
- break;
- default:
- break;
- }
- return status;
- }
- static void vdpasim_handle_cvq(struct vdpasim *vdpasim)
- {
- struct vdpasim_virtqueue *cvq = &vdpasim->vqs[2];
- struct vdpasim_net *net = sim_to_net(vdpasim);
- virtio_net_ctrl_ack status = VIRTIO_NET_ERR;
- struct virtio_net_ctrl_hdr ctrl;
- size_t read, write;
- u64 requests = 0, errors = 0, successes = 0;
- int err;
- if (!(vdpasim->features & (1ULL << VIRTIO_NET_F_CTRL_VQ)))
- return;
- if (!cvq->ready)
- return;
- while (true) {
- err = vringh_getdesc_iotlb(&cvq->vring, &cvq->in_iov,
- &cvq->out_iov,
- &cvq->head, GFP_ATOMIC);
- if (err <= 0)
- break;
- ++requests;
- read = vringh_iov_pull_iotlb(&cvq->vring, &cvq->in_iov, &ctrl,
- sizeof(ctrl));
- if (read != sizeof(ctrl)) {
- ++errors;
- break;
- }
- switch (ctrl.class) {
- case VIRTIO_NET_CTRL_MAC:
- status = vdpasim_handle_ctrl_mac(vdpasim, ctrl.cmd);
- break;
- default:
- break;
- }
- if (status == VIRTIO_NET_OK)
- ++successes;
- else
- ++errors;
- /* Make sure data is wrote before advancing index */
- smp_wmb();
- write = vringh_iov_push_iotlb(&cvq->vring, &cvq->out_iov,
- &status, sizeof(status));
- vringh_complete_iotlb(&cvq->vring, cvq->head, write);
- vringh_kiov_cleanup(&cvq->in_iov);
- vringh_kiov_cleanup(&cvq->out_iov);
- /* Make sure used is visible before rasing the interrupt. */
- smp_wmb();
- local_bh_disable();
- if (cvq->cb)
- cvq->cb(cvq->private);
- local_bh_enable();
- }
- u64_stats_update_begin(&net->cq_stats.syncp);
- net->cq_stats.requests += requests;
- net->cq_stats.errors += errors;
- net->cq_stats.successes += successes;
- u64_stats_update_end(&net->cq_stats.syncp);
- }
- static void vdpasim_net_work(struct vdpasim *vdpasim)
- {
- struct vdpasim_virtqueue *txq = &vdpasim->vqs[1];
- struct vdpasim_virtqueue *rxq = &vdpasim->vqs[0];
- struct vdpasim_net *net = sim_to_net(vdpasim);
- ssize_t read, write;
- u64 tx_pkts = 0, rx_pkts = 0, tx_bytes = 0, rx_bytes = 0;
- u64 rx_drops = 0, rx_overruns = 0, rx_errors = 0, tx_errors = 0;
- int err;
- mutex_lock(&vdpasim->mutex);
- if (!vdpasim->running)
- goto out;
- if (!(vdpasim->status & VIRTIO_CONFIG_S_DRIVER_OK))
- goto out;
- vdpasim_handle_cvq(vdpasim);
- if (!txq->ready || !rxq->ready)
- goto out;
- while (true) {
- err = vringh_getdesc_iotlb(&txq->vring, &txq->out_iov, NULL,
- &txq->head, GFP_ATOMIC);
- if (err <= 0) {
- if (err)
- ++tx_errors;
- break;
- }
- ++tx_pkts;
- read = vringh_iov_pull_iotlb(&txq->vring, &txq->out_iov,
- net->buffer, PAGE_SIZE);
- tx_bytes += read;
- if (!receive_filter(vdpasim, read)) {
- ++rx_drops;
- vdpasim_net_complete(txq, 0);
- continue;
- }
- err = vringh_getdesc_iotlb(&rxq->vring, NULL, &rxq->in_iov,
- &rxq->head, GFP_ATOMIC);
- if (err <= 0) {
- ++rx_overruns;
- vdpasim_net_complete(txq, 0);
- break;
- }
- write = vringh_iov_push_iotlb(&rxq->vring, &rxq->in_iov,
- net->buffer, read);
- if (write <= 0) {
- ++rx_errors;
- break;
- }
- ++rx_pkts;
- rx_bytes += write;
- vdpasim_net_complete(txq, 0);
- vdpasim_net_complete(rxq, write);
- if (tx_pkts > 4) {
- vdpasim_schedule_work(vdpasim);
- goto out;
- }
- }
- out:
- mutex_unlock(&vdpasim->mutex);
- u64_stats_update_begin(&net->tx_stats.syncp);
- net->tx_stats.pkts += tx_pkts;
- net->tx_stats.bytes += tx_bytes;
- net->tx_stats.errors += tx_errors;
- u64_stats_update_end(&net->tx_stats.syncp);
- u64_stats_update_begin(&net->rx_stats.syncp);
- net->rx_stats.pkts += rx_pkts;
- net->rx_stats.bytes += rx_bytes;
- net->rx_stats.drops += rx_drops;
- net->rx_stats.errors += rx_errors;
- net->rx_stats.overruns += rx_overruns;
- u64_stats_update_end(&net->rx_stats.syncp);
- }
- static int vdpasim_net_get_stats(struct vdpasim *vdpasim, u16 idx,
- struct sk_buff *msg,
- struct netlink_ext_ack *extack)
- {
- struct vdpasim_net *net = sim_to_net(vdpasim);
- u64 rx_pkts, rx_bytes, rx_errors, rx_overruns, rx_drops;
- u64 tx_pkts, tx_bytes, tx_errors, tx_drops;
- u64 cq_requests, cq_successes, cq_errors;
- unsigned int start;
- int err = -EMSGSIZE;
- switch(idx) {
- case 0:
- do {
- start = u64_stats_fetch_begin(&net->rx_stats.syncp);
- rx_pkts = net->rx_stats.pkts;
- rx_bytes = net->rx_stats.bytes;
- rx_errors = net->rx_stats.errors;
- rx_overruns = net->rx_stats.overruns;
- rx_drops = net->rx_stats.drops;
- } while (u64_stats_fetch_retry(&net->rx_stats.syncp, start));
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "rx packets"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- rx_pkts, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "rx bytes"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- rx_bytes, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "rx errors"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- rx_errors, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "rx overruns"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- rx_overruns, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "rx drops"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- rx_drops, VDPA_ATTR_PAD))
- break;
- err = 0;
- break;
- case 1:
- do {
- start = u64_stats_fetch_begin(&net->tx_stats.syncp);
- tx_pkts = net->tx_stats.pkts;
- tx_bytes = net->tx_stats.bytes;
- tx_errors = net->tx_stats.errors;
- tx_drops = net->tx_stats.drops;
- } while (u64_stats_fetch_retry(&net->tx_stats.syncp, start));
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "tx packets"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- tx_pkts, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "tx bytes"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- tx_bytes, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "tx errors"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- tx_errors, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "tx drops"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- tx_drops, VDPA_ATTR_PAD))
- break;
- err = 0;
- break;
- case 2:
- do {
- start = u64_stats_fetch_begin(&net->cq_stats.syncp);
- cq_requests = net->cq_stats.requests;
- cq_successes = net->cq_stats.successes;
- cq_errors = net->cq_stats.errors;
- } while (u64_stats_fetch_retry(&net->cq_stats.syncp, start));
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "cvq requests"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- cq_requests, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "cvq successes"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- cq_successes, VDPA_ATTR_PAD))
- break;
- if (nla_put_string(msg, VDPA_ATTR_DEV_VENDOR_ATTR_NAME,
- "cvq errors"))
- break;
- if (nla_put_u64_64bit(msg, VDPA_ATTR_DEV_VENDOR_ATTR_VALUE,
- cq_errors, VDPA_ATTR_PAD))
- break;
- err = 0;
- break;
- default:
- err = -EINVAL;
- break;
- }
- return err;
- }
- static void vdpasim_net_get_config(struct vdpasim *vdpasim, void *config)
- {
- struct virtio_net_config *net_config = config;
- net_config->status = cpu_to_vdpasim16(vdpasim, VIRTIO_NET_S_LINK_UP);
- }
- static int vdpasim_net_set_attr(struct vdpa_mgmt_dev *mdev, struct vdpa_device *dev,
- const struct vdpa_dev_set_config *config)
- {
- struct vdpasim *vdpasim = container_of(dev, struct vdpasim, vdpa);
- struct virtio_net_config *vio_config = vdpasim->config;
- mutex_lock(&vdpasim->mutex);
- if (config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MACADDR)) {
- ether_addr_copy(vio_config->mac, config->net.mac);
- mutex_unlock(&vdpasim->mutex);
- return 0;
- }
- mutex_unlock(&vdpasim->mutex);
- return -EOPNOTSUPP;
- }
- static void vdpasim_net_setup_config(struct vdpasim *vdpasim,
- const struct vdpa_dev_set_config *config)
- {
- struct virtio_net_config *vio_config = vdpasim->config;
- if (config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MACADDR))
- memcpy(vio_config->mac, config->net.mac, ETH_ALEN);
- if (config->mask & (1 << VDPA_ATTR_DEV_NET_CFG_MTU))
- vio_config->mtu = cpu_to_vdpasim16(vdpasim, config->net.mtu);
- else
- /* Setup default MTU to be 1500 */
- vio_config->mtu = cpu_to_vdpasim16(vdpasim, 1500);
- }
- static void vdpasim_net_free(struct vdpasim *vdpasim)
- {
- struct vdpasim_net *net = sim_to_net(vdpasim);
- kvfree(net->buffer);
- }
- static void vdpasim_net_mgmtdev_release(struct device *dev)
- {
- }
- static struct device vdpasim_net_mgmtdev = {
- .init_name = "vdpasim_net",
- .release = vdpasim_net_mgmtdev_release,
- };
- static int vdpasim_net_dev_add(struct vdpa_mgmt_dev *mdev, const char *name,
- const struct vdpa_dev_set_config *config)
- {
- struct vdpasim_dev_attr dev_attr = {};
- struct vdpasim_net *net;
- struct vdpasim *simdev;
- int ret;
- dev_attr.mgmt_dev = mdev;
- dev_attr.name = name;
- dev_attr.id = VIRTIO_ID_NET;
- dev_attr.supported_features = VDPASIM_NET_FEATURES;
- dev_attr.nvqs = VDPASIM_NET_VQ_NUM;
- dev_attr.ngroups = VDPASIM_NET_GROUP_NUM;
- dev_attr.nas = VDPASIM_NET_AS_NUM;
- dev_attr.alloc_size = sizeof(struct vdpasim_net);
- dev_attr.config_size = sizeof(struct virtio_net_config);
- dev_attr.get_config = vdpasim_net_get_config;
- dev_attr.work_fn = vdpasim_net_work;
- dev_attr.get_stats = vdpasim_net_get_stats;
- dev_attr.free = vdpasim_net_free;
- simdev = vdpasim_create(&dev_attr, config);
- if (IS_ERR(simdev))
- return PTR_ERR(simdev);
- vdpasim_net_setup_config(simdev, config);
- net = sim_to_net(simdev);
- u64_stats_init(&net->tx_stats.syncp);
- u64_stats_init(&net->rx_stats.syncp);
- u64_stats_init(&net->cq_stats.syncp);
- net->buffer = kvmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!net->buffer) {
- ret = -ENOMEM;
- goto reg_err;
- }
- /*
- * Initialization must be completed before this call, since it can
- * connect the device to the vDPA bus, so requests can arrive after
- * this call.
- */
- ret = _vdpa_register_device(&simdev->vdpa, VDPASIM_NET_VQ_NUM);
- if (ret)
- goto reg_err;
- return 0;
- reg_err:
- put_device(&simdev->vdpa.dev);
- return ret;
- }
- static void vdpasim_net_dev_del(struct vdpa_mgmt_dev *mdev,
- struct vdpa_device *dev)
- {
- struct vdpasim *simdev = container_of(dev, struct vdpasim, vdpa);
- _vdpa_unregister_device(&simdev->vdpa);
- }
- static const struct vdpa_mgmtdev_ops vdpasim_net_mgmtdev_ops = {
- .dev_add = vdpasim_net_dev_add,
- .dev_del = vdpasim_net_dev_del,
- .dev_set_attr = vdpasim_net_set_attr
- };
- static struct virtio_device_id id_table[] = {
- { VIRTIO_ID_NET, VIRTIO_DEV_ANY_ID },
- { 0 },
- };
- static struct vdpa_mgmt_dev mgmt_dev = {
- .device = &vdpasim_net_mgmtdev,
- .id_table = id_table,
- .ops = &vdpasim_net_mgmtdev_ops,
- .config_attr_mask = (1 << VDPA_ATTR_DEV_NET_CFG_MACADDR |
- 1 << VDPA_ATTR_DEV_NET_CFG_MTU |
- 1 << VDPA_ATTR_DEV_FEATURES),
- .max_supported_vqs = VDPASIM_NET_VQ_NUM,
- .supported_features = VDPASIM_NET_FEATURES,
- };
- static int __init vdpasim_net_init(void)
- {
- int ret;
- ret = device_register(&vdpasim_net_mgmtdev);
- if (ret) {
- put_device(&vdpasim_net_mgmtdev);
- return ret;
- }
- ret = vdpa_mgmtdev_register(&mgmt_dev);
- if (ret)
- goto parent_err;
- return 0;
- parent_err:
- device_unregister(&vdpasim_net_mgmtdev);
- return ret;
- }
- static void __exit vdpasim_net_exit(void)
- {
- vdpa_mgmtdev_unregister(&mgmt_dev);
- device_unregister(&vdpasim_net_mgmtdev);
- }
- module_init(vdpasim_net_init);
- module_exit(vdpasim_net_exit);
- MODULE_VERSION(DRV_VERSION);
- MODULE_LICENSE(DRV_LICENSE);
- MODULE_AUTHOR(DRV_AUTHOR);
- MODULE_DESCRIPTION(DRV_DESC);
|