From 6c59c195266cc95c15142e05d7c444b2b1db73e1 Mon Sep 17 00:00:00 2001 From: Mike Pattrick Date: Tue, 21 Nov 2023 14:26:50 -0500 Subject: [PATCH] netdev-linux: Use ethtool to detect offload support. Currently when userspace-tso is enabled, netdev-linux interfaces will indicate support for all offload flags regardless of interface configuration. This patch checks for which offload features are enabled during netdev construction. Signed-off-by: Mike Pattrick Acked-by: Simon Horman Signed-off-by: Ilya Maximets --- lib/netdev-linux.c | 150 ++++++++++++++++++++++++++++++++++++++-- tests/ofproto-macros.at | 1 + 2 files changed, 144 insertions(+), 7 deletions(-) diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c index 18b62d90c28..93a5845c064 100644 --- a/lib/netdev-linux.c +++ b/lib/netdev-linux.c @@ -558,6 +558,7 @@ static bool netdev_linux_miimon_enabled(void); static void netdev_linux_miimon_run(void); static void netdev_linux_miimon_wait(void); static int netdev_linux_get_mtu__(struct netdev_linux *netdev, int *mtup); +static void netdev_linux_set_ol(struct netdev *netdev); static bool is_tap_netdev(const struct netdev *netdev) @@ -959,14 +960,12 @@ netdev_linux_construct(struct netdev *netdev_) return error; } - /* The socket interface doesn't offer the option to enable only - * csum offloading without TSO. */ if (userspace_tso_enabled()) { - netdev_->ol_flags |= NETDEV_TX_OFFLOAD_TCP_TSO; - netdev_->ol_flags |= NETDEV_TX_OFFLOAD_TCP_CKSUM; - netdev_->ol_flags |= NETDEV_TX_OFFLOAD_UDP_CKSUM; - netdev_->ol_flags |= NETDEV_TX_OFFLOAD_SCTP_CKSUM; - netdev_->ol_flags |= NETDEV_TX_OFFLOAD_IPV4_CKSUM; + /* The AF_PACKET socket interface uses the same option to facilitate + * both csum and segmentation offloading. However, these features can + * be toggled off or on individually at the interface level. The netdev + * flags are set based on the features indicated by ethtool. */ + netdev_linux_set_ol(netdev_); } error = get_flags(&netdev->up, &netdev->ifi_flags); @@ -2381,6 +2380,143 @@ netdev_internal_get_stats(const struct netdev *netdev_, return error; } +static int +netdev_linux_read_stringset_info(struct netdev_linux *netdev, uint32_t *len) +{ + union { + struct ethtool_sset_info hdr; + struct { + uint64_t pad[2]; + uint32_t sset_len[1]; + }; + } sset_info; + int error; + + sset_info.hdr.cmd = ETHTOOL_GSSET_INFO; + sset_info.hdr.reserved = 0; + sset_info.hdr.sset_mask = 1ULL << ETH_SS_FEATURES; + + error = netdev_linux_do_ethtool(netdev_get_name(&netdev->up), + (struct ethtool_cmd *) &sset_info, + ETHTOOL_GSSET_INFO, "ETHTOOL_GSSET_INFO"); + if (error) { + return error; + } + if (sset_info.hdr.sset_mask & (1ULL << ETH_SS_FEATURES)) { + *len = sset_info.sset_len[0]; + return 0; + } else { + /* ETH_SS_FEATURES is not supported. */ + return -EOPNOTSUPP; + } +} + + +static int +netdev_linux_read_definitions(struct netdev_linux *netdev, + struct ethtool_gstrings **pstrings) +{ + struct ethtool_gstrings *strings = NULL; + uint32_t len = 0; + int error = 0; + + error = netdev_linux_read_stringset_info(netdev, &len); + if (error || !len) { + return error; + } + strings = xzalloc(sizeof *strings + len * ETH_GSTRING_LEN); + + strings->cmd = ETHTOOL_GSTRINGS; + strings->string_set = ETH_SS_FEATURES; + strings->len = len; + error = netdev_linux_do_ethtool(netdev_get_name(&netdev->up), + (struct ethtool_cmd *) strings, + ETHTOOL_GSTRINGS, "ETHTOOL_GSTRINGS"); + if (error) { + goto out; + } + + for (int i = 0; i < len; i++) { + strings->data[(i + 1) * ETH_GSTRING_LEN - 1] = 0; + } + + *pstrings = strings; + + return 0; +out: + *pstrings = NULL; + free(strings); + return error; +} + +static void +netdev_linux_set_ol(struct netdev *netdev_) +{ + struct netdev_linux *netdev = netdev_linux_cast(netdev_); + struct ethtool_gfeatures *features = NULL; + struct ethtool_gstrings *names = NULL; + int error; + + COVERAGE_INC(netdev_get_ethtool); + + error = netdev_linux_read_definitions(netdev, &names); + if (error) { + return; + } + + features = xzalloc(sizeof *features + + DIV_ROUND_UP(names->len, 32) * + sizeof features->features[0]); + + features->cmd = ETHTOOL_GFEATURES; + features->size = DIV_ROUND_UP(names->len, 32); + error = netdev_linux_do_ethtool(netdev_get_name(netdev_), + (struct ethtool_cmd *) features, + ETHTOOL_GFEATURES, "ETHTOOL_GFEATURES"); + + if (error) { + goto out; + } + +#define FEATURE_WORD(blocks, index, field) ((blocks)[(index) / 32U].field) +#define FEATURE_FIELD_FLAG(index) (1U << (index) % 32U) +#define FEATURE_BIT_IS_SET(blocks, index, field) \ + (FEATURE_WORD(blocks, index, field) & FEATURE_FIELD_FLAG(index)) + + netdev->up.ol_flags = 0; + static const struct { + char *string; + uint32_t value; + } t_list[] = { + {"tx-checksum-ipv4", NETDEV_TX_OFFLOAD_IPV4_CKSUM | + NETDEV_TX_OFFLOAD_TCP_CKSUM | + NETDEV_TX_OFFLOAD_UDP_CKSUM}, + {"tx-checksum-ipv6", NETDEV_TX_OFFLOAD_TCP_CKSUM | + NETDEV_TX_OFFLOAD_UDP_CKSUM}, + {"tx-checksum-ip-generic", NETDEV_TX_OFFLOAD_IPV4_CKSUM | + NETDEV_TX_OFFLOAD_TCP_CKSUM | + NETDEV_TX_OFFLOAD_UDP_CKSUM}, + {"tx-checksum-sctp", NETDEV_TX_OFFLOAD_SCTP_CKSUM}, + {"tx-tcp-segmentation", NETDEV_TX_OFFLOAD_TCP_TSO}, + }; + + for (int j = 0; j < ARRAY_SIZE(t_list); j++) { + for (int i = 0; i < names->len; i++) { + char *name = (char *) names->data + i * ETH_GSTRING_LEN; + if (strcmp(t_list[j].string, name) == 0) { + if (FEATURE_BIT_IS_SET(features->features, i, active)) { + netdev_->ol_flags |= t_list[j].value; + } + break; + } + } + } + +out: + free(names); + free(features); +} + static void netdev_linux_read_features(struct netdev_linux *netdev) { diff --git a/tests/ofproto-macros.at b/tests/ofproto-macros.at index d2e6ac768ba..5a7b7a6e77c 100644 --- a/tests/ofproto-macros.at +++ b/tests/ofproto-macros.at @@ -260,6 +260,7 @@ check_logs () { /ovs_rcu.*blocked [[0-9]]* ms waiting for .* to quiesce/d /Dropped [[0-9]]* log messages/d /setting extended ack support failed/d +/ETHTOOL_GSSET_INFO/d /|WARN|/p /|ERR|/p /|EMER|/p" ${logs}