// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */ #include #include #include #include "wx_type.h" #include "wx_ethtool.h" #include "wx_hw.h" #include "wx_lib.h" struct wx_stats { char stat_string[ETH_GSTRING_LEN]; size_t sizeof_stat; off_t stat_offset; }; #define WX_STAT(str, m) { \ .stat_string = str, \ .sizeof_stat = sizeof(((struct wx *)0)->m), \ .stat_offset = offsetof(struct wx, m) } static const struct wx_stats wx_gstrings_stats[] = { WX_STAT("rx_dma_pkts", stats.gprc), WX_STAT("tx_dma_pkts", stats.gptc), WX_STAT("rx_dma_bytes", stats.gorc), WX_STAT("tx_dma_bytes", stats.gotc), WX_STAT("rx_total_pkts", stats.tpr), WX_STAT("tx_total_pkts", stats.tpt), WX_STAT("rx_long_length_count", stats.roc), WX_STAT("rx_short_length_count", stats.ruc), WX_STAT("os2bmc_rx_by_bmc", stats.o2bgptc), WX_STAT("os2bmc_tx_by_bmc", stats.b2ospc), WX_STAT("os2bmc_tx_by_host", stats.o2bspc), WX_STAT("os2bmc_rx_by_host", stats.b2ogprc), WX_STAT("rx_no_dma_resources", stats.rdmdrop), WX_STAT("tx_busy", tx_busy), WX_STAT("non_eop_descs", non_eop_descs), WX_STAT("tx_restart_queue", restart_queue), WX_STAT("rx_csum_offload_good_count", hw_csum_rx_good), WX_STAT("rx_csum_offload_errors", hw_csum_rx_error), WX_STAT("alloc_rx_buff_failed", alloc_rx_buff_failed), }; /* drivers allocates num_tx_queues and num_rx_queues symmetrically so * we set the num_rx_queues to evaluate to num_tx_queues. This is * used because we do not have a good way to get the max number of * rx queues with CONFIG_RPS disabled. */ #define WX_NUM_RX_QUEUES netdev->num_tx_queues #define WX_NUM_TX_QUEUES netdev->num_tx_queues #define WX_QUEUE_STATS_LEN ( \ (WX_NUM_TX_QUEUES + WX_NUM_RX_QUEUES) * \ (sizeof(struct wx_queue_stats) / sizeof(u64))) #define WX_GLOBAL_STATS_LEN ARRAY_SIZE(wx_gstrings_stats) #define WX_STATS_LEN (WX_GLOBAL_STATS_LEN + WX_QUEUE_STATS_LEN) int wx_get_sset_count(struct net_device *netdev, int sset) { switch (sset) { case ETH_SS_STATS: return WX_STATS_LEN; default: return -EOPNOTSUPP; } } EXPORT_SYMBOL(wx_get_sset_count); void wx_get_strings(struct net_device *netdev, u32 stringset, u8 *data) { u8 *p = data; int i; switch (stringset) { case ETH_SS_STATS: for (i = 0; i < WX_GLOBAL_STATS_LEN; i++) ethtool_puts(&p, wx_gstrings_stats[i].stat_string); for (i = 0; i < netdev->num_tx_queues; i++) { ethtool_sprintf(&p, "tx_queue_%u_packets", i); ethtool_sprintf(&p, "tx_queue_%u_bytes", i); } for (i = 0; i < WX_NUM_RX_QUEUES; i++) { ethtool_sprintf(&p, "rx_queue_%u_packets", i); ethtool_sprintf(&p, "rx_queue_%u_bytes", i); } break; } } EXPORT_SYMBOL(wx_get_strings); void wx_get_ethtool_stats(struct net_device *netdev, struct ethtool_stats *stats, u64 *data) { struct wx *wx = netdev_priv(netdev); struct wx_ring *ring; unsigned int start; int i, j; char *p; wx_update_stats(wx); for (i = 0; i < WX_GLOBAL_STATS_LEN; i++) { p = (char *)wx + wx_gstrings_stats[i].stat_offset; data[i] = (wx_gstrings_stats[i].sizeof_stat == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; } for (j = 0; j < netdev->num_tx_queues; j++) { ring = wx->tx_ring[j]; if (!ring) { data[i++] = 0; data[i++] = 0; continue; } do { start = u64_stats_fetch_begin(&ring->syncp); data[i] = ring->stats.packets; data[i + 1] = ring->stats.bytes; } while (u64_stats_fetch_retry(&ring->syncp, start)); i += 2; } for (j = 0; j < WX_NUM_RX_QUEUES; j++) { ring = wx->rx_ring[j]; if (!ring) { data[i++] = 0; data[i++] = 0; continue; } do { start = u64_stats_fetch_begin(&ring->syncp); data[i] = ring->stats.packets; data[i + 1] = ring->stats.bytes; } while (u64_stats_fetch_retry(&ring->syncp, start)); i += 2; } } EXPORT_SYMBOL(wx_get_ethtool_stats); void wx_get_mac_stats(struct net_device *netdev, struct ethtool_eth_mac_stats *mac_stats) { struct wx *wx = netdev_priv(netdev); struct wx_hw_stats *hwstats; wx_update_stats(wx); hwstats = &wx->stats; mac_stats->MulticastFramesXmittedOK = hwstats->mptc; mac_stats->BroadcastFramesXmittedOK = hwstats->bptc; mac_stats->MulticastFramesReceivedOK = hwstats->mprc; mac_stats->BroadcastFramesReceivedOK = hwstats->bprc; } EXPORT_SYMBOL(wx_get_mac_stats); void wx_get_pause_stats(struct net_device *netdev, struct ethtool_pause_stats *stats) { struct wx *wx = netdev_priv(netdev); struct wx_hw_stats *hwstats; wx_update_stats(wx); hwstats = &wx->stats; stats->tx_pause_frames = hwstats->lxontxc + hwstats->lxofftxc; stats->rx_pause_frames = hwstats->lxonoffrxc; } EXPORT_SYMBOL(wx_get_pause_stats); void wx_get_drvinfo(struct net_device *netdev, struct ethtool_drvinfo *info) { struct wx *wx = netdev_priv(netdev); strscpy(info->driver, wx->driver_name, sizeof(info->driver)); strscpy(info->fw_version, wx->eeprom_id, sizeof(info->fw_version)); strscpy(info->bus_info, pci_name(wx->pdev), sizeof(info->bus_info)); if (wx->num_tx_queues <= WX_NUM_TX_QUEUES) { info->n_stats = WX_STATS_LEN - (WX_NUM_TX_QUEUES - wx->num_tx_queues) * (sizeof(struct wx_queue_stats) / sizeof(u64)) * 2; } else { info->n_stats = WX_STATS_LEN; } } EXPORT_SYMBOL(wx_get_drvinfo); int wx_nway_reset(struct net_device *netdev) { struct wx *wx = netdev_priv(netdev); return phylink_ethtool_nway_reset(wx->phylink); } EXPORT_SYMBOL(wx_nway_reset); int wx_get_link_ksettings(struct net_device *netdev, struct ethtool_link_ksettings *cmd) { struct wx *wx = netdev_priv(netdev); return phylink_ethtool_ksettings_get(wx->phylink, cmd); } EXPORT_SYMBOL(wx_get_link_ksettings); int wx_set_link_ksettings(struct net_device *netdev, const struct ethtool_link_ksettings *cmd) { struct wx *wx = netdev_priv(netdev); return phylink_ethtool_ksettings_set(wx->phylink, cmd); } EXPORT_SYMBOL(wx_set_link_ksettings); void wx_get_pauseparam(struct net_device *netdev, struct ethtool_pauseparam *pause) { struct wx *wx = netdev_priv(netdev); phylink_ethtool_get_pauseparam(wx->phylink, pause); } EXPORT_SYMBOL(wx_get_pauseparam); int wx_set_pauseparam(struct net_device *netdev, struct ethtool_pauseparam *pause) { struct wx *wx = netdev_priv(netdev); return phylink_ethtool_set_pauseparam(wx->phylink, pause); } EXPORT_SYMBOL(wx_set_pauseparam); void wx_get_ringparam(struct net_device *netdev, struct ethtool_ringparam *ring, struct kernel_ethtool_ringparam *kernel_ring, struct netlink_ext_ack *extack) { struct wx *wx = netdev_priv(netdev); ring->rx_max_pending = WX_MAX_RXD; ring->tx_max_pending = WX_MAX_TXD; ring->rx_mini_max_pending = 0; ring->rx_jumbo_max_pending = 0; ring->rx_pending = wx->rx_ring_count; ring->tx_pending = wx->tx_ring_count; ring->rx_mini_pending = 0; ring->rx_jumbo_pending = 0; } EXPORT_SYMBOL(wx_get_ringparam); int wx_get_coalesce(struct net_device *netdev, struct ethtool_coalesce *ec, struct kernel_ethtool_coalesce *kernel_coal, struct netlink_ext_ack *extack) { struct wx *wx = netdev_priv(netdev); ec->tx_max_coalesced_frames_irq = wx->tx_work_limit; /* only valid if in constant ITR mode */ if (wx->rx_itr_setting <= 1) ec->rx_coalesce_usecs = wx->rx_itr_setting; else ec->rx_coalesce_usecs = wx->rx_itr_setting >> 2; /* if in mixed tx/rx queues per vector mode, report only rx settings */ if (wx->q_vector[0]->tx.count && wx->q_vector[0]->rx.count) return 0; /* only valid if in constant ITR mode */ if (wx->tx_itr_setting <= 1) ec->tx_coalesce_usecs = wx->tx_itr_setting; else ec->tx_coalesce_usecs = wx->tx_itr_setting >> 2; return 0; } EXPORT_SYMBOL(wx_get_coalesce); int wx_set_coalesce(struct net_device *netdev, struct ethtool_coalesce *ec, struct kernel_ethtool_coalesce *kernel_coal, struct netlink_ext_ack *extack) { struct wx *wx = netdev_priv(netdev); u16 tx_itr_param, rx_itr_param; struct wx_q_vector *q_vector; u16 max_eitr; int i; if (wx->q_vector[0]->tx.count && wx->q_vector[0]->rx.count) { /* reject Tx specific changes in case of mixed RxTx vectors */ if (ec->tx_coalesce_usecs) return -EOPNOTSUPP; } if (ec->tx_max_coalesced_frames_irq) wx->tx_work_limit = ec->tx_max_coalesced_frames_irq; if (wx->mac.type == wx_mac_sp) max_eitr = WX_SP_MAX_EITR; else max_eitr = WX_EM_MAX_EITR; if ((ec->rx_coalesce_usecs > (max_eitr >> 2)) || (ec->tx_coalesce_usecs > (max_eitr >> 2))) return -EINVAL; if (ec->rx_coalesce_usecs > 1) wx->rx_itr_setting = ec->rx_coalesce_usecs << 2; else wx->rx_itr_setting = ec->rx_coalesce_usecs; if (wx->rx_itr_setting == 1) rx_itr_param = WX_20K_ITR; else rx_itr_param = wx->rx_itr_setting; if (ec->tx_coalesce_usecs > 1) wx->tx_itr_setting = ec->tx_coalesce_usecs << 2; else wx->tx_itr_setting = ec->tx_coalesce_usecs; if (wx->tx_itr_setting == 1) { if (wx->mac.type == wx_mac_sp) tx_itr_param = WX_12K_ITR; else tx_itr_param = WX_20K_ITR; } else { tx_itr_param = wx->tx_itr_setting; } /* mixed Rx/Tx */ if (wx->q_vector[0]->tx.count && wx->q_vector[0]->rx.count) wx->tx_itr_setting = wx->rx_itr_setting; for (i = 0; i < wx->num_q_vectors; i++) { q_vector = wx->q_vector[i]; if (q_vector->tx.count && !q_vector->rx.count) /* tx only */ q_vector->itr = tx_itr_param; else /* rx only or mixed */ q_vector->itr = rx_itr_param; wx_write_eitr(q_vector); } return 0; } EXPORT_SYMBOL(wx_set_coalesce); static unsigned int wx_max_channels(struct wx *wx) { unsigned int max_combined; if (!wx->msix_q_entries) { /* We only support one q_vector without MSI-X */ max_combined = 1; } else { /* support up to max allowed queues with RSS */ if (wx->mac.type == wx_mac_sp) max_combined = 63; else max_combined = 8; } return max_combined; } void wx_get_channels(struct net_device *dev, struct ethtool_channels *ch) { struct wx *wx = netdev_priv(dev); /* report maximum channels */ ch->max_combined = wx_max_channels(wx); /* report info for other vector */ if (wx->msix_q_entries) { ch->max_other = 1; ch->other_count = 1; } /* record RSS queues */ ch->combined_count = wx->ring_feature[RING_F_RSS].indices; } EXPORT_SYMBOL(wx_get_channels); int wx_set_channels(struct net_device *dev, struct ethtool_channels *ch) { unsigned int count = ch->combined_count; struct wx *wx = netdev_priv(dev); /* verify other_count has not changed */ if (ch->other_count != 1) return -EINVAL; /* verify the number of channels does not exceed hardware limits */ if (count > wx_max_channels(wx)) return -EINVAL; wx->ring_feature[RING_F_RSS].limit = count; return 0; } EXPORT_SYMBOL(wx_set_channels);