:p
atchew
Login
Based-on: <20240915-queue-v1-0-b49bd49b926d@daynix.com> ("[PATCH 0/7] virtio-net fixes") I'm proposing to add a feature to offload virtio-net RSS/hash report to Linux. This series contain patches to utilize the proposed Linux feature. The patches for Linux are available at: https://lore.kernel.org/r/20240915-rss-v3-0-c630015db082@daynix.com/ This work will be presented at LPC 2024: https://lpc.events/event/18/contributions/1963/ --- Akihiko Odaki (11): qdev-properties: DEFINE_PROP_ON_OFF_AUTO_BIT() net/vhost-vdpa: Report hashing capability virtio-net: Move virtio_net_get_features() down virtio-net: Retrieve peer hashing capability net/vhost-vdpa: Remove dummy SetSteeringEBPF virtio-net: Add hash type options net: Allow configuring virtio hashing virtio-net: Use qemu_set_vnet_hash() virtio-net: Offload hashing without vhost tap: Report virtio-net hashing support on Linux docs/devel/ebpf_rss.rst: Update for peer RSS docs/devel/ebpf_rss.rst | 23 ++- include/hw/qdev-properties.h | 18 +++ include/hw/virtio/virtio-net.h | 6 +- include/net/net.h | 20 +++ net/tap-linux.h | 2 + net/tap_int.h | 3 + hw/core/qdev-properties.c | 66 ++++++++- hw/net/virtio-net.c | 327 +++++++++++++++++++++++++++++------------ net/net.c | 14 ++ net/tap-bsd.c | 10 ++ net/tap-linux.c | 18 +++ net/tap-solaris.c | 10 ++ net/tap-stub.c | 10 ++ net/tap.c | 15 ++ net/vhost-vdpa.c | 41 +++++- 15 files changed, 473 insertions(+), 110 deletions(-) --- base-commit: decf357a35b1201b34cc37c47b4b027f9601855e change-id: 20240828-hash-628329a45d4d Best regards, -- Akihiko Odaki <akihiko.odaki@daynix.com>
Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/hw/qdev-properties.h | 18 ++++++++++++ hw/core/qdev-properties.c | 66 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -XXX,XX +XXX,XX @@ struct PropertyInfo { ObjectPropertyRelease *release; }; +/** + * struct OnOffAutoBit - OnOffAuto storage with 64 elements. + * @on_bits: Bitmap of elements with "on". + * @auto_bits: Bitmap of elements with "auto". + */ +typedef struct OnOffAutoBit { + uint32_t on_bits; + uint32_t auto_bits; +} OnOffAutoBit; + /*** qdev-properties.c ***/ extern const PropertyInfo qdev_prop_bit; +extern const PropertyInfo qdev_prop_on_off_auto_bit; extern const PropertyInfo qdev_prop_bit64; extern const PropertyInfo qdev_prop_bool; extern const PropertyInfo qdev_prop_enum; @@ -XXX,XX +XXX,XX @@ extern const PropertyInfo qdev_prop_link; .set_default = true, \ .defval.u = (bool)_defval) +#define DEFINE_PROP_ON_OFF_AUTO_BIT(_name, _state, _field, _bit, _defval) \ + DEFINE_PROP(_name, _state, _field, qdev_prop_on_off_auto_bit, \ + OnOffAutoBit, \ + .bitnr = (_bit), \ + .set_default = true, \ + .defval.i = (OnOffAuto)_defval) + #define DEFINE_PROP_UNSIGNED(_name, _state, _field, _defval, _prop, _type) \ DEFINE_PROP(_name, _state, _field, _prop, _type, \ .set_default = true, \ diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index XXXXXXX..XXXXXXX 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -XXX,XX +XXX,XX @@ const PropertyInfo qdev_prop_enum = { static uint32_t qdev_get_prop_mask(Property *prop) { - assert(prop->info == &qdev_prop_bit); + assert(prop->info == &qdev_prop_bit || + prop->info == &qdev_prop_on_off_auto_bit); return 0x1 << prop->bitnr; } @@ -XXX,XX +XXX,XX @@ const PropertyInfo qdev_prop_bit = { .set_default_value = set_default_value_bool, }; +static void prop_get_on_off_auto_bit(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + Property *prop = opaque; + OnOffAutoBit *p = object_field_prop_ptr(obj, prop); + int value; + uint32_t mask = qdev_get_prop_mask(prop); + + if (p->auto_bits & mask) { + value = ON_OFF_AUTO_AUTO; + } else if (p->on_bits & mask) { + value = ON_OFF_AUTO_ON; + } else { + value = ON_OFF_AUTO_OFF; + } + + visit_type_enum(v, name, &value, &OnOffAuto_lookup, errp); +} + +static void prop_set_on_off_auto_bit(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + Property *prop = opaque; + OnOffAutoBit *p = object_field_prop_ptr(obj, prop); + bool bool_value; + int value; + uint32_t mask = qdev_get_prop_mask(prop); + + if (visit_type_bool(v, name, &bool_value, NULL)) { + value = bool_value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF; + } else if (!visit_type_enum(v, name, &value, &OnOffAuto_lookup, errp)) { + return; + } + + switch (value) { + case ON_OFF_AUTO_AUTO: + p->on_bits &= ~mask; + p->auto_bits |= mask; + break; + + case ON_OFF_AUTO_ON: + p->on_bits |= mask; + p->auto_bits &= ~mask; + break; + + case ON_OFF_AUTO_OFF: + p->on_bits &= ~mask; + p->auto_bits &= ~mask; + break; + } +} + +const PropertyInfo qdev_prop_on_off_auto_bit = { + .name = "OnOffAuto", + .description = "on/off/auto", + .enum_table = &OnOffAuto_lookup, + .get = prop_get_on_off_auto_bit, + .set = prop_set_on_off_auto_bit, + .set_default_value = qdev_propinfo_set_default_value_enum, +}; + /* Bit64 */ static uint64_t qdev_get_prop_mask64(Property *prop) -- 2.46.0
Report hashing capability so that virtio-net can deliver the correct capability information to the guest. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/net/net.h | 3 +++ net/net.c | 9 +++++++++ net/vhost-vdpa.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/include/net/net.h b/include/net/net.h index XXXXXXX..XXXXXXX 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -XXX,XX +XXX,XX @@ typedef bool (HasVnetHdrLen)(NetClientState *, int); typedef void (SetOffload)(NetClientState *, int, int, int, int, int, int, int); typedef int (GetVnetHdrLen)(NetClientState *); typedef void (SetVnetHdrLen)(NetClientState *, int); +typedef bool (GetVnetHashSupportedTypes)(NetClientState *, uint32_t *); typedef int (SetVnetLE)(NetClientState *, bool); typedef int (SetVnetBE)(NetClientState *, bool); typedef struct SocketReadState SocketReadState; @@ -XXX,XX +XXX,XX @@ typedef struct NetClientInfo { SetVnetHdrLen *set_vnet_hdr_len; SetVnetLE *set_vnet_le; SetVnetBE *set_vnet_be; + GetVnetHashSupportedTypes *get_vnet_hash_supported_types; NetAnnounce *announce; SetSteeringEBPF *set_steering_ebpf; NetCheckPeerType *check_peer_type; @@ -XXX,XX +XXX,XX @@ void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6, int ecn, int ufo, int uso4, int uso6); int qemu_get_vnet_hdr_len(NetClientState *nc); void qemu_set_vnet_hdr_len(NetClientState *nc, int len); +bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types); int qemu_set_vnet_le(NetClientState *nc, bool is_le); int qemu_set_vnet_be(NetClientState *nc, bool is_be); void qemu_macaddr_default_if_unset(MACAddr *macaddr); diff --git a/net/net.c b/net/net.c index XXXXXXX..XXXXXXX 100644 --- a/net/net.c +++ b/net/net.c @@ -XXX,XX +XXX,XX @@ void qemu_set_vnet_hdr_len(NetClientState *nc, int len) nc->info->set_vnet_hdr_len(nc, len); } +bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types) +{ + if (!nc || !nc->info->get_vnet_hash_supported_types) { + return false; + } + + return nc->info->get_vnet_hash_supported_types(nc, types); +} + int qemu_set_vnet_le(NetClientState *nc, bool is_le) { #if HOST_BIG_ENDIAN diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc) return true; } +static bool vhost_vdpa_get_vnet_hash_supported_types(NetClientState *nc, + uint32_t *types) +{ + assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); + VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc); + uint64_t features = s->vhost_vdpa.dev->features; + int fd = s->vhost_vdpa.shared->device_fd; + struct { + struct vhost_vdpa_config hdr; + uint32_t supported_hash_types; + } config; + + if (!virtio_has_feature(features, VIRTIO_NET_F_HASH_REPORT) && + !virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + return false; + } + + config.hdr.off = offsetof(struct virtio_net_config, supported_hash_types); + config.hdr.len = sizeof(config.supported_hash_types); + + assert(!ioctl(fd, VHOST_VDPA_GET_CONFIG, &config)); + *types = le32_to_cpu(config.supported_hash_types); + + return true; +} + static bool vhost_vdpa_has_ufo(NetClientState *nc) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_info = { .stop = vhost_vdpa_net_client_stop, .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, + .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .stop = vhost_vdpa_net_cvq_stop, .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, + .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, -- 2.46.0
Move virtio_net_get_features() to the later part of the file so that it can call other functions. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- hw/net/virtio-net.c | 146 ++++++++++++++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_set_queue_pairs(VirtIONet *n) static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue); -static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, - Error **errp) -{ - VirtIONet *n = VIRTIO_NET(vdev); - NetClientState *nc = qemu_get_queue(n->nic); - - /* Firstly sync all virtio-net possible supported features */ - features |= n->host_features; - - virtio_add_feature(&features, VIRTIO_NET_F_MAC); - - if (!peer_has_vnet_hdr(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_CSUM); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN); - - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN); - - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); - - virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); - } - - if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO); - } - - if (!peer_has_uso(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); - } - - if (!get_vhost_net(nc->peer)) { - return features; - } - - if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); - } - features = vhost_net_get_features(get_vhost_net(nc->peer), features); - vdev->backend_features = features; - - if (n->mtu_bypass_backend && - (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { - features |= (1ULL << VIRTIO_NET_F_MTU); - } - - /* - * Since GUEST_ANNOUNCE is emulated the feature bit could be set without - * enabled. This happens in the vDPA case. - * - * Make sure the feature set is not incoherent, as the driver could refuse - * to start. - * - * TODO: QEMU is able to emulate a CVQ just for guest_announce purposes, - * helping guest to notify the new location with vDPA devices that does not - * support it. - */ - if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_CTRL_VQ)) { - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ANNOUNCE); - } - - return features; -} - static uint64_t virtio_net_bad_features(VirtIODevice *vdev) { uint64_t features = 0; @@ -XXX,XX +XXX,XX @@ static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue) virtio_net_set_queue_pairs(n); } +static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, + Error **errp) +{ + VirtIONet *n = VIRTIO_NET(vdev); + NetClientState *nc = qemu_get_queue(n->nic); + + /* Firstly sync all virtio-net possible supported features */ + features |= n->host_features; + + virtio_add_feature(&features, VIRTIO_NET_F_MAC); + + if (!peer_has_vnet_hdr(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_CSUM); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN); + + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN); + + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); + + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + } + + if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO); + } + + if (!peer_has_uso(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); + } + + if (!get_vhost_net(nc->peer)) { + return features; + } + + if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } + features = vhost_net_get_features(get_vhost_net(nc->peer), features); + vdev->backend_features = features; + + if (n->mtu_bypass_backend && + (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { + features |= (1ULL << VIRTIO_NET_F_MTU); + } + + /* + * Since GUEST_ANNOUNCE is emulated the feature bit could be set without + * enabled. This happens in the vDPA case. + * + * Make sure the feature set is not incoherent, as the driver could refuse + * to start. + * + * TODO: QEMU is able to emulate a CVQ just for guest_announce purposes, + * helping guest to notify the new location with vDPA devices that does not + * support it. + */ + if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_CTRL_VQ)) { + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ANNOUNCE); + } + + return features; +} + static int virtio_net_post_load_device(void *opaque, int version_id) { VirtIONet *n = opaque; -- 2.46.0
Retrieve peer hashing capability instead of hardcoding. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/hw/virtio/virtio-net.h | 5 +++- hw/net/virtio-net.c | 67 ++++++++++++++++++++++++++++++++++-------- net/vhost-vdpa.c | 4 +-- 3 files changed, 60 insertions(+), 16 deletions(-) diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -XXX,XX +XXX,XX @@ typedef struct VirtioNetRssData { bool enabled_software_rss; bool redirect; bool populate_hash; - uint32_t hash_types; + bool peer_hash_available; + uint32_t runtime_hash_types; + uint32_t supported_hash_types; + uint32_t peer_hash_types; uint8_t key[VIRTIO_NET_RSS_MAX_KEY_SIZE]; uint16_t indirections_len; uint16_t *indirections_table; diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_get_config(VirtIODevice *vdev, uint8_t *config) virtio_host_has_feature(vdev, VIRTIO_NET_F_RSS) ? VIRTIO_NET_RSS_MAX_TABLE_LEN : 1); virtio_stl_p(vdev, &netcfg.supported_hash_types, - VIRTIO_NET_RSS_SUPPORTED_HASHES); + n->rss_data.supported_hash_types); memcpy(config, &netcfg, n->config_size); /* @@ -XXX,XX +XXX,XX @@ static void rss_data_to_rss_config(struct VirtioNetRssData *data, { config->redirect = data->redirect; config->populate_hash = data->populate_hash; - config->hash_types = data->hash_types; + config->hash_types = data->runtime_hash_types; config->indirections_len = data->indirections_len; config->default_queue = data->default_queue; } @@ -XXX,XX +XXX,XX @@ static void virtio_net_detach_epbf_rss(VirtIONet *n) static void virtio_net_commit_rss_config(VirtIONet *n) { + if (n->rss_data.peer_hash_available) { + return; + } + if (n->rss_data.enabled) { n->rss_data.enabled_software_rss = n->rss_data.populate_hash; if (n->rss_data.populate_hash) { @@ -XXX,XX +XXX,XX @@ static void virtio_net_commit_rss_config(VirtIONet *n) } } - trace_virtio_net_rss_enable(n->rss_data.hash_types, + trace_virtio_net_rss_enable(n->rss_data.runtime_hash_types, n->rss_data.indirections_len, sizeof(n->rss_data.key)); } else { @@ -XXX,XX +XXX,XX @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, err_value = (uint32_t)s; goto error; } - n->rss_data.hash_types = virtio_ldl_p(vdev, &cfg.hash_types); + n->rss_data.runtime_hash_types = virtio_ldl_p(vdev, &cfg.hash_types); n->rss_data.indirections_len = virtio_lduw_p(vdev, &cfg.indirection_table_mask); n->rss_data.indirections_len++; @@ -XXX,XX +XXX,XX @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, err_value = temp.b; goto error; } - if (!temp.b && n->rss_data.hash_types) { + if (!temp.b && n->rss_data.runtime_hash_types) { err_msg = "No key provided"; err_value = 0; goto error; } - if (!temp.b && !n->rss_data.hash_types) { + if (!temp.b && !n->rss_data.runtime_hash_types) { virtio_net_disable_rss(n); return queue_pairs; } @@ -XXX,XX +XXX,XX @@ static int virtio_net_process_rss(NetClientState *nc, const uint8_t *buf, net_rx_pkt_set_protocols(pkt, &iov, 1, n->host_hdr_len); net_rx_pkt_get_protocols(pkt, &hasip4, &hasip6, &l4hdr_proto); net_hash_type = virtio_net_get_hash_type(hasip4, hasip6, l4hdr_proto, - n->rss_data.hash_types); + n->rss_data.runtime_hash_types); if (net_hash_type > NetPktRssIpV6UdpEx) { if (n->rss_data.populate_hash) { hdr->hash_value = VIRTIO_NET_HASH_REPORT_NONE; @@ -XXX,XX +XXX,XX @@ static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, { VirtIONet *n = VIRTIO_NET(vdev); NetClientState *nc = qemu_get_queue(n->nic); + uint32_t supported_hash_types = n->rss_data.supported_hash_types; + uint32_t peer_hash_types = n->rss_data.peer_hash_types; + bool use_own_hash = + (supported_hash_types & VIRTIO_NET_RSS_SUPPORTED_HASHES) == + supported_hash_types; + bool use_peer_hash = + n->rss_data.peer_hash_available && + (supported_hash_types & peer_hash_types) == supported_hash_types; /* Firstly sync all virtio-net possible supported features */ features |= n->host_features; @@ -XXX,XX +XXX,XX @@ static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, } if (!get_vhost_net(nc->peer)) { + if (!use_own_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + virtio_net_load_ebpf(n); + } + return features; } - if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + if (!use_peer_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + + if (!use_own_hash || !virtio_net_load_ebpf(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } } + features = vhost_net_get_features(get_vhost_net(nc->peer), features); vdev->backend_features = features; @@ -XXX,XX +XXX,XX @@ static const VMStateDescription vmstate_virtio_net_has_vnet = { }, }; +static int virtio_net_rss_post_load(void *opaque, int version_id) +{ + VirtIONet *n = VIRTIO_NET(opaque); + + if (version_id == 1) { + n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; + } + + return 0; +} + static bool virtio_net_rss_needed(void *opaque) { return VIRTIO_NET(opaque)->rss_data.enabled; @@ -XXX,XX +XXX,XX @@ static bool virtio_net_rss_needed(void *opaque) static const VMStateDescription vmstate_virtio_net_rss = { .name = "virtio-net-device/rss", - .version_id = 1, + .version_id = 2, .minimum_version_id = 1, + .post_load = virtio_net_rss_post_load, .needed = virtio_net_rss_needed, .fields = (const VMStateField[]) { VMSTATE_BOOL(rss_data.enabled, VirtIONet), VMSTATE_BOOL(rss_data.redirect, VirtIONet), VMSTATE_BOOL(rss_data.populate_hash, VirtIONet), - VMSTATE_UINT32(rss_data.hash_types, VirtIONet), + VMSTATE_UINT32(rss_data.runtime_hash_types, VirtIONet), + VMSTATE_UINT32_V(rss_data.supported_hash_types, VirtIONet, 2), VMSTATE_UINT16(rss_data.indirections_len, VirtIONet), VMSTATE_UINT16(rss_data.default_queue, VirtIONet), VMSTATE_UINT8_ARRAY(rss_data.key, VirtIONet, @@ -XXX,XX +XXX,XX @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) net_rx_pkt_init(&n->rx_pkt); - if (virtio_has_feature(n->host_features, VIRTIO_NET_F_RSS)) { - virtio_net_load_ebpf(n); + if (qemu_get_vnet_hash_supported_types(qemu_get_queue(n->nic)->peer, + &n->rss_data.peer_hash_types)) { + n->rss_data.peer_hash_available = true; + n->rss_data.supported_hash_types = n->rss_data.peer_hash_types; + } else { + n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; } } diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static int vhost_vdpa_net_load_rss(VhostVDPAState *s, const VirtIONet *n, * configuration only at live migration. */ if (!n->rss_data.enabled || - n->rss_data.hash_types == VIRTIO_NET_HASH_REPORT_NONE) { + n->rss_data.runtime_hash_types == VIRTIO_NET_HASH_REPORT_NONE) { return 0; } table = g_malloc_n(n->rss_data.indirections_len, sizeof(n->rss_data.indirections_table[0])); - cfg.hash_types = cpu_to_le32(n->rss_data.hash_types); + cfg.hash_types = cpu_to_le32(n->rss_data.runtime_hash_types); if (do_rss) { /* -- 2.46.0
It is no longer used. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- net/vhost-vdpa.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static void vhost_vdpa_cleanup(NetClientState *nc) g_free(s->vhost_vdpa.shared); } -/** Dummy SetSteeringEBPF to support RSS for vhost-vdpa backend */ -static bool vhost_vdpa_set_steering_ebpf(NetClientState *nc, int prog_fd) -{ - return true; -} - static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_info = { .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, - .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, }; static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index, @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, - .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, }; /* -- 2.46.0
By default, virtio-net limits the hash types that will be advertised to the guest so that all hash types are covered by the offloading capability the client provides. This change allows to override this behavior and to advertise hash types that require user-space hash calculation by specifying "on" for the corresponding properties. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/hw/virtio/virtio-net.h | 1 + hw/net/virtio-net.c | 45 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -XXX,XX +XXX,XX @@ typedef struct VirtioNetRssData { uint32_t runtime_hash_types; uint32_t supported_hash_types; uint32_t peer_hash_types; + OnOffAutoBit specified_hash_types; uint8_t key[VIRTIO_NET_RSS_MAX_KEY_SIZE]; uint16_t indirections_len; uint16_t *indirections_table; diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) if (qemu_get_vnet_hash_supported_types(qemu_get_queue(n->nic)->peer, &n->rss_data.peer_hash_types)) { n->rss_data.peer_hash_available = true; - n->rss_data.supported_hash_types = n->rss_data.peer_hash_types; + n->rss_data.supported_hash_types = + n->rss_data.specified_hash_types.on_bits | + (n->rss_data.specified_hash_types.auto_bits & + n->rss_data.peer_hash_types); } else { - n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; + n->rss_data.supported_hash_types = + n->rss_data.specified_hash_types.on_bits | + n->rss_data.specified_hash_types.auto_bits; } } @@ -XXX,XX +XXX,XX @@ static Property virtio_net_properties[] = { VIRTIO_NET_F_GUEST_USO6, true), DEFINE_PROP_BIT64("host_uso", VirtIONet, host_features, VIRTIO_NET_F_HOST_USO, true), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-ipv4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-tcp4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-udp4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-ipv6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-tcp6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-udp6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-ipv6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv6_EX - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-tcp6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv6_EX - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT("hash-udp6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv6_EX - 1, + ON_OFF_AUTO_AUTO), DEFINE_PROP_END_OF_LIST(), }; -- 2.46.0
This adds set_vnet_hash() to configure virtio hashing and implements it for Linux's tap. vDPA will have an empty function as configuring virtio hashing is done with the load(). Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/net/net.h | 17 +++++++++++++++++ net/tap-linux.h | 1 + net/tap_int.h | 2 ++ net/net.c | 5 +++++ net/tap-bsd.c | 5 +++++ net/tap-linux.c | 5 +++++ net/tap-solaris.c | 5 +++++ net/tap-stub.c | 5 +++++ net/tap.c | 7 +++++++ net/vhost-vdpa.c | 7 +++++++ 10 files changed, 59 insertions(+) diff --git a/include/net/net.h b/include/net/net.h index XXXXXXX..XXXXXXX 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -XXX,XX +XXX,XX @@ typedef struct NICConf { int32_t bootindex; } NICConf; +#define NET_VNET_HASH_REPORT 1 +#define NET_VNET_HASH_RSS 2 + +typedef struct NetVnetHash { + uint16_t flags; + uint8_t pad[2]; + uint32_t types; +} NetVnetHash; + +typedef struct NetVnetHashRss { + uint16_t indirection_table_mask; + uint16_t unclassified_queue; +} NetVnetHashRss; + #define DEFINE_NIC_PROPERTIES(_state, _conf) \ DEFINE_PROP_MACADDR("mac", _state, _conf.macaddr), \ DEFINE_PROP_NETDEV("netdev", _state, _conf.peers) @@ -XXX,XX +XXX,XX @@ typedef void (SetOffload)(NetClientState *, int, int, int, int, int, int, int); typedef int (GetVnetHdrLen)(NetClientState *); typedef void (SetVnetHdrLen)(NetClientState *, int); typedef bool (GetVnetHashSupportedTypes)(NetClientState *, uint32_t *); +typedef void (SetVnetHash)(NetClientState *, const NetVnetHash *); typedef int (SetVnetLE)(NetClientState *, bool); typedef int (SetVnetBE)(NetClientState *, bool); typedef struct SocketReadState SocketReadState; @@ -XXX,XX +XXX,XX @@ typedef struct NetClientInfo { SetVnetLE *set_vnet_le; SetVnetBE *set_vnet_be; GetVnetHashSupportedTypes *get_vnet_hash_supported_types; + SetVnetHash *set_vnet_hash; NetAnnounce *announce; SetSteeringEBPF *set_steering_ebpf; NetCheckPeerType *check_peer_type; @@ -XXX,XX +XXX,XX @@ void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6, int qemu_get_vnet_hdr_len(NetClientState *nc); void qemu_set_vnet_hdr_len(NetClientState *nc, int len); bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types); +void qemu_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash); int qemu_set_vnet_le(NetClientState *nc, bool is_le); int qemu_set_vnet_be(NetClientState *nc, bool is_be); void qemu_macaddr_default_if_unset(MACAddr *macaddr); diff --git a/net/tap-linux.h b/net/tap-linux.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.h +++ b/net/tap-linux.h @@ -XXX,XX +XXX,XX @@ #define TUNSETVNETLE _IOW('T', 220, int) #define TUNSETVNETBE _IOW('T', 222, int) #define TUNSETSTEERINGEBPF _IOR('T', 224, int) +#define TUNSETVNETHASH _IOW('T', 229, NetVnetHash) #endif diff --git a/net/tap_int.h b/net/tap_int.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -XXX,XX +XXX,XX @@ #define NET_TAP_INT_H #include "qapi/qapi-types-net.h" +#include "net/net.h" int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required, int mq_required, Error **errp); @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd); void tap_fd_set_offload(int fd, int csum, int tso4, int tso6, int ecn, int ufo, int uso4, int uso6); void tap_fd_set_vnet_hdr_len(int fd, int len); +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash); int tap_fd_set_vnet_le(int fd, int vnet_is_le); int tap_fd_set_vnet_be(int fd, int vnet_is_be); int tap_fd_enable(int fd); diff --git a/net/net.c b/net/net.c index XXXXXXX..XXXXXXX 100644 --- a/net/net.c +++ b/net/net.c @@ -XXX,XX +XXX,XX @@ bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types) return nc->info->get_vnet_hash_supported_types(nc, types); } +void qemu_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash) +{ + nc->info->set_vnet_hash(nc, hash); +} + int qemu_set_vnet_le(NetClientState *nc, bool is_le) { #if HOST_BIG_ENDIAN diff --git a/net/tap-bsd.c b/net/tap-bsd.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) { } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + g_assert_not_reached(); +} + int tap_fd_set_vnet_le(int fd, int is_le) { return -EINVAL; diff --git a/net/tap-linux.c b/net/tap-linux.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) } } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + assert(!ioctl(fd, TUNSETVNETHASH, hash)); +} + int tap_fd_set_vnet_le(int fd, int is_le) { int arg = is_le ? 1 : 0; diff --git a/net/tap-solaris.c b/net/tap-solaris.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) { } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + g_assert_not_reached(); +} + int tap_fd_set_vnet_le(int fd, int is_le) { return -EINVAL; diff --git a/net/tap-stub.c b/net/tap-stub.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) { } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + g_assert_not_reached(); +} + int tap_fd_set_vnet_le(int fd, int is_le) { return -EINVAL; diff --git a/net/tap.c b/net/tap.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap.c +++ b/net/tap.c @@ -XXX,XX +XXX,XX @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len) s->using_vnet_hdr = true; } +static void tap_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + return tap_fd_set_vnet_hash(s->fd, hash); +} + static int tap_set_vnet_le(NetClientState *nc, bool is_le) { TAPState *s = DO_UPCAST(TAPState, nc, nc); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_tap_info = { .has_vnet_hdr_len = tap_has_vnet_hdr_len, .set_offload = tap_set_offload, .set_vnet_hdr_len = tap_set_vnet_hdr_len, + .set_vnet_hash = tap_set_vnet_hash, .set_vnet_le = tap_set_vnet_le, .set_vnet_be = tap_set_vnet_be, .set_steering_ebpf = tap_set_steering_ebpf, diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static bool vhost_vdpa_get_vnet_hash_supported_types(NetClientState *nc, return true; } +static void vhost_vdpa_set_vnet_hash(NetClientState *nc, + const NetVnetHash *hash) +{ +} + static bool vhost_vdpa_has_ufo(NetClientState *nc) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_info = { .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, + .set_vnet_hash = vhost_vdpa_set_vnet_hash, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, }; @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, + .set_vnet_hash = vhost_vdpa_set_vnet_hash, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, }; -- 2.46.0
This is necessary to offload hashing to tap. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- hw/net/virtio-net.c | 77 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_detach_epbf_rss(VirtIONet *n) static void virtio_net_commit_rss_config(VirtIONet *n) { - if (n->rss_data.peer_hash_available) { - return; - } - if (n->rss_data.enabled) { - n->rss_data.enabled_software_rss = n->rss_data.populate_hash; - if (n->rss_data.populate_hash) { - virtio_net_detach_epbf_rss(n); - } else if (!virtio_net_attach_epbf_rss(n)) { - if (get_vhost_net(qemu_get_queue(n->nic)->peer)) { - warn_report("Can't load eBPF RSS for vhost"); + if (n->rss_data.peer_hash_available && + (n->rss_data.peer_hash_types & n->rss_data.runtime_hash_types) == + n->rss_data.runtime_hash_types) { + NetVnetHash hash = { + .flags = (n->rss_data.redirect ? NET_VNET_HASH_RSS : 0) | + (n->rss_data.populate_hash ? NET_VNET_HASH_REPORT : 0), + .types = n->rss_data.runtime_hash_types + }; + + if (n->rss_data.redirect) { + size_t indirection_table_size = + n->rss_data.indirections_len * + sizeof(*n->rss_data.indirections_table); + + size_t hash_size = sizeof(NetVnetHash) + + sizeof(NetVnetHashRss) + + indirection_table_size + + sizeof(n->rss_data.key); + + g_autofree struct { + NetVnetHash hdr; + NetVnetHashRss rss; + uint8_t footer[]; + } *rss = g_malloc(hash_size); + + rss->hdr = hash; + rss->rss.indirection_table_mask = + n->rss_data.indirections_len - 1; + rss->rss.unclassified_queue = n->rss_data.default_queue; + + memcpy(rss->footer, n->rss_data.indirections_table, + indirection_table_size); + + memcpy(rss->footer + indirection_table_size, n->rss_data.key, + sizeof(n->rss_data.key)); + + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &rss->hdr); } else { - warn_report("Can't load eBPF RSS - fallback to software RSS"); - n->rss_data.enabled_software_rss = true; + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &hash); + } + + n->rss_data.enabled_software_rss = false; + } else { + if (n->rss_data.peer_hash_available) { + NetVnetHash hash = { .flags = 0 }; + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &hash); + } + + n->rss_data.enabled_software_rss = n->rss_data.populate_hash; + if (n->rss_data.populate_hash) { + virtio_net_detach_epbf_rss(n); + } else if (!virtio_net_attach_epbf_rss(n)) { + if (get_vhost_net(qemu_get_queue(n->nic)->peer)) { + warn_report("Can't load eBPF RSS for vhost"); + } else { + warn_report("Can't load eBPF RSS - fallback to software RSS"); + n->rss_data.enabled_software_rss = true; + } } } @@ -XXX,XX +XXX,XX @@ static void virtio_net_commit_rss_config(VirtIONet *n) n->rss_data.indirections_len, sizeof(n->rss_data.key)); } else { - virtio_net_detach_epbf_rss(n); + if (n->rss_data.peer_hash_available) { + NetVnetHash hash = { .flags = 0 }; + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &hash); + } else { + virtio_net_detach_epbf_rss(n); + } + trace_virtio_net_rss_disable(); } } -- 2.46.0
This is necessary to offload hashing to tap. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- hw/net/virtio-net.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static size_t receive_header(VirtIONet *n, struct virtio_net_hdr *hdr, { size_t hdr_len = n->guest_hdr_len; - memcpy(hdr, buf, sizeof(struct virtio_net_hdr)); + memcpy(hdr, buf, + n->rss_data.populate_hash && + n->rss_data.enabled && !n->rss_data.enabled_software_rss ? + sizeof(struct virtio_net_hdr_v1_hash) : + sizeof(struct virtio_net_hdr)); *buf_offset = n->host_hdr_len; work_around_broken_dhclient(hdr, &hdr_len, buf, buf_size, buf_offset); @@ -XXX,XX +XXX,XX @@ static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, } if (!get_vhost_net(nc->peer)) { - if (!use_own_hash) { - virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); - } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { - virtio_net_load_ebpf(n); + if (!use_peer_hash) { + if (!use_own_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + virtio_net_load_ebpf(n); + } } return features; -- 2.46.0
This allows offloading virtio-net hashing to tap on Linux. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- net/tap-linux.h | 1 + net/tap_int.h | 1 + net/tap-bsd.c | 5 +++++ net/tap-linux.c | 13 +++++++++++++ net/tap-solaris.c | 5 +++++ net/tap-stub.c | 5 +++++ net/tap.c | 8 ++++++++ 7 files changed, 38 insertions(+) diff --git a/net/tap-linux.h b/net/tap-linux.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.h +++ b/net/tap-linux.h @@ -XXX,XX +XXX,XX @@ #define TUNSETVNETLE _IOW('T', 220, int) #define TUNSETVNETBE _IOW('T', 222, int) #define TUNSETSTEERINGEBPF _IOR('T', 224, int) +#define TUNGETVNETHASHCAP _IOR('T', 228, NetVnetHash) #define TUNSETVNETHASH _IOW('T', 229, NetVnetHash) #endif diff --git a/net/tap_int.h b/net/tap_int.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -XXX,XX +XXX,XX @@ ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen); void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp); int tap_probe_vnet_hdr(int fd, Error **errp); +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types); int tap_probe_has_ufo(int fd); int tap_probe_has_uso(int fd); void tap_fd_set_offload(int fd, int csum, int tso4, int tso6, int ecn, int ufo, diff --git a/net/tap-bsd.c b/net/tap-bsd.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 0; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + return false; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { } diff --git a/net/tap-linux.c b/net/tap-linux.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 1; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + NetVnetHash hash; + + if (ioctl(fd, TUNGETVNETHASHCAP, &hash)) { + return false; + } + + *types = hash.types; + + return true; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) { diff --git a/net/tap-solaris.c b/net/tap-solaris.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 0; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + return false; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { } diff --git a/net/tap-stub.c b/net/tap-stub.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 0; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + return false; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { } diff --git a/net/tap.c b/net/tap.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap.c +++ b/net/tap.c @@ -XXX,XX +XXX,XX @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len) s->using_vnet_hdr = true; } +static bool tap_get_vnet_hash_supported_types(NetClientState *nc, + uint32_t *types) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + return tap_probe_vnet_hash_supported_types(s->fd, types); +} + static void tap_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash) { TAPState *s = DO_UPCAST(TAPState, nc, nc); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_tap_info = { .has_vnet_hdr_len = tap_has_vnet_hdr_len, .set_offload = tap_set_offload, .set_vnet_hdr_len = tap_set_vnet_hdr_len, + .get_vnet_hash_supported_types = tap_get_vnet_hash_supported_types, .set_vnet_hash = tap_set_vnet_hash, .set_vnet_le = tap_set_vnet_le, .set_vnet_be = tap_set_vnet_be, -- 2.46.0
eBPF RSS virtio-net support was written in assumption that there is only one alternative RSS implementation: 'in-qemu' RSS. It is no longer true, and we now have yet another implementation; namely the peer RSS. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- docs/devel/ebpf_rss.rst | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/devel/ebpf_rss.rst b/docs/devel/ebpf_rss.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/devel/ebpf_rss.rst +++ b/docs/devel/ebpf_rss.rst @@ -XXX,XX +XXX,XX @@ eBPF RSS virtio-net support RSS(Receive Side Scaling) is used to distribute network packets to guest virtqueues by calculating packet hash. Usually every queue is processed then by a specific guest CPU core. -For now there are 2 RSS implementations in qemu: -- 'in-qemu' RSS (functions if qemu receives network packets, i.e. vhost=off) -- eBPF RSS (can function with also with vhost=on) +For now there are 3 RSS implementations in qemu: +1. Peer RSS +2. eBPF RSS +3. 'In-QEMU' RSS + +'In-QEMU' RSS is incompatible with vhost since the packets are not routed to +QEMU. eBPF RSS requires Linux 5.8+. Peer RSS requires the peer to implement RSS. +Currently QEMU can use the RSS implementation of vDPA and Linux's TUN module +with the following patch applied: +https://lore.kernel.org/r/20240915-rss-v3-0-c630015db082@daynix.com/ + +eBPF RSS does not support hash reporting. Peer RSS may support limited hash +types. + +virtio-net automatically chooses the RSS implementation to use. Peer RSS is +the most preferred, and 'in-QEMU' RSS is the least. eBPF support (CONFIG_EBPF) is enabled by 'configure' script. To enable eBPF RSS support use './configure --enable-bpf'. @@ -XXX,XX +XXX,XX @@ eBPF RSS turned on by different combinations of vhost-net, vitrio-net and tap co tap,vhost=on & virtio-net-pci,rss=on,hash=on -If CONFIG_EBPF is not set then only 'in-qemu' RSS is supported. -Also 'in-qemu' RSS, as a fallback, is used if the eBPF program failed to load or set to TUN. - RSS eBPF program ---------------- @@ -XXX,XX +XXX,XX @@ Prerequisites to recompile the eBPF program (regenerate ebpf/rss.bpf.skeleton.h) $ make -f Makefile.ebpf Current eBPF RSS implementation uses 'bounded loops' with 'backward jump instructions' which present in the last kernels. -Overall eBPF RSS works on kernels 5.8+. eBPF RSS implementation ----------------------- -- 2.46.0
I'm proposing to add a feature to offload virtio-net RSS/hash report to Linux. This series contain patches to utilize the proposed Linux feature. The patches for Linux are available at: https://lore.kernel.org/r/20250307-rss-v9-0-df76624025eb@daynix.com/ This work was presented at LPC 2024: https://lpc.events/event/18/contributions/1963/ Patch "docs/devel/ebpf_rss.rst: Update for peer RSS" provides comparion of existing RSS mechanism and the new one (called "peer RSS") and explains how QEMU selects one. --- Changes in v4: - Rebased. - Added a reference to the documentation to the cover letter. - Link to v3: https://lore.kernel.org/r/20240915-hash-v3-0-79cb08d28647@daynix.com --- Akihiko Odaki (11): qdev-properties: Add DEFINE_PROP_ON_OFF_AUTO_BIT64() net/vhost-vdpa: Report hashing capability virtio-net: Move virtio_net_get_features() down virtio-net: Retrieve peer hashing capability net/vhost-vdpa: Remove dummy SetSteeringEBPF virtio-net: Add hash type options net: Allow configuring virtio hashing virtio-net: Use qemu_set_vnet_hash() virtio-net: Offload hashing without vhost tap: Report virtio-net hashing support on Linux docs/devel/ebpf_rss.rst: Update for peer RSS docs/devel/ebpf_rss.rst | 23 ++- include/hw/qdev-properties.h | 18 +++ include/hw/virtio/virtio-net.h | 6 +- include/net/net.h | 20 +++ net/tap-linux.h | 2 + net/tap_int.h | 3 + hw/core/qdev-properties.c | 67 ++++++++- hw/net/virtio-net.c | 331 +++++++++++++++++++++++++++++------------ net/net.c | 14 ++ net/tap-bsd.c | 10 ++ net/tap-linux.c | 18 +++ net/tap-solaris.c | 10 ++ net/tap-stub.c | 10 ++ net/tap.c | 15 ++ net/vhost-vdpa.c | 41 ++++- 15 files changed, 478 insertions(+), 110 deletions(-) --- base-commit: 825b96dbcee23d134b691fc75618b59c5f53da32 change-id: 20240828-hash-628329a45d4d Best regards, -- Akihiko Odaki <akihiko.odaki@daynix.com>
DEFINE_PROP_ON_OFF_AUTO_BIT64() corresponds to DEFINE_PROP_ON_OFF_AUTO() as DEFINE_PROP_BIT64() corresponds to DEFINE_PROP_BOOL(). The difference is that DEFINE_PROP_ON_OFF_AUTO_BIT64() exposes OnOffAuto instead of bool. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/hw/qdev-properties.h | 18 ++++++++++++ hw/core/qdev-properties.c | 67 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -XXX,XX +XXX,XX @@ struct PropertyInfo { ObjectPropertyRelease *release; }; +/** + * struct OnOffAutoBit64 - OnOffAuto storage with 64 elements. + * @on_bits: Bitmap of elements with "on". + * @auto_bits: Bitmap of elements with "auto". + */ +typedef struct OnOffAutoBit64 { + uint64_t on_bits; + uint64_t auto_bits; +} OnOffAutoBit64; + /*** qdev-properties.c ***/ extern const PropertyInfo qdev_prop_bit; extern const PropertyInfo qdev_prop_bit64; +extern const PropertyInfo qdev_prop_on_off_auto_bit64; extern const PropertyInfo qdev_prop_bool; extern const PropertyInfo qdev_prop_uint8; extern const PropertyInfo qdev_prop_uint16; @@ -XXX,XX +XXX,XX @@ extern const PropertyInfo qdev_prop_link; .set_default = true, \ .defval.u = (bool)_defval) +#define DEFINE_PROP_ON_OFF_AUTO_BIT64(_name, _state, _field, _bit, _defval) \ + DEFINE_PROP(_name, _state, _field, qdev_prop_on_off_auto_bit64, \ + OnOffAutoBit64, \ + .bitnr = (_bit), \ + .set_default = true, \ + .defval.i = (OnOffAuto)_defval) + #define DEFINE_PROP_BOOL(_name, _state, _field, _defval) \ DEFINE_PROP(_name, _state, _field, qdev_prop_bool, bool, \ .set_default = true, \ diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index XXXXXXX..XXXXXXX 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -XXX,XX +XXX,XX @@ #include "hw/qdev-properties.h" #include "qapi/error.h" #include "qapi/qapi-types-misc.h" +#include "qapi/qapi-visit-common.h" #include "qobject/qlist.h" #include "qemu/ctype.h" #include "qemu/error-report.h" @@ -XXX,XX +XXX,XX @@ const PropertyInfo qdev_prop_bit = { static uint64_t qdev_get_prop_mask64(const Property *prop) { - assert(prop->info == &qdev_prop_bit64); + assert(prop->info == &qdev_prop_bit64 || + prop->info == &qdev_prop_on_off_auto_bit64); return 0x1ull << prop->bitnr; } @@ -XXX,XX +XXX,XX @@ const PropertyInfo qdev_prop_bit64 = { .set_default_value = set_default_value_bool, }; +static void prop_get_on_off_auto_bit64(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + Property *prop = opaque; + OnOffAutoBit64 *p = object_field_prop_ptr(obj, prop); + OnOffAuto value; + uint64_t mask = qdev_get_prop_mask64(prop); + + if (p->auto_bits & mask) { + value = ON_OFF_AUTO_AUTO; + } else if (p->on_bits & mask) { + value = ON_OFF_AUTO_ON; + } else { + value = ON_OFF_AUTO_OFF; + } + + visit_type_OnOffAuto(v, name, &value, errp); +} + +static void prop_set_on_off_auto_bit64(Object *obj, Visitor *v, + const char *name, void *opaque, + Error **errp) +{ + Property *prop = opaque; + OnOffAutoBit64 *p = object_field_prop_ptr(obj, prop); + OnOffAuto value; + uint64_t mask = qdev_get_prop_mask64(prop); + + if (!visit_type_OnOffAuto(v, name, &value, errp)) { + return; + } + + switch (value) { + case ON_OFF_AUTO_AUTO: + p->on_bits &= ~mask; + p->auto_bits |= mask; + break; + + case ON_OFF_AUTO_ON: + p->on_bits |= mask; + p->auto_bits &= ~mask; + break; + + case ON_OFF_AUTO_OFF: + p->on_bits &= ~mask; + p->auto_bits &= ~mask; + break; + + case ON_OFF_AUTO__MAX: + g_assert_not_reached(); + } +} + +const PropertyInfo qdev_prop_on_off_auto_bit64 = { + .type = "OnOffAuto", + .description = "on/off/auto", + .enum_table = &OnOffAuto_lookup, + .get = prop_get_on_off_auto_bit64, + .set = prop_set_on_off_auto_bit64, + .set_default_value = qdev_propinfo_set_default_value_enum, +}; + /* --- bool --- */ static void get_bool(Object *obj, Visitor *v, const char *name, void *opaque, -- 2.48.1
Report hashing capability so that virtio-net can deliver the correct capability information to the guest. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/net/net.h | 3 +++ net/net.c | 9 +++++++++ net/vhost-vdpa.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/include/net/net.h b/include/net/net.h index XXXXXXX..XXXXXXX 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -XXX,XX +XXX,XX @@ typedef bool (HasVnetHdrLen)(NetClientState *, int); typedef void (SetOffload)(NetClientState *, int, int, int, int, int, int, int); typedef int (GetVnetHdrLen)(NetClientState *); typedef void (SetVnetHdrLen)(NetClientState *, int); +typedef bool (GetVnetHashSupportedTypes)(NetClientState *, uint32_t *); typedef int (SetVnetLE)(NetClientState *, bool); typedef int (SetVnetBE)(NetClientState *, bool); typedef struct SocketReadState SocketReadState; @@ -XXX,XX +XXX,XX @@ typedef struct NetClientInfo { SetVnetHdrLen *set_vnet_hdr_len; SetVnetLE *set_vnet_le; SetVnetBE *set_vnet_be; + GetVnetHashSupportedTypes *get_vnet_hash_supported_types; NetAnnounce *announce; SetSteeringEBPF *set_steering_ebpf; NetCheckPeerType *check_peer_type; @@ -XXX,XX +XXX,XX @@ void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6, int ecn, int ufo, int uso4, int uso6); int qemu_get_vnet_hdr_len(NetClientState *nc); void qemu_set_vnet_hdr_len(NetClientState *nc, int len); +bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types); int qemu_set_vnet_le(NetClientState *nc, bool is_le); int qemu_set_vnet_be(NetClientState *nc, bool is_be); void qemu_macaddr_default_if_unset(MACAddr *macaddr); diff --git a/net/net.c b/net/net.c index XXXXXXX..XXXXXXX 100644 --- a/net/net.c +++ b/net/net.c @@ -XXX,XX +XXX,XX @@ void qemu_set_vnet_hdr_len(NetClientState *nc, int len) nc->info->set_vnet_hdr_len(nc, len); } +bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types) +{ + if (!nc || !nc->info->get_vnet_hash_supported_types) { + return false; + } + + return nc->info->get_vnet_hash_supported_types(nc, types); +} + int qemu_set_vnet_le(NetClientState *nc, bool is_le) { #if HOST_BIG_ENDIAN diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc) return true; } +static bool vhost_vdpa_get_vnet_hash_supported_types(NetClientState *nc, + uint32_t *types) +{ + assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); + VhostVDPAState *s = DO_UPCAST(VhostVDPAState, nc, nc); + uint64_t features = s->vhost_vdpa.dev->features; + int fd = s->vhost_vdpa.shared->device_fd; + struct { + struct vhost_vdpa_config hdr; + uint32_t supported_hash_types; + } config; + + if (!virtio_has_feature(features, VIRTIO_NET_F_HASH_REPORT) && + !virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + return false; + } + + config.hdr.off = offsetof(struct virtio_net_config, supported_hash_types); + config.hdr.len = sizeof(config.supported_hash_types); + + assert(!ioctl(fd, VHOST_VDPA_GET_CONFIG, &config)); + *types = le32_to_cpu(config.supported_hash_types); + + return true; +} + static bool vhost_vdpa_has_ufo(NetClientState *nc) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_info = { .stop = vhost_vdpa_net_client_stop, .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, + .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .stop = vhost_vdpa_net_cvq_stop, .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, + .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, -- 2.48.1
Move virtio_net_get_features() to the later part of the file so that it can call other functions. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- hw/net/virtio-net.c | 146 ++++++++++++++++++++++++++-------------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_set_queue_pairs(VirtIONet *n) static void virtio_net_set_multiqueue(VirtIONet *n, int multiqueue); -static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, - Error **errp) -{ - VirtIONet *n = VIRTIO_NET(vdev); - NetClientState *nc = qemu_get_queue(n->nic); - - /* Firstly sync all virtio-net possible supported features */ - features |= n->host_features; - - virtio_add_feature(&features, VIRTIO_NET_F_MAC); - - if (!peer_has_vnet_hdr(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_CSUM); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN); - - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN); - - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); - - virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); - } - - if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO); - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO); - } - - if (!peer_has_uso(n)) { - virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); - } - - if (!get_vhost_net(nc->peer)) { - return features; - } - - if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); - } - features = vhost_net_get_features(get_vhost_net(nc->peer), features); - vdev->backend_features = features; - - if (n->mtu_bypass_backend && - (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { - features |= (1ULL << VIRTIO_NET_F_MTU); - } - - /* - * Since GUEST_ANNOUNCE is emulated the feature bit could be set without - * enabled. This happens in the vDPA case. - * - * Make sure the feature set is not incoherent, as the driver could refuse - * to start. - * - * TODO: QEMU is able to emulate a CVQ just for guest_announce purposes, - * helping guest to notify the new location with vDPA devices that does not - * support it. - */ - if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_CTRL_VQ)) { - virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ANNOUNCE); - } - - return features; -} - static uint64_t virtio_net_bad_features(VirtIODevice *vdev) { uint64_t features = 0; @@ -XXX,XX +XXX,XX @@ static int virtio_net_pre_load_queues(VirtIODevice *vdev) return 0; } +static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, + Error **errp) +{ + VirtIONet *n = VIRTIO_NET(vdev); + NetClientState *nc = qemu_get_queue(n->nic); + + /* Firstly sync all virtio-net possible supported features */ + features |= n->host_features; + + virtio_add_feature(&features, VIRTIO_NET_F_MAC); + + if (!peer_has_vnet_hdr(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_CSUM); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO4); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_TSO6); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_ECN); + + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_CSUM); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_TSO6); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ECN); + + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); + + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + } + + if (!peer_has_vnet_hdr(n) || !peer_has_ufo(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_UFO); + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_UFO); + } + + if (!peer_has_uso(n)) { + virtio_clear_feature(&features, VIRTIO_NET_F_HOST_USO); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO4); + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_USO6); + } + + if (!get_vhost_net(nc->peer)) { + return features; + } + + if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } + features = vhost_net_get_features(get_vhost_net(nc->peer), features); + vdev->backend_features = features; + + if (n->mtu_bypass_backend && + (n->host_features & 1ULL << VIRTIO_NET_F_MTU)) { + features |= (1ULL << VIRTIO_NET_F_MTU); + } + + /* + * Since GUEST_ANNOUNCE is emulated the feature bit could be set without + * enabled. This happens in the vDPA case. + * + * Make sure the feature set is not incoherent, as the driver could refuse + * to start. + * + * TODO: QEMU is able to emulate a CVQ just for guest_announce purposes, + * helping guest to notify the new location with vDPA devices that does not + * support it. + */ + if (!virtio_has_feature(vdev->backend_features, VIRTIO_NET_F_CTRL_VQ)) { + virtio_clear_feature(&features, VIRTIO_NET_F_GUEST_ANNOUNCE); + } + + return features; +} + static int virtio_net_post_load_device(void *opaque, int version_id) { VirtIONet *n = opaque; -- 2.48.1
Retrieve peer hashing capability instead of hardcoding. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/hw/virtio/virtio-net.h | 5 ++- hw/net/virtio-net.c | 71 ++++++++++++++++++++++++++++++++++-------- net/vhost-vdpa.c | 4 +-- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -XXX,XX +XXX,XX @@ typedef struct VirtioNetRssData { bool enabled_software_rss; bool redirect; bool populate_hash; - uint32_t hash_types; + bool peer_hash_available; + uint32_t runtime_hash_types; + uint32_t supported_hash_types; + uint32_t peer_hash_types; uint8_t key[VIRTIO_NET_RSS_MAX_KEY_SIZE]; uint16_t indirections_len; uint16_t *indirections_table; diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_get_config(VirtIODevice *vdev, uint8_t *config) virtio_host_has_feature(vdev, VIRTIO_NET_F_RSS) ? VIRTIO_NET_RSS_MAX_TABLE_LEN : 1); virtio_stl_p(vdev, &netcfg.supported_hash_types, - VIRTIO_NET_RSS_SUPPORTED_HASHES); + n->rss_data.supported_hash_types); memcpy(config, &netcfg, n->config_size); /* @@ -XXX,XX +XXX,XX @@ static void rss_data_to_rss_config(struct VirtioNetRssData *data, { config->redirect = data->redirect; config->populate_hash = data->populate_hash; - config->hash_types = data->hash_types; + config->hash_types = data->runtime_hash_types; config->indirections_len = data->indirections_len; config->default_queue = data->default_queue; } @@ -XXX,XX +XXX,XX @@ static void virtio_net_detach_ebpf_rss(VirtIONet *n) static void virtio_net_commit_rss_config(VirtIONet *n) { + if (n->rss_data.peer_hash_available) { + return; + } + if (n->rss_data.enabled) { n->rss_data.enabled_software_rss = n->rss_data.populate_hash; if (n->rss_data.populate_hash) { @@ -XXX,XX +XXX,XX @@ static void virtio_net_commit_rss_config(VirtIONet *n) } trace_virtio_net_rss_enable(n, - n->rss_data.hash_types, + n->rss_data.runtime_hash_types, n->rss_data.indirections_len, sizeof(n->rss_data.key)); } else { @@ -XXX,XX +XXX,XX @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, err_value = (uint32_t)s; goto error; } - n->rss_data.hash_types = virtio_ldl_p(vdev, &cfg.hash_types); + n->rss_data.runtime_hash_types = virtio_ldl_p(vdev, &cfg.hash_types); n->rss_data.indirections_len = virtio_lduw_p(vdev, &cfg.indirection_table_mask); if (!do_rss) { @@ -XXX,XX +XXX,XX @@ static uint16_t virtio_net_handle_rss(VirtIONet *n, err_value = temp.b; goto error; } - if (!temp.b && n->rss_data.hash_types) { + if (!temp.b && n->rss_data.runtime_hash_types) { err_msg = "No key provided"; err_value = 0; goto error; } - if (!temp.b && !n->rss_data.hash_types) { + if (!temp.b && !n->rss_data.runtime_hash_types) { virtio_net_disable_rss(n); return queue_pairs; } @@ -XXX,XX +XXX,XX @@ static int virtio_net_process_rss(NetClientState *nc, const uint8_t *buf, net_rx_pkt_set_protocols(pkt, &iov, 1, n->host_hdr_len); net_rx_pkt_get_protocols(pkt, &hasip4, &hasip6, &l4hdr_proto); net_hash_type = virtio_net_get_hash_type(hasip4, hasip6, l4hdr_proto, - n->rss_data.hash_types); + n->rss_data.runtime_hash_types); if (net_hash_type > NetPktRssIpV6UdpEx) { if (n->rss_data.populate_hash) { hdr->hash_value = VIRTIO_NET_HASH_REPORT_NONE; @@ -XXX,XX +XXX,XX @@ static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, { VirtIONet *n = VIRTIO_NET(vdev); NetClientState *nc = qemu_get_queue(n->nic); + uint32_t supported_hash_types = n->rss_data.supported_hash_types; + uint32_t peer_hash_types = n->rss_data.peer_hash_types; + bool use_own_hash = + (supported_hash_types & VIRTIO_NET_RSS_SUPPORTED_HASHES) == + supported_hash_types; + bool use_peer_hash = + n->rss_data.peer_hash_available && + (supported_hash_types & peer_hash_types) == supported_hash_types; /* Firstly sync all virtio-net possible supported features */ features |= n->host_features; @@ -XXX,XX +XXX,XX @@ static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, } if (!get_vhost_net(nc->peer)) { + if (!use_own_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + virtio_net_load_ebpf(n, errp); + } + return features; } - if (!ebpf_rss_is_loaded(&n->ebpf_rss)) { - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + if (!use_peer_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + + if (!use_own_hash || !virtio_net_attach_ebpf_to_backend(n->nic, -1)) { + if (!virtio_net_load_ebpf(n, errp)) { + return features; + } + + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } } + features = vhost_net_get_features(get_vhost_net(nc->peer), features); vdev->backend_features = features; @@ -XXX,XX +XXX,XX @@ static const VMStateDescription vmstate_virtio_net_has_vnet = { }, }; +static int virtio_net_rss_post_load(void *opaque, int version_id) +{ + VirtIONet *n = VIRTIO_NET(opaque); + + if (version_id == 1) { + n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; + } + + return 0; +} + static bool virtio_net_rss_needed(void *opaque) { return VIRTIO_NET(opaque)->rss_data.enabled; @@ -XXX,XX +XXX,XX @@ static bool virtio_net_rss_needed(void *opaque) static const VMStateDescription vmstate_virtio_net_rss = { .name = "virtio-net-device/rss", - .version_id = 1, + .version_id = 2, .minimum_version_id = 1, + .post_load = virtio_net_rss_post_load, .needed = virtio_net_rss_needed, .fields = (const VMStateField[]) { VMSTATE_BOOL(rss_data.enabled, VirtIONet), VMSTATE_BOOL(rss_data.redirect, VirtIONet), VMSTATE_BOOL(rss_data.populate_hash, VirtIONet), - VMSTATE_UINT32(rss_data.hash_types, VirtIONet), + VMSTATE_UINT32(rss_data.runtime_hash_types, VirtIONet), + VMSTATE_UINT32_V(rss_data.supported_hash_types, VirtIONet, 2), VMSTATE_UINT16(rss_data.indirections_len, VirtIONet), VMSTATE_UINT16(rss_data.default_queue, VirtIONet), VMSTATE_UINT8_ARRAY(rss_data.key, VirtIONet, @@ -XXX,XX +XXX,XX @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) net_rx_pkt_init(&n->rx_pkt); - if (virtio_has_feature(n->host_features, VIRTIO_NET_F_RSS)) { - virtio_net_load_ebpf(n, errp); + if (qemu_get_vnet_hash_supported_types(qemu_get_queue(n->nic)->peer, + &n->rss_data.peer_hash_types)) { + n->rss_data.peer_hash_available = true; + n->rss_data.supported_hash_types = n->rss_data.peer_hash_types; + } else { + n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; } } diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static int vhost_vdpa_net_load_rss(VhostVDPAState *s, const VirtIONet *n, * configuration only at live migration. */ if (!n->rss_data.enabled || - n->rss_data.hash_types == VIRTIO_NET_HASH_REPORT_NONE) { + n->rss_data.runtime_hash_types == VIRTIO_NET_HASH_REPORT_NONE) { return 0; } table = g_malloc_n(n->rss_data.indirections_len, sizeof(n->rss_data.indirections_table[0])); - cfg.hash_types = cpu_to_le32(n->rss_data.hash_types); + cfg.hash_types = cpu_to_le32(n->rss_data.runtime_hash_types); if (do_rss) { /* -- 2.48.1
It is no longer used. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- net/vhost-vdpa.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static void vhost_vdpa_cleanup(NetClientState *nc) g_free(s->vhost_vdpa.shared); } -/** Dummy SetSteeringEBPF to support RSS for vhost-vdpa backend */ -static bool vhost_vdpa_set_steering_ebpf(NetClientState *nc, int prog_fd) -{ - return true; -} - static bool vhost_vdpa_has_vnet_hdr(NetClientState *nc) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_info = { .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, - .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, }; static int64_t vhost_vdpa_get_vring_group(int device_fd, unsigned vq_index, @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, - .set_steering_ebpf = vhost_vdpa_set_steering_ebpf, }; /* -- 2.48.1
By default, virtio-net limits the hash types that will be advertised to the guest so that all hash types are covered by the offloading capability the client provides. This change allows to override this behavior and to advertise hash types that require user-space hash calculation by specifying "on" for the corresponding properties. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/hw/virtio/virtio-net.h | 1 + hw/net/virtio-net.c | 45 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/include/hw/virtio/virtio-net.h b/include/hw/virtio/virtio-net.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/virtio-net.h +++ b/include/hw/virtio/virtio-net.h @@ -XXX,XX +XXX,XX @@ typedef struct VirtioNetRssData { uint32_t runtime_hash_types; uint32_t supported_hash_types; uint32_t peer_hash_types; + OnOffAutoBit64 specified_hash_types; uint8_t key[VIRTIO_NET_RSS_MAX_KEY_SIZE]; uint16_t indirections_len; uint16_t *indirections_table; diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_device_realize(DeviceState *dev, Error **errp) if (qemu_get_vnet_hash_supported_types(qemu_get_queue(n->nic)->peer, &n->rss_data.peer_hash_types)) { n->rss_data.peer_hash_available = true; - n->rss_data.supported_hash_types = n->rss_data.peer_hash_types; + n->rss_data.supported_hash_types = + n->rss_data.specified_hash_types.on_bits | + (n->rss_data.specified_hash_types.auto_bits & + n->rss_data.peer_hash_types); } else { - n->rss_data.supported_hash_types = VIRTIO_NET_RSS_SUPPORTED_HASHES; + n->rss_data.supported_hash_types = + n->rss_data.specified_hash_types.on_bits | + n->rss_data.specified_hash_types.auto_bits; } } @@ -XXX,XX +XXX,XX @@ static const Property virtio_net_properties[] = { VIRTIO_NET_F_GUEST_USO6, true), DEFINE_PROP_BIT64("host_uso", VirtIONet, host_features, VIRTIO_NET_F_HOST_USO, true), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-ipv4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-tcp4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-udp4", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv4 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-ipv6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-tcp6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-udp6", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv6 - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-ipv6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_IPv6_EX - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-tcp6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_TCPv6_EX - 1, + ON_OFF_AUTO_AUTO), + DEFINE_PROP_ON_OFF_AUTO_BIT64("hash-udp6ex", VirtIONet, + rss_data.specified_hash_types, + VIRTIO_NET_HASH_REPORT_UDPv6_EX - 1, + ON_OFF_AUTO_AUTO), }; static void virtio_net_class_init(ObjectClass *klass, void *data) -- 2.48.1
This adds set_vnet_hash() to configure virtio hashing and implements it for Linux's tap. vDPA will have an empty function as configuring virtio hashing is done with the load(). Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- include/net/net.h | 17 +++++++++++++++++ net/tap-linux.h | 1 + net/tap_int.h | 2 ++ net/net.c | 5 +++++ net/tap-bsd.c | 5 +++++ net/tap-linux.c | 5 +++++ net/tap-solaris.c | 5 +++++ net/tap-stub.c | 5 +++++ net/tap.c | 7 +++++++ net/vhost-vdpa.c | 7 +++++++ 10 files changed, 59 insertions(+) diff --git a/include/net/net.h b/include/net/net.h index XXXXXXX..XXXXXXX 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -XXX,XX +XXX,XX @@ typedef struct NICConf { int32_t bootindex; } NICConf; +#define NET_VNET_HASH_REPORT 1 +#define NET_VNET_HASH_RSS 2 + +typedef struct NetVnetHash { + uint16_t flags; + uint8_t pad[2]; + uint32_t types; +} NetVnetHash; + +typedef struct NetVnetHashRss { + uint16_t indirection_table_mask; + uint16_t unclassified_queue; +} NetVnetHashRss; + #define DEFINE_NIC_PROPERTIES(_state, _conf) \ DEFINE_PROP_MACADDR("mac", _state, _conf.macaddr), \ DEFINE_PROP_NETDEV("netdev", _state, _conf.peers) @@ -XXX,XX +XXX,XX @@ typedef void (SetOffload)(NetClientState *, int, int, int, int, int, int, int); typedef int (GetVnetHdrLen)(NetClientState *); typedef void (SetVnetHdrLen)(NetClientState *, int); typedef bool (GetVnetHashSupportedTypes)(NetClientState *, uint32_t *); +typedef void (SetVnetHash)(NetClientState *, const NetVnetHash *); typedef int (SetVnetLE)(NetClientState *, bool); typedef int (SetVnetBE)(NetClientState *, bool); typedef struct SocketReadState SocketReadState; @@ -XXX,XX +XXX,XX @@ typedef struct NetClientInfo { SetVnetLE *set_vnet_le; SetVnetBE *set_vnet_be; GetVnetHashSupportedTypes *get_vnet_hash_supported_types; + SetVnetHash *set_vnet_hash; NetAnnounce *announce; SetSteeringEBPF *set_steering_ebpf; NetCheckPeerType *check_peer_type; @@ -XXX,XX +XXX,XX @@ void qemu_set_offload(NetClientState *nc, int csum, int tso4, int tso6, int qemu_get_vnet_hdr_len(NetClientState *nc); void qemu_set_vnet_hdr_len(NetClientState *nc, int len); bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types); +void qemu_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash); int qemu_set_vnet_le(NetClientState *nc, bool is_le); int qemu_set_vnet_be(NetClientState *nc, bool is_be); void qemu_macaddr_default_if_unset(MACAddr *macaddr); diff --git a/net/tap-linux.h b/net/tap-linux.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.h +++ b/net/tap-linux.h @@ -XXX,XX +XXX,XX @@ #define TUNSETVNETLE _IOW('T', 220, int) #define TUNSETVNETBE _IOW('T', 222, int) #define TUNSETSTEERINGEBPF _IOR('T', 224, int) +#define TUNSETVNETHASH _IOW('T', 229, NetVnetHash) #endif diff --git a/net/tap_int.h b/net/tap_int.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -XXX,XX +XXX,XX @@ #define NET_TAP_INT_H #include "qapi/qapi-types-net.h" +#include "net/net.h" int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required, int mq_required, Error **errp); @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd); void tap_fd_set_offload(int fd, int csum, int tso4, int tso6, int ecn, int ufo, int uso4, int uso6); void tap_fd_set_vnet_hdr_len(int fd, int len); +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash); int tap_fd_set_vnet_le(int fd, int vnet_is_le); int tap_fd_set_vnet_be(int fd, int vnet_is_be); int tap_fd_enable(int fd); diff --git a/net/net.c b/net/net.c index XXXXXXX..XXXXXXX 100644 --- a/net/net.c +++ b/net/net.c @@ -XXX,XX +XXX,XX @@ bool qemu_get_vnet_hash_supported_types(NetClientState *nc, uint32_t *types) return nc->info->get_vnet_hash_supported_types(nc, types); } +void qemu_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash) +{ + nc->info->set_vnet_hash(nc, hash); +} + int qemu_set_vnet_le(NetClientState *nc, bool is_le) { #if HOST_BIG_ENDIAN diff --git a/net/tap-bsd.c b/net/tap-bsd.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) { } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + g_assert_not_reached(); +} + int tap_fd_set_vnet_le(int fd, int is_le) { return -EINVAL; diff --git a/net/tap-linux.c b/net/tap-linux.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) } } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + assert(!ioctl(fd, TUNSETVNETHASH, hash)); +} + int tap_fd_set_vnet_le(int fd, int is_le) { int arg = is_le ? 1 : 0; diff --git a/net/tap-solaris.c b/net/tap-solaris.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) { } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + g_assert_not_reached(); +} + int tap_fd_set_vnet_le(int fd, int is_le) { return -EINVAL; diff --git a/net/tap-stub.c b/net/tap-stub.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -XXX,XX +XXX,XX @@ void tap_fd_set_vnet_hdr_len(int fd, int len) { } +void tap_fd_set_vnet_hash(int fd, const NetVnetHash *hash) +{ + g_assert_not_reached(); +} + int tap_fd_set_vnet_le(int fd, int is_le) { return -EINVAL; diff --git a/net/tap.c b/net/tap.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap.c +++ b/net/tap.c @@ -XXX,XX +XXX,XX @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len) s->using_vnet_hdr = true; } +static void tap_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + return tap_fd_set_vnet_hash(s->fd, hash); +} + static int tap_set_vnet_le(NetClientState *nc, bool is_le) { TAPState *s = DO_UPCAST(TAPState, nc, nc); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_tap_info = { .has_vnet_hdr_len = tap_has_vnet_hdr_len, .set_offload = tap_set_offload, .set_vnet_hdr_len = tap_set_vnet_hdr_len, + .set_vnet_hash = tap_set_vnet_hash, .set_vnet_le = tap_set_vnet_le, .set_vnet_be = tap_set_vnet_be, .set_steering_ebpf = tap_set_steering_ebpf, diff --git a/net/vhost-vdpa.c b/net/vhost-vdpa.c index XXXXXXX..XXXXXXX 100644 --- a/net/vhost-vdpa.c +++ b/net/vhost-vdpa.c @@ -XXX,XX +XXX,XX @@ static bool vhost_vdpa_get_vnet_hash_supported_types(NetClientState *nc, return true; } +static void vhost_vdpa_set_vnet_hash(NetClientState *nc, + const NetVnetHash *hash) +{ +} + static bool vhost_vdpa_has_ufo(NetClientState *nc) { assert(nc->info->type == NET_CLIENT_DRIVER_VHOST_VDPA); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_info = { .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, + .set_vnet_hash = vhost_vdpa_set_vnet_hash, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, }; @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_vhost_vdpa_cvq_info = { .cleanup = vhost_vdpa_cleanup, .has_vnet_hdr = vhost_vdpa_has_vnet_hdr, .get_vnet_hash_supported_types = vhost_vdpa_get_vnet_hash_supported_types, + .set_vnet_hash = vhost_vdpa_set_vnet_hash, .has_ufo = vhost_vdpa_has_ufo, .check_peer_type = vhost_vdpa_check_peer_type, }; -- 2.48.1
This is necessary to offload hashing to tap. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- hw/net/virtio-net.c | 77 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 64 insertions(+), 13 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static void virtio_net_detach_ebpf_rss(VirtIONet *n) static void virtio_net_commit_rss_config(VirtIONet *n) { - if (n->rss_data.peer_hash_available) { - return; - } - if (n->rss_data.enabled) { - n->rss_data.enabled_software_rss = n->rss_data.populate_hash; - if (n->rss_data.populate_hash) { - virtio_net_detach_ebpf_rss(n); - } else if (!virtio_net_attach_ebpf_rss(n)) { - if (get_vhost_net(qemu_get_queue(n->nic)->peer)) { - warn_report("Can't load eBPF RSS for vhost"); + if (n->rss_data.peer_hash_available && + (n->rss_data.peer_hash_types & n->rss_data.runtime_hash_types) == + n->rss_data.runtime_hash_types) { + NetVnetHash hash = { + .flags = (n->rss_data.redirect ? NET_VNET_HASH_RSS : 0) | + (n->rss_data.populate_hash ? NET_VNET_HASH_REPORT : 0), + .types = n->rss_data.runtime_hash_types + }; + + if (n->rss_data.redirect) { + size_t indirection_table_size = + n->rss_data.indirections_len * + sizeof(*n->rss_data.indirections_table); + + size_t hash_size = sizeof(NetVnetHash) + + sizeof(NetVnetHashRss) + + indirection_table_size + + sizeof(n->rss_data.key); + + g_autofree struct { + NetVnetHash hdr; + NetVnetHashRss rss; + uint8_t footer[]; + } *rss = g_malloc(hash_size); + + rss->hdr = hash; + rss->rss.indirection_table_mask = + n->rss_data.indirections_len - 1; + rss->rss.unclassified_queue = n->rss_data.default_queue; + + memcpy(rss->footer, n->rss_data.indirections_table, + indirection_table_size); + + memcpy(rss->footer + indirection_table_size, n->rss_data.key, + sizeof(n->rss_data.key)); + + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &rss->hdr); } else { - warn_report("Can't load eBPF RSS - fallback to software RSS"); - n->rss_data.enabled_software_rss = true; + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &hash); + } + + n->rss_data.enabled_software_rss = false; + } else { + if (n->rss_data.peer_hash_available) { + NetVnetHash hash = { .flags = 0 }; + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &hash); + } + + n->rss_data.enabled_software_rss = n->rss_data.populate_hash; + if (n->rss_data.populate_hash) { + virtio_net_detach_ebpf_rss(n); + } else if (!virtio_net_attach_ebpf_rss(n)) { + if (get_vhost_net(qemu_get_queue(n->nic)->peer)) { + warn_report("Can't load eBPF RSS for vhost"); + } else { + warn_report("Can't load eBPF RSS - fallback to software RSS"); + n->rss_data.enabled_software_rss = true; + } } } @@ -XXX,XX +XXX,XX @@ static void virtio_net_commit_rss_config(VirtIONet *n) n->rss_data.indirections_len, sizeof(n->rss_data.key)); } else { - virtio_net_detach_ebpf_rss(n); + if (n->rss_data.peer_hash_available) { + NetVnetHash hash = { .flags = 0 }; + qemu_set_vnet_hash(qemu_get_queue(n->nic)->peer, &hash); + } else { + virtio_net_detach_ebpf_rss(n); + } + trace_virtio_net_rss_disable(n); } } -- 2.48.1
This is necessary to offload hashing to tap. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- hw/net/virtio-net.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c index XXXXXXX..XXXXXXX 100644 --- a/hw/net/virtio-net.c +++ b/hw/net/virtio-net.c @@ -XXX,XX +XXX,XX @@ static size_t receive_header(VirtIONet *n, struct virtio_net_hdr *hdr, { size_t hdr_len = n->guest_hdr_len; - memcpy(hdr, buf, sizeof(struct virtio_net_hdr)); + memcpy(hdr, buf, + n->rss_data.populate_hash && + n->rss_data.enabled && !n->rss_data.enabled_software_rss ? + sizeof(struct virtio_net_hdr_v1_hash) : + sizeof(struct virtio_net_hdr)); *buf_offset = n->host_hdr_len; work_around_broken_dhclient(hdr, &hdr_len, buf, buf_size, buf_offset); @@ -XXX,XX +XXX,XX @@ static uint64_t virtio_net_get_features(VirtIODevice *vdev, uint64_t features, } if (!get_vhost_net(nc->peer)) { - if (!use_own_hash) { - virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); - virtio_clear_feature(&features, VIRTIO_NET_F_RSS); - } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { - virtio_net_load_ebpf(n, errp); + if (!use_peer_hash) { + if (!use_own_hash) { + virtio_clear_feature(&features, VIRTIO_NET_F_HASH_REPORT); + virtio_clear_feature(&features, VIRTIO_NET_F_RSS); + } else if (virtio_has_feature(features, VIRTIO_NET_F_RSS)) { + virtio_net_load_ebpf(n, errp); + } } return features; -- 2.48.1
This allows offloading virtio-net hashing to tap on Linux. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- net/tap-linux.h | 1 + net/tap_int.h | 1 + net/tap-bsd.c | 5 +++++ net/tap-linux.c | 13 +++++++++++++ net/tap-solaris.c | 5 +++++ net/tap-stub.c | 5 +++++ net/tap.c | 8 ++++++++ 7 files changed, 38 insertions(+) diff --git a/net/tap-linux.h b/net/tap-linux.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.h +++ b/net/tap-linux.h @@ -XXX,XX +XXX,XX @@ #define TUNSETVNETLE _IOW('T', 220, int) #define TUNSETVNETBE _IOW('T', 222, int) #define TUNSETSTEERINGEBPF _IOR('T', 224, int) +#define TUNGETVNETHASHCAP _IOR('T', 228, NetVnetHash) #define TUNSETVNETHASH _IOW('T', 229, NetVnetHash) #endif diff --git a/net/tap_int.h b/net/tap_int.h index XXXXXXX..XXXXXXX 100644 --- a/net/tap_int.h +++ b/net/tap_int.h @@ -XXX,XX +XXX,XX @@ ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen); void tap_set_sndbuf(int fd, const NetdevTapOptions *tap, Error **errp); int tap_probe_vnet_hdr(int fd, Error **errp); +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types); int tap_probe_has_ufo(int fd); int tap_probe_has_uso(int fd); void tap_fd_set_offload(int fd, int csum, int tso4, int tso6, int ecn, int ufo, diff --git a/net/tap-bsd.c b/net/tap-bsd.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-bsd.c +++ b/net/tap-bsd.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 0; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + return false; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { } diff --git a/net/tap-linux.c b/net/tap-linux.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-linux.c +++ b/net/tap-linux.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 1; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + NetVnetHash hash; + + if (ioctl(fd, TUNGETVNETHASHCAP, &hash)) { + return false; + } + + *types = hash.types; + + return true; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) { diff --git a/net/tap-solaris.c b/net/tap-solaris.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-solaris.c +++ b/net/tap-solaris.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 0; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + return false; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { } diff --git a/net/tap-stub.c b/net/tap-stub.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap-stub.c +++ b/net/tap-stub.c @@ -XXX,XX +XXX,XX @@ int tap_probe_has_uso(int fd) return 0; } +bool tap_probe_vnet_hash_supported_types(int fd, uint32_t *types) +{ + return false; +} + void tap_fd_set_vnet_hdr_len(int fd, int len) { } diff --git a/net/tap.c b/net/tap.c index XXXXXXX..XXXXXXX 100644 --- a/net/tap.c +++ b/net/tap.c @@ -XXX,XX +XXX,XX @@ static void tap_set_vnet_hdr_len(NetClientState *nc, int len) s->using_vnet_hdr = true; } +static bool tap_get_vnet_hash_supported_types(NetClientState *nc, + uint32_t *types) +{ + TAPState *s = DO_UPCAST(TAPState, nc, nc); + return tap_probe_vnet_hash_supported_types(s->fd, types); +} + static void tap_set_vnet_hash(NetClientState *nc, const NetVnetHash *hash) { TAPState *s = DO_UPCAST(TAPState, nc, nc); @@ -XXX,XX +XXX,XX @@ static NetClientInfo net_tap_info = { .has_vnet_hdr_len = tap_has_vnet_hdr_len, .set_offload = tap_set_offload, .set_vnet_hdr_len = tap_set_vnet_hdr_len, + .get_vnet_hash_supported_types = tap_get_vnet_hash_supported_types, .set_vnet_hash = tap_set_vnet_hash, .set_vnet_le = tap_set_vnet_le, .set_vnet_be = tap_set_vnet_be, -- 2.48.1
eBPF RSS virtio-net support was written in assumption that there is only one alternative RSS implementation: 'in-qemu' RSS. It is no longer true, and we now have yet another implementation; namely the peer RSS. Signed-off-by: Akihiko Odaki <akihiko.odaki@daynix.com> --- docs/devel/ebpf_rss.rst | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/docs/devel/ebpf_rss.rst b/docs/devel/ebpf_rss.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/devel/ebpf_rss.rst +++ b/docs/devel/ebpf_rss.rst @@ -XXX,XX +XXX,XX @@ eBPF RSS virtio-net support RSS(Receive Side Scaling) is used to distribute network packets to guest virtqueues by calculating packet hash. Usually every queue is processed then by a specific guest CPU core. -For now there are 2 RSS implementations in qemu: -- 'in-qemu' RSS (functions if qemu receives network packets, i.e. vhost=off) -- eBPF RSS (can function with also with vhost=on) +For now there are 3 RSS implementations in qemu: +1. Peer RSS +2. eBPF RSS +3. 'In-QEMU' RSS + +'In-QEMU' RSS is incompatible with vhost since the packets are not routed to +QEMU. eBPF RSS requires Linux 5.8+. Peer RSS requires the peer to implement RSS. +Currently QEMU can use the RSS implementation of vDPA and Linux's TUN module +with the following patch applied: +https://lore.kernel.org/r/20240915-rss-v3-0-c630015db082@daynix.com/ + +eBPF RSS does not support hash reporting. Peer RSS may support limited hash +types. + +virtio-net automatically chooses the RSS implementation to use. Peer RSS is +the most preferred, and 'in-QEMU' RSS is the least. eBPF support (CONFIG_EBPF) is enabled by 'configure' script. To enable eBPF RSS support use './configure --enable-bpf'. @@ -XXX,XX +XXX,XX @@ eBPF RSS turned on by different combinations of vhost-net, vitrio-net and tap co tap,vhost=on & virtio-net-pci,rss=on,hash=on -If CONFIG_EBPF is not set then only 'in-qemu' RSS is supported. -Also 'in-qemu' RSS, as a fallback, is used if the eBPF program failed to load or set to TUN. - RSS eBPF program ---------------- @@ -XXX,XX +XXX,XX @@ Prerequisites to recompile the eBPF program (regenerate ebpf/rss.bpf.skeleton.h) $ make -f Makefile.ebpf Current eBPF RSS implementation uses 'bounded loops' with 'backward jump instructions' which present in the last kernels. -Overall eBPF RSS works on kernels 5.8+. eBPF RSS implementation ----------------------- -- 2.48.1