Put the functions of ethtool in an independent file. Signed-off-by: Xuan Zhuo <xuanzhuo@xxxxxxxxxxxxxxxxx> --- drivers/net/virtio/Makefile | 3 +- drivers/net/virtio/virtnet.c | 573 +------------------------- drivers/net/virtio/virtnet.h | 3 + drivers/net/virtio/virtnet_ethtool.c | 578 +++++++++++++++++++++++++++ drivers/net/virtio/virtnet_ethtool.h | 8 + 5 files changed, 596 insertions(+), 569 deletions(-) create mode 100644 drivers/net/virtio/virtnet_ethtool.c create mode 100644 drivers/net/virtio/virtnet_ethtool.h diff --git a/drivers/net/virtio/Makefile b/drivers/net/virtio/Makefile index a2d80f95c921..9b35fb00d6c7 100644 --- a/drivers/net/virtio/Makefile +++ b/drivers/net/virtio/Makefile @@ -5,4 +5,5 @@ obj-$(CONFIG_VIRTIO_NET) += virtio_net.o -virtio_net-y := virtnet.o virtnet_common.o virtnet_ctrl.o +virtio_net-y := virtnet.o virtnet_common.o virtnet_ctrl.o \ + virtnet_ethtool.o diff --git a/drivers/net/virtio/virtnet.c b/drivers/net/virtio/virtnet.c index 36c747e43b3f..1323c6733f56 100644 --- a/drivers/net/virtio/virtnet.c +++ b/drivers/net/virtio/virtnet.c @@ -23,6 +23,7 @@ #include "virtnet.h" #include "virtnet_common.h" #include "virtnet_ctrl.h" +#include "virtnet_ethtool.h" static int napi_weight = NAPI_POLL_WEIGHT; module_param(napi_weight, int, 0444); @@ -54,37 +55,6 @@ static const unsigned long guest_offloads[] = { (1ULL << VIRTIO_NET_F_GUEST_USO4) | \ (1ULL << VIRTIO_NET_F_GUEST_USO6)) -struct virtnet_stat_desc { - char desc[ETH_GSTRING_LEN]; - size_t offset; -}; - -#define VIRTNET_SQ_STAT(m) offsetof(struct virtnet_sq_stats, m) -#define VIRTNET_RQ_STAT(m) offsetof(struct virtnet_rq_stats, m) - -static const struct virtnet_stat_desc virtnet_sq_stats_desc[] = { - { "packets", VIRTNET_SQ_STAT(packets) }, - { "bytes", VIRTNET_SQ_STAT(bytes) }, - { "xdp_tx", VIRTNET_SQ_STAT(xdp_tx) }, - { "xdp_tx_drops", VIRTNET_SQ_STAT(xdp_tx_drops) }, - { "kicks", VIRTNET_SQ_STAT(kicks) }, - { "tx_timeouts", VIRTNET_SQ_STAT(tx_timeouts) }, -}; - -static const struct virtnet_stat_desc virtnet_rq_stats_desc[] = { - { "packets", VIRTNET_RQ_STAT(packets) }, - { "bytes", VIRTNET_RQ_STAT(bytes) }, - { "drops", VIRTNET_RQ_STAT(drops) }, - { "xdp_packets", VIRTNET_RQ_STAT(xdp_packets) }, - { "xdp_tx", VIRTNET_RQ_STAT(xdp_tx) }, - { "xdp_redirects", VIRTNET_RQ_STAT(xdp_redirects) }, - { "xdp_drops", VIRTNET_RQ_STAT(xdp_drops) }, - { "kicks", VIRTNET_RQ_STAT(kicks) }, -}; - -#define VIRTNET_SQ_STATS_LEN ARRAY_SIZE(virtnet_sq_stats_desc) -#define VIRTNET_RQ_STATS_LEN ARRAY_SIZE(virtnet_rq_stats_desc) - struct padded_vnet_hdr { struct virtio_net_hdr_v1_hash hdr; /* @@ -1550,21 +1520,6 @@ static void refill_work(struct work_struct *work) } } -static void virtnet_rq_update_stats(struct virtnet_rq *rq, struct virtnet_rq_stats *stats) -{ - int i; - - u64_stats_update_begin(&rq->stats.syncp); - for (i = 0; i < VIRTNET_RQ_STATS_LEN; i++) { - size_t offset = virtnet_rq_stats_desc[i].offset; - u64 *item; - - item = (u64 *)((u8 *)&rq->stats + offset); - *item += *(u64 *)((u8 *)stats + offset); - } - u64_stats_update_end(&rq->stats.syncp); -} - static int virtnet_receive(struct virtnet_rq *rq, int budget, unsigned int *xdp_xmit) { @@ -1845,8 +1800,7 @@ static netdev_tx_t start_xmit(struct sk_buff *skb, struct net_device *dev) return NETDEV_TX_OK; } -static int virtnet_rx_resize(struct virtnet_info *vi, - struct virtnet_rq *rq, u32 ring_num) +int virtnet_rx_resize(struct virtnet_info *vi, struct virtnet_rq *rq, u32 ring_num) { bool running = netif_running(vi->dev); int err, qindex; @@ -1868,8 +1822,7 @@ static int virtnet_rx_resize(struct virtnet_info *vi, return err; } -static int virtnet_tx_resize(struct virtnet_info *vi, - struct virtnet_sq *sq, u32 ring_num) +int virtnet_tx_resize(struct virtnet_info *vi, struct virtnet_sq *sq, u32 ring_num) { bool running = netif_running(vi->dev); struct netdev_queue *txq; @@ -1991,7 +1944,7 @@ static void virtnet_stats(struct net_device *dev, tot->rx_frame_errors = dev->stats.rx_frame_errors; } -static int _virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs) +int _virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs) { struct net_device *dev = vi->dev; @@ -2041,66 +1994,6 @@ static int virtnet_close(struct net_device *dev) return 0; } -static void virtnet_get_ringparam(struct net_device *dev, - struct ethtool_ringparam *ring, - struct kernel_ethtool_ringparam *kernel_ring, - struct netlink_ext_ack *extack) -{ - struct virtnet_info *vi = netdev_priv(dev); - - ring->rx_max_pending = vi->rq[0].vq->num_max; - ring->tx_max_pending = vi->sq[0].vq->num_max; - ring->rx_pending = virtqueue_get_vring_size(vi->rq[0].vq); - ring->tx_pending = virtqueue_get_vring_size(vi->sq[0].vq); -} - -static int virtnet_set_ringparam(struct net_device *dev, - struct ethtool_ringparam *ring, - struct kernel_ethtool_ringparam *kernel_ring, - struct netlink_ext_ack *extack) -{ - struct virtnet_info *vi = netdev_priv(dev); - u32 rx_pending, tx_pending; - struct virtnet_rq *rq; - struct virtnet_sq *sq; - int i, err; - - if (ring->rx_mini_pending || ring->rx_jumbo_pending) - return -EINVAL; - - rx_pending = virtqueue_get_vring_size(vi->rq[0].vq); - tx_pending = virtqueue_get_vring_size(vi->sq[0].vq); - - if (ring->rx_pending == rx_pending && - ring->tx_pending == tx_pending) - return 0; - - if (ring->rx_pending > vi->rq[0].vq->num_max) - return -EINVAL; - - if (ring->tx_pending > vi->sq[0].vq->num_max) - return -EINVAL; - - for (i = 0; i < vi->max_queue_pairs; i++) { - rq = vi->rq + i; - sq = vi->sq + i; - - if (ring->tx_pending != tx_pending) { - err = virtnet_tx_resize(vi, sq, ring->tx_pending); - if (err) - return err; - } - - if (ring->rx_pending != rx_pending) { - err = virtnet_rx_resize(vi, rq, ring->rx_pending); - if (err) - return err; - } - } - - return 0; -} - static void virtnet_init_default_rss(struct virtnet_info *vi) { u32 indir_val = 0; @@ -2123,351 +2016,6 @@ static void virtnet_init_default_rss(struct virtnet_info *vi) netdev_rss_key_fill(vi->ctrl->rss.key, vi->rss_key_size); } -static void virtnet_get_hashflow(const struct virtnet_info *vi, struct ethtool_rxnfc *info) -{ - info->data = 0; - switch (info->flow_type) { - case TCP_V4_FLOW: - if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_TCPv4) { - info->data = RXH_IP_SRC | RXH_IP_DST | - RXH_L4_B_0_1 | RXH_L4_B_2_3; - } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv4) { - info->data = RXH_IP_SRC | RXH_IP_DST; - } - break; - case TCP_V6_FLOW: - if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_TCPv6) { - info->data = RXH_IP_SRC | RXH_IP_DST | - RXH_L4_B_0_1 | RXH_L4_B_2_3; - } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv6) { - info->data = RXH_IP_SRC | RXH_IP_DST; - } - break; - case UDP_V4_FLOW: - if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_UDPv4) { - info->data = RXH_IP_SRC | RXH_IP_DST | - RXH_L4_B_0_1 | RXH_L4_B_2_3; - } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv4) { - info->data = RXH_IP_SRC | RXH_IP_DST; - } - break; - case UDP_V6_FLOW: - if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_UDPv6) { - info->data = RXH_IP_SRC | RXH_IP_DST | - RXH_L4_B_0_1 | RXH_L4_B_2_3; - } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv6) { - info->data = RXH_IP_SRC | RXH_IP_DST; - } - break; - case IPV4_FLOW: - if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv4) - info->data = RXH_IP_SRC | RXH_IP_DST; - - break; - case IPV6_FLOW: - if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv6) - info->data = RXH_IP_SRC | RXH_IP_DST; - - break; - default: - info->data = 0; - break; - } -} - -static bool virtnet_set_hashflow(struct virtnet_info *vi, struct ethtool_rxnfc *info) -{ - u32 new_hashtypes = vi->rss_hash_types_saved; - bool is_disable = info->data & RXH_DISCARD; - bool is_l4 = info->data == (RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3); - - /* supports only 'sd', 'sdfn' and 'r' */ - if (!((info->data == (RXH_IP_SRC | RXH_IP_DST)) | is_l4 | is_disable)) - return false; - - switch (info->flow_type) { - case TCP_V4_FLOW: - new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv4 | VIRTIO_NET_RSS_HASH_TYPE_TCPv4); - if (!is_disable) - new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv4 - | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_TCPv4 : 0); - break; - case UDP_V4_FLOW: - new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv4 | VIRTIO_NET_RSS_HASH_TYPE_UDPv4); - if (!is_disable) - new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv4 - | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_UDPv4 : 0); - break; - case IPV4_FLOW: - new_hashtypes &= ~VIRTIO_NET_RSS_HASH_TYPE_IPv4; - if (!is_disable) - new_hashtypes = VIRTIO_NET_RSS_HASH_TYPE_IPv4; - break; - case TCP_V6_FLOW: - new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv6 | VIRTIO_NET_RSS_HASH_TYPE_TCPv6); - if (!is_disable) - new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv6 - | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_TCPv6 : 0); - break; - case UDP_V6_FLOW: - new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv6 | VIRTIO_NET_RSS_HASH_TYPE_UDPv6); - if (!is_disable) - new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv6 - | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_UDPv6 : 0); - break; - case IPV6_FLOW: - new_hashtypes &= ~VIRTIO_NET_RSS_HASH_TYPE_IPv6; - if (!is_disable) - new_hashtypes = VIRTIO_NET_RSS_HASH_TYPE_IPv6; - break; - default: - /* unsupported flow */ - return false; - } - - /* if unsupported hashtype was set */ - if (new_hashtypes != (new_hashtypes & vi->rss_hash_types_supported)) - return false; - - if (new_hashtypes != vi->rss_hash_types_saved) { - vi->rss_hash_types_saved = new_hashtypes; - vi->ctrl->rss.hash_types = vi->rss_hash_types_saved; - if (vi->dev->features & NETIF_F_RXHASH) - return virtnet_commit_rss_command(vi); - } - - return true; -} - -static void virtnet_get_drvinfo(struct net_device *dev, - struct ethtool_drvinfo *info) -{ - struct virtnet_info *vi = netdev_priv(dev); - struct virtio_device *vdev = vi->vdev; - - strscpy(info->driver, KBUILD_MODNAME, sizeof(info->driver)); - strscpy(info->version, VIRTNET_DRIVER_VERSION, sizeof(info->version)); - strscpy(info->bus_info, virtio_bus_name(vdev), sizeof(info->bus_info)); - -} - -/* TODO: Eliminate OOO packets during switching */ -static int virtnet_set_channels(struct net_device *dev, - struct ethtool_channels *channels) -{ - struct virtnet_info *vi = netdev_priv(dev); - u16 queue_pairs = channels->combined_count; - int err; - - /* We don't support separate rx/tx channels. - * We don't allow setting 'other' channels. - */ - if (channels->rx_count || channels->tx_count || channels->other_count) - return -EINVAL; - - if (queue_pairs > vi->max_queue_pairs || queue_pairs == 0) - return -EINVAL; - - /* For now we don't support modifying channels while XDP is loaded - * also when XDP is loaded all RX queues have XDP programs so we only - * need to check a single RX queue. - */ - if (vi->rq[0].xdp_prog) - return -EINVAL; - - cpus_read_lock(); - err = _virtnet_set_queues(vi, queue_pairs); - if (err) { - cpus_read_unlock(); - goto err; - } - virtnet_set_affinity(vi); - cpus_read_unlock(); - - netif_set_real_num_tx_queues(dev, queue_pairs); - netif_set_real_num_rx_queues(dev, queue_pairs); - err: - return err; -} - -static void virtnet_get_strings(struct net_device *dev, u32 stringset, u8 *data) -{ - struct virtnet_info *vi = netdev_priv(dev); - unsigned int i, j; - u8 *p = data; - - switch (stringset) { - case ETH_SS_STATS: - for (i = 0; i < vi->curr_queue_pairs; i++) { - for (j = 0; j < VIRTNET_RQ_STATS_LEN; j++) - ethtool_sprintf(&p, "rx_queue_%u_%s", i, - virtnet_rq_stats_desc[j].desc); - } - - for (i = 0; i < vi->curr_queue_pairs; i++) { - for (j = 0; j < VIRTNET_SQ_STATS_LEN; j++) - ethtool_sprintf(&p, "tx_queue_%u_%s", i, - virtnet_sq_stats_desc[j].desc); - } - break; - } -} - -static int virtnet_get_sset_count(struct net_device *dev, int sset) -{ - struct virtnet_info *vi = netdev_priv(dev); - - switch (sset) { - case ETH_SS_STATS: - return vi->curr_queue_pairs * (VIRTNET_RQ_STATS_LEN + - VIRTNET_SQ_STATS_LEN); - default: - return -EOPNOTSUPP; - } -} - -static void virtnet_get_ethtool_stats(struct net_device *dev, - struct ethtool_stats *stats, u64 *data) -{ - struct virtnet_info *vi = netdev_priv(dev); - unsigned int idx = 0, start, i, j; - const u8 *stats_base; - size_t offset; - - for (i = 0; i < vi->curr_queue_pairs; i++) { - struct virtnet_rq *rq = &vi->rq[i]; - - stats_base = (u8 *)&rq->stats; - do { - start = u64_stats_fetch_begin(&rq->stats.syncp); - for (j = 0; j < VIRTNET_RQ_STATS_LEN; j++) { - offset = virtnet_rq_stats_desc[j].offset; - data[idx + j] = *(u64 *)(stats_base + offset); - } - } while (u64_stats_fetch_retry(&rq->stats.syncp, start)); - idx += VIRTNET_RQ_STATS_LEN; - } - - for (i = 0; i < vi->curr_queue_pairs; i++) { - struct virtnet_sq *sq = &vi->sq[i]; - - stats_base = (u8 *)&sq->stats; - do { - start = u64_stats_fetch_begin(&sq->stats.syncp); - for (j = 0; j < VIRTNET_SQ_STATS_LEN; j++) { - offset = virtnet_sq_stats_desc[j].offset; - data[idx + j] = *(u64 *)(stats_base + offset); - } - } while (u64_stats_fetch_retry(&sq->stats.syncp, start)); - idx += VIRTNET_SQ_STATS_LEN; - } -} - -static void virtnet_get_channels(struct net_device *dev, - struct ethtool_channels *channels) -{ - struct virtnet_info *vi = netdev_priv(dev); - - channels->combined_count = vi->curr_queue_pairs; - channels->max_combined = vi->max_queue_pairs; - channels->max_other = 0; - channels->rx_count = 0; - channels->tx_count = 0; - channels->other_count = 0; -} - -static int virtnet_set_link_ksettings(struct net_device *dev, - const struct ethtool_link_ksettings *cmd) -{ - struct virtnet_info *vi = netdev_priv(dev); - - return ethtool_virtdev_set_link_ksettings(dev, cmd, - &vi->speed, &vi->duplex); -} - -static int virtnet_get_link_ksettings(struct net_device *dev, - struct ethtool_link_ksettings *cmd) -{ - struct virtnet_info *vi = netdev_priv(dev); - - cmd->base.speed = vi->speed; - cmd->base.duplex = vi->duplex; - cmd->base.port = PORT_OTHER; - - return 0; -} - -static int virtnet_coal_params_supported(struct ethtool_coalesce *ec) -{ - /* usecs coalescing is supported only if VIRTIO_NET_F_NOTF_COAL - * feature is negotiated. - */ - if (ec->rx_coalesce_usecs || ec->tx_coalesce_usecs) - return -EOPNOTSUPP; - - if (ec->tx_max_coalesced_frames > 1 || - ec->rx_max_coalesced_frames != 1) - return -EINVAL; - - return 0; -} - -static int virtnet_set_coalesce(struct net_device *dev, - struct ethtool_coalesce *ec, - struct kernel_ethtool_coalesce *kernel_coal, - struct netlink_ext_ack *extack) -{ - struct virtnet_info *vi = netdev_priv(dev); - int ret, i, napi_weight; - bool update_napi = false; - - /* Can't change NAPI weight if the link is up */ - napi_weight = ec->tx_max_coalesced_frames ? NAPI_POLL_WEIGHT : 0; - if (napi_weight ^ vi->sq[0].napi.weight) { - if (dev->flags & IFF_UP) - return -EBUSY; - else - update_napi = true; - } - - if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_NOTF_COAL)) - ret = virtnet_send_notf_coal_cmds(vi, ec); - else - ret = virtnet_coal_params_supported(ec); - - if (ret) - return ret; - - if (update_napi) { - for (i = 0; i < vi->max_queue_pairs; i++) - vi->sq[i].napi.weight = napi_weight; - } - - return ret; -} - -static int virtnet_get_coalesce(struct net_device *dev, - struct ethtool_coalesce *ec, - struct kernel_ethtool_coalesce *kernel_coal, - struct netlink_ext_ack *extack) -{ - struct virtnet_info *vi = netdev_priv(dev); - - if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_NOTF_COAL)) { - ec->rx_coalesce_usecs = vi->rx_usecs; - ec->tx_coalesce_usecs = vi->tx_usecs; - ec->tx_max_coalesced_frames = vi->tx_max_packets; - ec->rx_max_coalesced_frames = vi->rx_max_packets; - } else { - ec->rx_max_coalesced_frames = 1; - - if (vi->sq[0].napi.weight) - ec->tx_max_coalesced_frames = 1; - } - - return 0; -} - static void virtnet_init_settings(struct net_device *dev) { struct virtnet_info *vi = netdev_priv(dev); @@ -2495,117 +2043,6 @@ static void virtnet_update_settings(struct virtnet_info *vi) vi->duplex = duplex; } -static u32 virtnet_get_rxfh_key_size(struct net_device *dev) -{ - return ((struct virtnet_info *)netdev_priv(dev))->rss_key_size; -} - -static u32 virtnet_get_rxfh_indir_size(struct net_device *dev) -{ - return ((struct virtnet_info *)netdev_priv(dev))->rss_indir_table_size; -} - -static int virtnet_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, u8 *hfunc) -{ - struct virtnet_info *vi = netdev_priv(dev); - int i; - - if (indir) { - for (i = 0; i < vi->rss_indir_table_size; ++i) - indir[i] = vi->ctrl->rss.indirection_table[i]; - } - - if (key) - memcpy(key, vi->ctrl->rss.key, vi->rss_key_size); - - if (hfunc) - *hfunc = ETH_RSS_HASH_TOP; - - return 0; -} - -static int virtnet_set_rxfh(struct net_device *dev, const u32 *indir, const u8 *key, const u8 hfunc) -{ - struct virtnet_info *vi = netdev_priv(dev); - int i; - - if (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP) - return -EOPNOTSUPP; - - if (indir) { - for (i = 0; i < vi->rss_indir_table_size; ++i) - vi->ctrl->rss.indirection_table[i] = indir[i]; - } - if (key) - memcpy(vi->ctrl->rss.key, key, vi->rss_key_size); - - virtnet_commit_rss_command(vi); - - return 0; -} - -static int virtnet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info, u32 *rule_locs) -{ - struct virtnet_info *vi = netdev_priv(dev); - int rc = 0; - - switch (info->cmd) { - case ETHTOOL_GRXRINGS: - info->data = vi->curr_queue_pairs; - break; - case ETHTOOL_GRXFH: - virtnet_get_hashflow(vi, info); - break; - default: - rc = -EOPNOTSUPP; - } - - return rc; -} - -static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info) -{ - struct virtnet_info *vi = netdev_priv(dev); - int rc = 0; - - switch (info->cmd) { - case ETHTOOL_SRXFH: - if (!virtnet_set_hashflow(vi, info)) - rc = -EINVAL; - - break; - default: - rc = -EOPNOTSUPP; - } - - return rc; -} - -static const struct ethtool_ops virtnet_ethtool_ops = { - .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES | - ETHTOOL_COALESCE_USECS, - .get_drvinfo = virtnet_get_drvinfo, - .get_link = ethtool_op_get_link, - .get_ringparam = virtnet_get_ringparam, - .set_ringparam = virtnet_set_ringparam, - .get_strings = virtnet_get_strings, - .get_sset_count = virtnet_get_sset_count, - .get_ethtool_stats = virtnet_get_ethtool_stats, - .set_channels = virtnet_set_channels, - .get_channels = virtnet_get_channels, - .get_ts_info = ethtool_op_get_ts_info, - .get_link_ksettings = virtnet_get_link_ksettings, - .set_link_ksettings = virtnet_set_link_ksettings, - .set_coalesce = virtnet_set_coalesce, - .get_coalesce = virtnet_get_coalesce, - .get_rxfh_key_size = virtnet_get_rxfh_key_size, - .get_rxfh_indir_size = virtnet_get_rxfh_indir_size, - .get_rxfh = virtnet_get_rxfh, - .set_rxfh = virtnet_set_rxfh, - .get_rxnfc = virtnet_get_rxnfc, - .set_rxnfc = virtnet_set_rxnfc, -}; - static void virtnet_freeze_down(struct virtio_device *vdev) { struct virtnet_info *vi = vdev->priv; @@ -3352,7 +2789,7 @@ static int virtnet_probe(struct virtio_device *vdev) dev->netdev_ops = &virtnet_netdev; dev->features = NETIF_F_HIGHDMA; - dev->ethtool_ops = &virtnet_ethtool_ops; + dev->ethtool_ops = virtnet_get_ethtool_ops(); SET_NETDEV_DEV(dev, &vdev->dev); /* Do we support "hardware" checksums? */ diff --git a/drivers/net/virtio/virtnet.h b/drivers/net/virtio/virtnet.h index 669e0499f340..b889825c54d0 100644 --- a/drivers/net/virtio/virtnet.h +++ b/drivers/net/virtio/virtnet.h @@ -181,4 +181,7 @@ struct virtnet_info { struct failover *failover; }; +int virtnet_rx_resize(struct virtnet_info *vi, struct virtnet_rq *rq, u32 ring_num); +int virtnet_tx_resize(struct virtnet_info *vi, struct virtnet_sq *sq, u32 ring_num); +int _virtnet_set_queues(struct virtnet_info *vi, u16 queue_pairs); #endif diff --git a/drivers/net/virtio/virtnet_ethtool.c b/drivers/net/virtio/virtnet_ethtool.c new file mode 100644 index 000000000000..ac0595744b4c --- /dev/null +++ b/drivers/net/virtio/virtnet_ethtool.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// +#include <linux/netdevice.h> +#include <linux/ethtool.h> +#include <linux/cpu.h> +#include <linux/virtio.h> +#include <linux/virtio_net.h> + +#include "virtnet.h" +#include "virtnet_ctrl.h" +#include "virtnet_common.h" +#include "virtnet_ethtool.h" + +struct virtnet_stat_desc { + char desc[ETH_GSTRING_LEN]; + size_t offset; +}; + +#define VIRTNET_SQ_STAT(m) offsetof(struct virtnet_sq_stats, m) +#define VIRTNET_RQ_STAT(m) offsetof(struct virtnet_rq_stats, m) + +static const struct virtnet_stat_desc virtnet_sq_stats_desc[] = { + { "packets", VIRTNET_SQ_STAT(packets) }, + { "bytes", VIRTNET_SQ_STAT(bytes) }, + { "xdp_tx", VIRTNET_SQ_STAT(xdp_tx) }, + { "xdp_tx_drops", VIRTNET_SQ_STAT(xdp_tx_drops) }, + { "kicks", VIRTNET_SQ_STAT(kicks) }, + { "tx_timeouts", VIRTNET_SQ_STAT(tx_timeouts) }, +}; + +static const struct virtnet_stat_desc virtnet_rq_stats_desc[] = { + { "packets", VIRTNET_RQ_STAT(packets) }, + { "bytes", VIRTNET_RQ_STAT(bytes) }, + { "drops", VIRTNET_RQ_STAT(drops) }, + { "xdp_packets", VIRTNET_RQ_STAT(xdp_packets) }, + { "xdp_tx", VIRTNET_RQ_STAT(xdp_tx) }, + { "xdp_redirects", VIRTNET_RQ_STAT(xdp_redirects) }, + { "xdp_drops", VIRTNET_RQ_STAT(xdp_drops) }, + { "kicks", VIRTNET_RQ_STAT(kicks) }, +}; + +#define VIRTNET_SQ_STATS_LEN ARRAY_SIZE(virtnet_sq_stats_desc) +#define VIRTNET_RQ_STATS_LEN ARRAY_SIZE(virtnet_rq_stats_desc) + +void virtnet_rq_update_stats(struct virtnet_rq *rq, struct virtnet_rq_stats *stats) +{ + int i; + + u64_stats_update_begin(&rq->stats.syncp); + for (i = 0; i < VIRTNET_RQ_STATS_LEN; i++) { + size_t offset = virtnet_rq_stats_desc[i].offset; + u64 *item; + + item = (u64 *)((u8 *)&rq->stats + offset); + *item += *(u64 *)((u8 *)stats + offset); + } + u64_stats_update_end(&rq->stats.syncp); +} + +static void virtnet_get_ringparam(struct net_device *dev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct virtnet_info *vi = netdev_priv(dev); + + ring->rx_max_pending = vi->rq[0].vq->num_max; + ring->tx_max_pending = vi->sq[0].vq->num_max; + ring->rx_pending = virtqueue_get_vring_size(vi->rq[0].vq); + ring->tx_pending = virtqueue_get_vring_size(vi->sq[0].vq); +} + +static int virtnet_set_ringparam(struct net_device *dev, + struct ethtool_ringparam *ring, + struct kernel_ethtool_ringparam *kernel_ring, + struct netlink_ext_ack *extack) +{ + struct virtnet_info *vi = netdev_priv(dev); + u32 rx_pending, tx_pending; + struct virtnet_rq *rq; + struct virtnet_sq *sq; + int i, err; + + if (ring->rx_mini_pending || ring->rx_jumbo_pending) + return -EINVAL; + + rx_pending = virtqueue_get_vring_size(vi->rq[0].vq); + tx_pending = virtqueue_get_vring_size(vi->sq[0].vq); + + if (ring->rx_pending == rx_pending && + ring->tx_pending == tx_pending) + return 0; + + if (ring->rx_pending > vi->rq[0].vq->num_max) + return -EINVAL; + + if (ring->tx_pending > vi->sq[0].vq->num_max) + return -EINVAL; + + for (i = 0; i < vi->max_queue_pairs; i++) { + rq = vi->rq + i; + sq = vi->sq + i; + + if (ring->tx_pending != tx_pending) { + err = virtnet_tx_resize(vi, sq, ring->tx_pending); + if (err) + return err; + } + + if (ring->rx_pending != rx_pending) { + err = virtnet_rx_resize(vi, rq, ring->rx_pending); + if (err) + return err; + } + } + + return 0; +} + +static void virtnet_get_hashflow(const struct virtnet_info *vi, struct ethtool_rxnfc *info) +{ + info->data = 0; + switch (info->flow_type) { + case TCP_V4_FLOW: + if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_TCPv4) { + info->data = RXH_IP_SRC | RXH_IP_DST | + RXH_L4_B_0_1 | RXH_L4_B_2_3; + } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv4) { + info->data = RXH_IP_SRC | RXH_IP_DST; + } + break; + case TCP_V6_FLOW: + if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_TCPv6) { + info->data = RXH_IP_SRC | RXH_IP_DST | + RXH_L4_B_0_1 | RXH_L4_B_2_3; + } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv6) { + info->data = RXH_IP_SRC | RXH_IP_DST; + } + break; + case UDP_V4_FLOW: + if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_UDPv4) { + info->data = RXH_IP_SRC | RXH_IP_DST | + RXH_L4_B_0_1 | RXH_L4_B_2_3; + } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv4) { + info->data = RXH_IP_SRC | RXH_IP_DST; + } + break; + case UDP_V6_FLOW: + if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_UDPv6) { + info->data = RXH_IP_SRC | RXH_IP_DST | + RXH_L4_B_0_1 | RXH_L4_B_2_3; + } else if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv6) { + info->data = RXH_IP_SRC | RXH_IP_DST; + } + break; + case IPV4_FLOW: + if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv4) + info->data = RXH_IP_SRC | RXH_IP_DST; + + break; + case IPV6_FLOW: + if (vi->rss_hash_types_saved & VIRTIO_NET_RSS_HASH_TYPE_IPv6) + info->data = RXH_IP_SRC | RXH_IP_DST; + + break; + default: + info->data = 0; + break; + } +} + +static bool virtnet_set_hashflow(struct virtnet_info *vi, struct ethtool_rxnfc *info) +{ + u32 new_hashtypes = vi->rss_hash_types_saved; + bool is_disable = info->data & RXH_DISCARD; + bool is_l4 = info->data == (RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3); + + /* supports only 'sd', 'sdfn' and 'r' */ + if (!((info->data == (RXH_IP_SRC | RXH_IP_DST)) | is_l4 | is_disable)) + return false; + + switch (info->flow_type) { + case TCP_V4_FLOW: + new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv4 | VIRTIO_NET_RSS_HASH_TYPE_TCPv4); + if (!is_disable) + new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv4 + | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_TCPv4 : 0); + break; + case UDP_V4_FLOW: + new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv4 | VIRTIO_NET_RSS_HASH_TYPE_UDPv4); + if (!is_disable) + new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv4 + | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_UDPv4 : 0); + break; + case IPV4_FLOW: + new_hashtypes &= ~VIRTIO_NET_RSS_HASH_TYPE_IPv4; + if (!is_disable) + new_hashtypes = VIRTIO_NET_RSS_HASH_TYPE_IPv4; + break; + case TCP_V6_FLOW: + new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv6 | VIRTIO_NET_RSS_HASH_TYPE_TCPv6); + if (!is_disable) + new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv6 + | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_TCPv6 : 0); + break; + case UDP_V6_FLOW: + new_hashtypes &= ~(VIRTIO_NET_RSS_HASH_TYPE_IPv6 | VIRTIO_NET_RSS_HASH_TYPE_UDPv6); + if (!is_disable) + new_hashtypes |= VIRTIO_NET_RSS_HASH_TYPE_IPv6 + | (is_l4 ? VIRTIO_NET_RSS_HASH_TYPE_UDPv6 : 0); + break; + case IPV6_FLOW: + new_hashtypes &= ~VIRTIO_NET_RSS_HASH_TYPE_IPv6; + if (!is_disable) + new_hashtypes = VIRTIO_NET_RSS_HASH_TYPE_IPv6; + break; + default: + /* unsupported flow */ + return false; + } + + /* if unsupported hashtype was set */ + if (new_hashtypes != (new_hashtypes & vi->rss_hash_types_supported)) + return false; + + if (new_hashtypes != vi->rss_hash_types_saved) { + vi->rss_hash_types_saved = new_hashtypes; + vi->ctrl->rss.hash_types = vi->rss_hash_types_saved; + if (vi->dev->features & NETIF_F_RXHASH) + return virtnet_commit_rss_command(vi); + } + + return true; +} + +static void virtnet_get_drvinfo(struct net_device *dev, + struct ethtool_drvinfo *info) +{ + struct virtnet_info *vi = netdev_priv(dev); + struct virtio_device *vdev = vi->vdev; + + strscpy(info->driver, KBUILD_MODNAME, sizeof(info->driver)); + strscpy(info->version, VIRTNET_DRIVER_VERSION, sizeof(info->version)); + strscpy(info->bus_info, virtio_bus_name(vdev), sizeof(info->bus_info)); +} + +/* TODO: Eliminate OOO packets during switching */ +static int virtnet_set_channels(struct net_device *dev, + struct ethtool_channels *channels) +{ + struct virtnet_info *vi = netdev_priv(dev); + u16 queue_pairs = channels->combined_count; + int err; + + /* We don't support separate rx/tx channels. + * We don't allow setting 'other' channels. + */ + if (channels->rx_count || channels->tx_count || channels->other_count) + return -EINVAL; + + if (queue_pairs > vi->max_queue_pairs || queue_pairs == 0) + return -EINVAL; + + /* For now we don't support modifying channels while XDP is loaded + * also when XDP is loaded all RX queues have XDP programs so we only + * need to check a single RX queue. + */ + if (vi->rq[0].xdp_prog) + return -EINVAL; + + cpus_read_lock(); + err = _virtnet_set_queues(vi, queue_pairs); + if (err) { + cpus_read_unlock(); + goto err; + } + virtnet_set_affinity(vi); + cpus_read_unlock(); + + netif_set_real_num_tx_queues(dev, queue_pairs); + netif_set_real_num_rx_queues(dev, queue_pairs); + err: + return err; +} + +static void virtnet_get_strings(struct net_device *dev, u32 stringset, u8 *data) +{ + struct virtnet_info *vi = netdev_priv(dev); + unsigned int i, j; + u8 *p = data; + + switch (stringset) { + case ETH_SS_STATS: + for (i = 0; i < vi->curr_queue_pairs; i++) { + for (j = 0; j < VIRTNET_RQ_STATS_LEN; j++) + ethtool_sprintf(&p, "rx_queue_%u_%s", i, + virtnet_rq_stats_desc[j].desc); + } + + for (i = 0; i < vi->curr_queue_pairs; i++) { + for (j = 0; j < VIRTNET_SQ_STATS_LEN; j++) + ethtool_sprintf(&p, "tx_queue_%u_%s", i, + virtnet_sq_stats_desc[j].desc); + } + break; + } +} + +static int virtnet_get_sset_count(struct net_device *dev, int sset) +{ + struct virtnet_info *vi = netdev_priv(dev); + + switch (sset) { + case ETH_SS_STATS: + return vi->curr_queue_pairs * (VIRTNET_RQ_STATS_LEN + + VIRTNET_SQ_STATS_LEN); + default: + return -EOPNOTSUPP; + } +} + +static void virtnet_get_ethtool_stats(struct net_device *dev, + struct ethtool_stats *stats, u64 *data) +{ + struct virtnet_info *vi = netdev_priv(dev); + unsigned int idx = 0, start, i, j; + const u8 *stats_base; + size_t offset; + + for (i = 0; i < vi->curr_queue_pairs; i++) { + struct virtnet_rq *rq = &vi->rq[i]; + + stats_base = (u8 *)&rq->stats; + do { + start = u64_stats_fetch_begin(&rq->stats.syncp); + for (j = 0; j < VIRTNET_RQ_STATS_LEN; j++) { + offset = virtnet_rq_stats_desc[j].offset; + data[idx + j] = *(u64 *)(stats_base + offset); + } + } while (u64_stats_fetch_retry(&rq->stats.syncp, start)); + idx += VIRTNET_RQ_STATS_LEN; + } + + for (i = 0; i < vi->curr_queue_pairs; i++) { + struct virtnet_sq *sq = &vi->sq[i]; + + stats_base = (u8 *)&sq->stats; + do { + start = u64_stats_fetch_begin(&sq->stats.syncp); + for (j = 0; j < VIRTNET_SQ_STATS_LEN; j++) { + offset = virtnet_sq_stats_desc[j].offset; + data[idx + j] = *(u64 *)(stats_base + offset); + } + } while (u64_stats_fetch_retry(&sq->stats.syncp, start)); + idx += VIRTNET_SQ_STATS_LEN; + } +} + +static void virtnet_get_channels(struct net_device *dev, + struct ethtool_channels *channels) +{ + struct virtnet_info *vi = netdev_priv(dev); + + channels->combined_count = vi->curr_queue_pairs; + channels->max_combined = vi->max_queue_pairs; + channels->max_other = 0; + channels->rx_count = 0; + channels->tx_count = 0; + channels->other_count = 0; +} + +static int virtnet_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *cmd) +{ + struct virtnet_info *vi = netdev_priv(dev); + + cmd->base.speed = vi->speed; + cmd->base.duplex = vi->duplex; + cmd->base.port = PORT_OTHER; + + return 0; +} + +static int virtnet_set_link_ksettings(struct net_device *dev, + const struct ethtool_link_ksettings *cmd) +{ + struct virtnet_info *vi = netdev_priv(dev); + + return ethtool_virtdev_set_link_ksettings(dev, cmd, + &vi->speed, &vi->duplex); +} + +static int virtnet_coal_params_supported(struct ethtool_coalesce *ec) +{ + /* usecs coalescing is supported only if VIRTIO_NET_F_NOTF_COAL + * feature is negotiated. + */ + if (ec->rx_coalesce_usecs || ec->tx_coalesce_usecs) + return -EOPNOTSUPP; + + if (ec->tx_max_coalesced_frames > 1 || + ec->rx_max_coalesced_frames != 1) + return -EINVAL; + + return 0; +} + +static int virtnet_set_coalesce(struct net_device *dev, + struct ethtool_coalesce *ec, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct virtnet_info *vi = netdev_priv(dev); + int ret, i, napi_weight; + bool update_napi = false; + + /* Can't change NAPI weight if the link is up */ + napi_weight = ec->tx_max_coalesced_frames ? NAPI_POLL_WEIGHT : 0; + if (napi_weight ^ vi->sq[0].napi.weight) { + if (dev->flags & IFF_UP) + return -EBUSY; + else + update_napi = true; + } + + if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_NOTF_COAL)) + ret = virtnet_send_notf_coal_cmds(vi, ec); + else + ret = virtnet_coal_params_supported(ec); + + if (ret) + return ret; + + if (update_napi) { + for (i = 0; i < vi->max_queue_pairs; i++) + vi->sq[i].napi.weight = napi_weight; + } + + return ret; +} + +static int virtnet_get_coalesce(struct net_device *dev, + struct ethtool_coalesce *ec, + struct kernel_ethtool_coalesce *kernel_coal, + struct netlink_ext_ack *extack) +{ + struct virtnet_info *vi = netdev_priv(dev); + + if (virtio_has_feature(vi->vdev, VIRTIO_NET_F_NOTF_COAL)) { + ec->rx_coalesce_usecs = vi->rx_usecs; + ec->tx_coalesce_usecs = vi->tx_usecs; + ec->tx_max_coalesced_frames = vi->tx_max_packets; + ec->rx_max_coalesced_frames = vi->rx_max_packets; + } else { + ec->rx_max_coalesced_frames = 1; + + if (vi->sq[0].napi.weight) + ec->tx_max_coalesced_frames = 1; + } + + return 0; +} + +static u32 virtnet_get_rxfh_key_size(struct net_device *dev) +{ + return ((struct virtnet_info *)netdev_priv(dev))->rss_key_size; +} + +static u32 virtnet_get_rxfh_indir_size(struct net_device *dev) +{ + return ((struct virtnet_info *)netdev_priv(dev))->rss_indir_table_size; +} + +static int virtnet_get_rxfh(struct net_device *dev, u32 *indir, u8 *key, u8 *hfunc) +{ + struct virtnet_info *vi = netdev_priv(dev); + int i; + + if (indir) { + for (i = 0; i < vi->rss_indir_table_size; ++i) + indir[i] = vi->ctrl->rss.indirection_table[i]; + } + + if (key) + memcpy(key, vi->ctrl->rss.key, vi->rss_key_size); + + if (hfunc) + *hfunc = ETH_RSS_HASH_TOP; + + return 0; +} + +static int virtnet_set_rxfh(struct net_device *dev, const u32 *indir, const u8 *key, const u8 hfunc) +{ + struct virtnet_info *vi = netdev_priv(dev); + int i; + + if (hfunc != ETH_RSS_HASH_NO_CHANGE && hfunc != ETH_RSS_HASH_TOP) + return -EOPNOTSUPP; + + if (indir) { + for (i = 0; i < vi->rss_indir_table_size; ++i) + vi->ctrl->rss.indirection_table[i] = indir[i]; + } + if (key) + memcpy(vi->ctrl->rss.key, key, vi->rss_key_size); + + virtnet_commit_rss_command(vi); + + return 0; +} + +static int virtnet_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info, u32 *rule_locs) +{ + struct virtnet_info *vi = netdev_priv(dev); + int rc = 0; + + switch (info->cmd) { + case ETHTOOL_GRXRINGS: + info->data = vi->curr_queue_pairs; + break; + case ETHTOOL_GRXFH: + virtnet_get_hashflow(vi, info); + break; + default: + rc = -EOPNOTSUPP; + } + + return rc; +} + +static int virtnet_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *info) +{ + struct virtnet_info *vi = netdev_priv(dev); + int rc = 0; + + switch (info->cmd) { + case ETHTOOL_SRXFH: + if (!virtnet_set_hashflow(vi, info)) + rc = -EINVAL; + + break; + default: + rc = -EOPNOTSUPP; + } + + return rc; +} + +static const struct ethtool_ops virtnet_ethtool_ops = { + .supported_coalesce_params = ETHTOOL_COALESCE_MAX_FRAMES | + ETHTOOL_COALESCE_USECS, + .get_drvinfo = virtnet_get_drvinfo, + .get_link = ethtool_op_get_link, + .get_ringparam = virtnet_get_ringparam, + .set_ringparam = virtnet_set_ringparam, + .get_strings = virtnet_get_strings, + .get_sset_count = virtnet_get_sset_count, + .get_ethtool_stats = virtnet_get_ethtool_stats, + .set_channels = virtnet_set_channels, + .get_channels = virtnet_get_channels, + .get_ts_info = ethtool_op_get_ts_info, + .get_link_ksettings = virtnet_get_link_ksettings, + .set_link_ksettings = virtnet_set_link_ksettings, + .set_coalesce = virtnet_set_coalesce, + .get_coalesce = virtnet_get_coalesce, + .get_rxfh_key_size = virtnet_get_rxfh_key_size, + .get_rxfh_indir_size = virtnet_get_rxfh_indir_size, + .get_rxfh = virtnet_get_rxfh, + .set_rxfh = virtnet_set_rxfh, + .get_rxnfc = virtnet_get_rxnfc, + .set_rxnfc = virtnet_set_rxnfc, +}; + +const struct ethtool_ops *virtnet_get_ethtool_ops(void) +{ + return &virtnet_ethtool_ops; +} diff --git a/drivers/net/virtio/virtnet_ethtool.h b/drivers/net/virtio/virtnet_ethtool.h new file mode 100644 index 000000000000..ed1b7a4877e0 --- /dev/null +++ b/drivers/net/virtio/virtnet_ethtool.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __VIRNET_ETHTOOL_H__ +#define __VIRNET_ETHTOOL_H__ + +void virtnet_rq_update_stats(struct virtnet_rq *rq, struct virtnet_rq_stats *stats); +const struct ethtool_ops *virtnet_get_ethtool_ops(void); +#endif -- 2.32.0.3.g01195cf9f _______________________________________________ Virtualization mailing list Virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linuxfoundation.org/mailman/listinfo/virtualization