// SPDX-License-Identifier: GPL-2.0 /* Copyright(c) 2021 Ramaxel Memory Technology, Ltd */ #define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt #include #include #include #include #include #include #include #include #include #include #include #include "sphw_hw.h" #include "sphw_crm.h" #include "sphw_mt.h" #include "spnic_nic_cfg.h" #include "spnic_nic_dev.h" #include "spnic_tx.h" #include "spnic_rx.h" struct spnic_stats { char name[ETH_GSTRING_LEN]; u32 size; int offset; }; #define SPNIC_NETDEV_STAT(_stat_item) { \ .name = #_stat_item, \ .size = sizeof_field(struct rtnl_link_stats64, _stat_item), \ .offset = offsetof(struct rtnl_link_stats64, _stat_item) \ } static struct spnic_stats spnic_netdev_stats[] = { SPNIC_NETDEV_STAT(rx_packets), SPNIC_NETDEV_STAT(tx_packets), SPNIC_NETDEV_STAT(rx_bytes), SPNIC_NETDEV_STAT(tx_bytes), SPNIC_NETDEV_STAT(rx_errors), SPNIC_NETDEV_STAT(tx_errors), SPNIC_NETDEV_STAT(rx_dropped), SPNIC_NETDEV_STAT(tx_dropped), SPNIC_NETDEV_STAT(multicast), SPNIC_NETDEV_STAT(collisions), SPNIC_NETDEV_STAT(rx_length_errors), SPNIC_NETDEV_STAT(rx_over_errors), SPNIC_NETDEV_STAT(rx_crc_errors), SPNIC_NETDEV_STAT(rx_frame_errors), SPNIC_NETDEV_STAT(rx_fifo_errors), SPNIC_NETDEV_STAT(rx_missed_errors), SPNIC_NETDEV_STAT(tx_aborted_errors), SPNIC_NETDEV_STAT(tx_carrier_errors), SPNIC_NETDEV_STAT(tx_fifo_errors), SPNIC_NETDEV_STAT(tx_heartbeat_errors), }; #define SPNIC_NIC_STAT(_stat_item) { \ .name = #_stat_item, \ .size = sizeof_field(struct spnic_nic_stats, _stat_item), \ .offset = offsetof(struct spnic_nic_stats, _stat_item) \ } static struct spnic_stats spnic_nic_dev_stats[] = { SPNIC_NIC_STAT(netdev_tx_timeout), }; static struct spnic_stats spnic_nic_dev_stats_extern[] = { SPNIC_NIC_STAT(tx_carrier_off_drop), SPNIC_NIC_STAT(tx_invalid_qid), }; #define SPNIC_RXQ_STAT(_stat_item) { \ .name = "rxq%d_"#_stat_item, \ .size = sizeof_field(struct spnic_rxq_stats, _stat_item), \ .offset = offsetof(struct spnic_rxq_stats, _stat_item) \ } #define SPNIC_TXQ_STAT(_stat_item) { \ .name = "txq%d_"#_stat_item, \ .size = sizeof_field(struct spnic_txq_stats, _stat_item), \ .offset = offsetof(struct spnic_txq_stats, _stat_item) \ } static struct spnic_stats spnic_rx_queue_stats[] = { SPNIC_RXQ_STAT(packets), SPNIC_RXQ_STAT(bytes), SPNIC_RXQ_STAT(errors), SPNIC_RXQ_STAT(csum_errors), SPNIC_RXQ_STAT(other_errors), SPNIC_RXQ_STAT(dropped), SPNIC_RXQ_STAT(xdp_dropped), SPNIC_RXQ_STAT(rx_buf_empty), }; static struct spnic_stats spnic_rx_queue_stats_extern[] = { SPNIC_RXQ_STAT(alloc_skb_err), SPNIC_RXQ_STAT(alloc_rx_buf_err), SPNIC_RXQ_STAT(xdp_large_pkt), }; static struct spnic_stats spnic_tx_queue_stats[] = { SPNIC_TXQ_STAT(packets), SPNIC_TXQ_STAT(bytes), SPNIC_TXQ_STAT(busy), SPNIC_TXQ_STAT(wake), SPNIC_TXQ_STAT(dropped), }; static struct spnic_stats spnic_tx_queue_stats_extern[] = { SPNIC_TXQ_STAT(skb_pad_err), SPNIC_TXQ_STAT(frag_len_overflow), SPNIC_TXQ_STAT(offload_cow_skb_err), SPNIC_TXQ_STAT(map_frag_err), SPNIC_TXQ_STAT(unknown_tunnel_pkt), SPNIC_TXQ_STAT(frag_size_err), }; #define SPNIC_FUNC_STAT(_stat_item) { \ .name = #_stat_item, \ .size = sizeof_field(struct spnic_vport_stats, _stat_item), \ .offset = offsetof(struct spnic_vport_stats, _stat_item) \ } static struct spnic_stats spnic_function_stats[] = { SPNIC_FUNC_STAT(tx_unicast_pkts_vport), SPNIC_FUNC_STAT(tx_unicast_bytes_vport), SPNIC_FUNC_STAT(tx_multicast_pkts_vport), SPNIC_FUNC_STAT(tx_multicast_bytes_vport), SPNIC_FUNC_STAT(tx_broadcast_pkts_vport), SPNIC_FUNC_STAT(tx_broadcast_bytes_vport), SPNIC_FUNC_STAT(rx_unicast_pkts_vport), SPNIC_FUNC_STAT(rx_unicast_bytes_vport), SPNIC_FUNC_STAT(rx_multicast_pkts_vport), SPNIC_FUNC_STAT(rx_multicast_bytes_vport), SPNIC_FUNC_STAT(rx_broadcast_pkts_vport), SPNIC_FUNC_STAT(rx_broadcast_bytes_vport), SPNIC_FUNC_STAT(tx_discard_vport), SPNIC_FUNC_STAT(rx_discard_vport), SPNIC_FUNC_STAT(tx_err_vport), SPNIC_FUNC_STAT(rx_err_vport), }; #define SPNIC_PORT_STAT(_stat_item) { \ .name = #_stat_item, \ .size = sizeof_field(struct mag_cmd_port_stats, _stat_item), \ .offset = offsetof(struct mag_cmd_port_stats, _stat_item) \ } static struct spnic_stats spnic_port_stats[] = { SPNIC_PORT_STAT(mac_rx_total_pkt_num), SPNIC_PORT_STAT(mac_rx_total_oct_num), SPNIC_PORT_STAT(mac_rx_bad_pkt_num), SPNIC_PORT_STAT(mac_rx_bad_oct_num), SPNIC_PORT_STAT(mac_rx_good_pkt_num), SPNIC_PORT_STAT(mac_rx_good_oct_num), SPNIC_PORT_STAT(mac_rx_uni_pkt_num), SPNIC_PORT_STAT(mac_rx_multi_pkt_num), SPNIC_PORT_STAT(mac_rx_broad_pkt_num), SPNIC_PORT_STAT(mac_tx_total_pkt_num), SPNIC_PORT_STAT(mac_tx_total_oct_num), SPNIC_PORT_STAT(mac_tx_bad_pkt_num), SPNIC_PORT_STAT(mac_tx_bad_oct_num), SPNIC_PORT_STAT(mac_tx_good_pkt_num), SPNIC_PORT_STAT(mac_tx_good_oct_num), SPNIC_PORT_STAT(mac_tx_uni_pkt_num), SPNIC_PORT_STAT(mac_tx_multi_pkt_num), SPNIC_PORT_STAT(mac_tx_broad_pkt_num), SPNIC_PORT_STAT(mac_rx_fragment_pkt_num), SPNIC_PORT_STAT(mac_rx_undersize_pkt_num), SPNIC_PORT_STAT(mac_rx_undermin_pkt_num), SPNIC_PORT_STAT(mac_rx_64_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_65_127_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_128_255_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_256_511_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_512_1023_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_1024_1518_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_1519_2047_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_2048_4095_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_4096_8191_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_8192_9216_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_9217_12287_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_12288_16383_oct_pkt_num), SPNIC_PORT_STAT(mac_rx_1519_max_good_pkt_num), SPNIC_PORT_STAT(mac_rx_1519_max_bad_pkt_num), SPNIC_PORT_STAT(mac_rx_oversize_pkt_num), SPNIC_PORT_STAT(mac_rx_jabber_pkt_num), SPNIC_PORT_STAT(mac_rx_pause_num), SPNIC_PORT_STAT(mac_rx_pfc_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri0_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri1_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri2_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri3_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri4_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri5_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri6_pkt_num), SPNIC_PORT_STAT(mac_rx_pfc_pri7_pkt_num), SPNIC_PORT_STAT(mac_rx_control_pkt_num), SPNIC_PORT_STAT(mac_rx_sym_err_pkt_num), SPNIC_PORT_STAT(mac_rx_fcs_err_pkt_num), SPNIC_PORT_STAT(mac_rx_send_app_good_pkt_num), SPNIC_PORT_STAT(mac_rx_send_app_bad_pkt_num), SPNIC_PORT_STAT(mac_tx_fragment_pkt_num), SPNIC_PORT_STAT(mac_tx_undersize_pkt_num), SPNIC_PORT_STAT(mac_tx_undermin_pkt_num), SPNIC_PORT_STAT(mac_tx_64_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_65_127_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_128_255_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_256_511_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_512_1023_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_1024_1518_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_1519_2047_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_2048_4095_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_4096_8191_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_8192_9216_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_9217_12287_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_12288_16383_oct_pkt_num), SPNIC_PORT_STAT(mac_tx_1519_max_good_pkt_num), SPNIC_PORT_STAT(mac_tx_1519_max_bad_pkt_num), SPNIC_PORT_STAT(mac_tx_oversize_pkt_num), SPNIC_PORT_STAT(mac_tx_jabber_pkt_num), SPNIC_PORT_STAT(mac_tx_pause_num), SPNIC_PORT_STAT(mac_tx_pfc_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri0_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri1_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri2_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri3_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri4_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri5_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri6_pkt_num), SPNIC_PORT_STAT(mac_tx_pfc_pri7_pkt_num), SPNIC_PORT_STAT(mac_tx_control_pkt_num), SPNIC_PORT_STAT(mac_tx_err_all_pkt_num), SPNIC_PORT_STAT(mac_tx_from_app_good_pkt_num), SPNIC_PORT_STAT(mac_tx_from_app_bad_pkt_num), }; static char g_spnic_priv_flags_strings[][ETH_GSTRING_LEN] = { "Symmetric-RSS", }; u32 spnic_get_io_stats_size(struct spnic_nic_dev *nic_dev) { u32 count; count = ARRAY_LEN(spnic_nic_dev_stats) + ARRAY_LEN(spnic_nic_dev_stats_extern) + (ARRAY_LEN(spnic_tx_queue_stats) + ARRAY_LEN(spnic_tx_queue_stats_extern) + ARRAY_LEN(spnic_rx_queue_stats) + ARRAY_LEN(spnic_rx_queue_stats_extern)) * nic_dev->max_qps; return count; } #define GET_VALUE_OF_PTR(size, ptr) ( \ (size) == sizeof(u64) ? *(u64 *)(ptr) : \ (size) == sizeof(u32) ? *(u32 *)(ptr) : \ (size) == sizeof(u16) ? *(u16 *)(ptr) : *(u8 *)(ptr) \ ) #define DEV_STATS_PACK(items, item_idx, array, stats_ptr) do { \ int j; \ for (j = 0; j < ARRAY_LEN(array); j++) { \ memcpy((items)[item_idx].name, (array)[j].name, \ SPNIC_SHOW_ITEM_LEN); \ (items)[item_idx].hexadecimal = 0; \ (items)[item_idx].value = \ GET_VALUE_OF_PTR((array)[j].size, \ (char *)(stats_ptr) + (array)[j].offset); \ (item_idx)++; \ } \ } while (0) #define QUEUE_STATS_PACK(items, item_idx, array, stats_ptr, qid) do { \ int j; \ for (j = 0; j < ARRAY_LEN(array); j++) { \ memcpy((items)[item_idx].name, (array)[j].name, \ SPNIC_SHOW_ITEM_LEN); \ snprintf((items)[item_idx].name, SPNIC_SHOW_ITEM_LEN, \ (array)[j].name, (qid)); \ (items)[item_idx].hexadecimal = 0; \ (items)[item_idx].value = \ GET_VALUE_OF_PTR((array)[j].size, \ (char *)(stats_ptr) + (array)[j].offset); \ (item_idx)++; \ } \ } while (0) void spnic_get_io_stats(struct spnic_nic_dev *nic_dev, void *stats) { struct spnic_show_item *items = stats; int item_idx = 0; u16 qid; DEV_STATS_PACK(items, item_idx, spnic_nic_dev_stats, &nic_dev->stats); DEV_STATS_PACK(items, item_idx, spnic_nic_dev_stats_extern, &nic_dev->stats); for (qid = 0; qid < nic_dev->max_qps; qid++) { QUEUE_STATS_PACK(items, item_idx, spnic_tx_queue_stats, &nic_dev->txqs[qid].txq_stats, qid); QUEUE_STATS_PACK(items, item_idx, spnic_tx_queue_stats_extern, &nic_dev->txqs[qid].txq_stats, qid); } for (qid = 0; qid < nic_dev->max_qps; qid++) { QUEUE_STATS_PACK(items, item_idx, spnic_rx_queue_stats, &nic_dev->rxqs[qid].rxq_stats, qid); QUEUE_STATS_PACK(items, item_idx, spnic_rx_queue_stats_extern, &nic_dev->rxqs[qid].rxq_stats, qid); } } static char spnic_test_strings[][ETH_GSTRING_LEN] = { "Internal lb test (on/offline)", "External lb test (external_lb)", }; int spnic_get_sset_count(struct net_device *netdev, int sset) { int count = 0, q_num = 0; struct spnic_nic_dev *nic_dev = netdev_priv(netdev); switch (sset) { case ETH_SS_TEST: return ARRAY_LEN(spnic_test_strings); case ETH_SS_STATS: q_num = nic_dev->q_params.num_qps; count = ARRAY_LEN(spnic_netdev_stats) + ARRAY_LEN(spnic_nic_dev_stats) + ARRAY_LEN(spnic_function_stats) + (ARRAY_LEN(spnic_tx_queue_stats) + ARRAY_LEN(spnic_rx_queue_stats)) * q_num; if (!SPNIC_FUNC_IS_VF(nic_dev->hwdev)) count += ARRAY_LEN(spnic_port_stats); return count; case ETH_SS_PRIV_FLAGS: return ARRAY_LEN(g_spnic_priv_flags_strings); default: return -EOPNOTSUPP; } } static void get_drv_queue_stats(struct spnic_nic_dev *nic_dev, u64 *data) { struct spnic_txq_stats txq_stats; struct spnic_rxq_stats rxq_stats; u16 i = 0, j = 0, qid = 0; char *p = NULL; for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) { if (!nic_dev->txqs) break; spnic_txq_get_stats(&nic_dev->txqs[qid], &txq_stats); for (j = 0; j < ARRAY_LEN(spnic_tx_queue_stats); j++, i++) { p = (char *)(&txq_stats) + spnic_tx_queue_stats[j].offset; data[i] = (spnic_tx_queue_stats[j].size == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; } } for (qid = 0; qid < nic_dev->q_params.num_qps; qid++) { if (!nic_dev->rxqs) break; spnic_rxq_get_stats(&nic_dev->rxqs[qid], &rxq_stats); for (j = 0; j < ARRAY_LEN(spnic_rx_queue_stats); j++, i++) { p = (char *)(&rxq_stats) + spnic_rx_queue_stats[j].offset; data[i] = (spnic_rx_queue_stats[j].size == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; } } } static u16 get_ethtool_port_stats(struct spnic_nic_dev *nic_dev, u64 *data) { struct mag_cmd_port_stats *port_stats; char *p = NULL; u16 i = 0, j = 0; int err; port_stats = kzalloc(sizeof(*port_stats), GFP_KERNEL); if (!port_stats) { nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to malloc port stats\n"); memset(&data[i], 0, ARRAY_LEN(spnic_port_stats) * sizeof(*data)); i += ARRAY_LEN(spnic_port_stats); return i; } err = spnic_get_phy_port_stats(nic_dev->hwdev, port_stats); if (err) nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to get port stats from fw\n"); for (j = 0; j < ARRAY_LEN(spnic_port_stats); j++, i++) { p = (char *)(port_stats) + spnic_port_stats[j].offset; data[i] = (spnic_port_stats[j].size == sizeof(u64)) ? *(u64 *)p : *(u32 *)p; } kfree(port_stats); return i; } void spnic_get_ethtool_stats(struct net_device *netdev, struct ethtool_stats *stats, u64 *data) { struct spnic_nic_dev *nic_dev = netdev_priv(netdev); struct rtnl_link_stats64 temp; const struct rtnl_link_stats64 *net_stats = NULL; struct spnic_nic_stats *nic_stats = NULL; struct spnic_vport_stats vport_stats = {0}; u16 i = 0, j = 0; char *p = NULL; int err; net_stats = dev_get_stats(netdev, &temp); for (j = 0; j < ARRAY_LEN(spnic_netdev_stats); j++, i++) { p = (char *)(net_stats) + spnic_netdev_stats[j].offset; data[i] = GET_VALUE_OF_PTR(spnic_netdev_stats[j].size, p); } nic_stats = &nic_dev->stats; for (j = 0; j < ARRAY_LEN(spnic_nic_dev_stats); j++, i++) { p = (char *)(nic_stats) + spnic_nic_dev_stats[j].offset; data[i] = GET_VALUE_OF_PTR(spnic_nic_dev_stats[j].size, p); } err = spnic_get_vport_stats(nic_dev->hwdev, &vport_stats); if (err) nicif_err(nic_dev, drv, netdev, "Failed to get function stats from fw\n"); for (j = 0; j < ARRAY_LEN(spnic_function_stats); j++, i++) { p = (char *)(&vport_stats) + spnic_function_stats[j].offset; data[i] = GET_VALUE_OF_PTR(spnic_function_stats[j].size, p); } if (!SPNIC_FUNC_IS_VF(nic_dev->hwdev)) i += get_ethtool_port_stats(nic_dev, data + i); get_drv_queue_stats(nic_dev, data + i); } static u16 get_drv_dev_strings(struct spnic_nic_dev *nic_dev, char *p) { u16 i, cnt = 0; for (i = 0; i < ARRAY_LEN(spnic_netdev_stats); i++) { memcpy(p, spnic_netdev_stats[i].name, ETH_GSTRING_LEN); p += ETH_GSTRING_LEN; cnt++; } for (i = 0; i < ARRAY_LEN(spnic_nic_dev_stats); i++) { memcpy(p, spnic_nic_dev_stats[i].name, ETH_GSTRING_LEN); p += ETH_GSTRING_LEN; cnt++; } return cnt; } static u16 get_hw_stats_strings(struct spnic_nic_dev *nic_dev, char *p) { u16 i, cnt = 0; for (i = 0; i < ARRAY_LEN(spnic_function_stats); i++) { memcpy(p, spnic_function_stats[i].name, ETH_GSTRING_LEN); p += ETH_GSTRING_LEN; cnt++; } if (!SPNIC_FUNC_IS_VF(nic_dev->hwdev)) { for (i = 0; i < ARRAY_LEN(spnic_port_stats); i++) { memcpy(p, spnic_port_stats[i].name, ETH_GSTRING_LEN); p += ETH_GSTRING_LEN; cnt++; } } return cnt; } static u16 get_qp_stats_strings(struct spnic_nic_dev *nic_dev, char *p) { u16 i = 0, j = 0, cnt = 0; for (i = 0; i < nic_dev->q_params.num_qps; i++) { for (j = 0; j < ARRAY_LEN(spnic_tx_queue_stats); j++) { sprintf(p, spnic_tx_queue_stats[j].name, i); p += ETH_GSTRING_LEN; cnt++; } } for (i = 0; i < nic_dev->q_params.num_qps; i++) { for (j = 0; j < ARRAY_LEN(spnic_rx_queue_stats); j++) { sprintf(p, spnic_rx_queue_stats[j].name, i); p += ETH_GSTRING_LEN; cnt++; } } return cnt; } void spnic_get_strings(struct net_device *netdev, u32 stringset, u8 *data) { struct spnic_nic_dev *nic_dev = netdev_priv(netdev); char *p = (char *)data; u16 offset = 0; switch (stringset) { case ETH_SS_TEST: memcpy(data, *spnic_test_strings, sizeof(spnic_test_strings)); return; case ETH_SS_STATS: offset = get_drv_dev_strings(nic_dev, p); offset += get_hw_stats_strings(nic_dev, p + offset * ETH_GSTRING_LEN); get_qp_stats_strings(nic_dev, p + offset * ETH_GSTRING_LEN); return; case ETH_SS_PRIV_FLAGS: memcpy(data, g_spnic_priv_flags_strings, sizeof(g_spnic_priv_flags_strings)); return; default: nicif_err(nic_dev, drv, netdev, "Invalid string set %u.", stringset); return; } } static const u32 spnic_mag_link_mode_ge[] = { ETHTOOL_LINK_MODE_1000baseT_Full_BIT, ETHTOOL_LINK_MODE_1000baseKX_Full_BIT, ETHTOOL_LINK_MODE_1000baseX_Full_BIT, }; static const u32 spnic_mag_link_mode_10ge_base_r[] = { ETHTOOL_LINK_MODE_10000baseKR_Full_BIT, ETHTOOL_LINK_MODE_10000baseR_FEC_BIT, ETHTOOL_LINK_MODE_10000baseCR_Full_BIT, ETHTOOL_LINK_MODE_10000baseSR_Full_BIT, ETHTOOL_LINK_MODE_10000baseLR_Full_BIT, ETHTOOL_LINK_MODE_10000baseLRM_Full_BIT, }; static const u32 spnic_mag_link_mode_25ge_base_r[] = { ETHTOOL_LINK_MODE_25000baseCR_Full_BIT, ETHTOOL_LINK_MODE_25000baseKR_Full_BIT, ETHTOOL_LINK_MODE_25000baseSR_Full_BIT, }; static const u32 spnic_mag_link_mode_40ge_base_r4[] = { ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT, ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT, ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT, ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT, }; static const u32 spnic_mag_link_mode_50ge_base_r[] = { ETHTOOL_LINK_MODE_50000baseKR_Full_BIT, ETHTOOL_LINK_MODE_50000baseSR_Full_BIT, ETHTOOL_LINK_MODE_50000baseCR_Full_BIT, }; static const u32 spnic_mag_link_mode_50ge_base_r2[] = { ETHTOOL_LINK_MODE_50000baseCR2_Full_BIT, ETHTOOL_LINK_MODE_50000baseKR2_Full_BIT, ETHTOOL_LINK_MODE_50000baseSR2_Full_BIT, }; static const u32 spnic_mag_link_mode_100ge_base_r[] = { ETHTOOL_LINK_MODE_100000baseKR_Full_BIT, ETHTOOL_LINK_MODE_100000baseSR_Full_BIT, ETHTOOL_LINK_MODE_100000baseCR_Full_BIT, }; static const u32 spnic_mag_link_mode_100ge_base_r2[] = { ETHTOOL_LINK_MODE_100000baseKR2_Full_BIT, ETHTOOL_LINK_MODE_100000baseSR2_Full_BIT, ETHTOOL_LINK_MODE_100000baseCR2_Full_BIT, }; static const u32 spnic_mag_link_mode_100ge_base_r4[] = { ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT, ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT, ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT, }; static const u32 spnic_mag_link_mode_200ge_base_r2[] = { ETHTOOL_LINK_MODE_200000baseKR2_Full_BIT, ETHTOOL_LINK_MODE_200000baseSR2_Full_BIT, ETHTOOL_LINK_MODE_200000baseCR2_Full_BIT, }; static const u32 spnic_mag_link_mode_200ge_base_r4[] = { ETHTOOL_LINK_MODE_200000baseKR4_Full_BIT, ETHTOOL_LINK_MODE_200000baseSR4_Full_BIT, ETHTOOL_LINK_MODE_200000baseCR4_Full_BIT, }; struct hw2ethtool_link_mode { const u32 *link_mode_bit_arr; u32 arr_size; u32 speed; }; static const struct hw2ethtool_link_mode hw2ethtool_link_mode_table[LINK_MODE_MAX_NUMBERS] = { [LINK_MODE_GE] = { .link_mode_bit_arr = spnic_mag_link_mode_ge, .arr_size = ARRAY_LEN(spnic_mag_link_mode_ge), .speed = SPEED_1000, }, [LINK_MODE_10GE_BASE_R] = { .link_mode_bit_arr = spnic_mag_link_mode_10ge_base_r, .arr_size = ARRAY_LEN(spnic_mag_link_mode_10ge_base_r), .speed = SPEED_10000, }, [LINK_MODE_25GE_BASE_R] = { .link_mode_bit_arr = spnic_mag_link_mode_25ge_base_r, .arr_size = ARRAY_LEN(spnic_mag_link_mode_25ge_base_r), .speed = SPEED_25000, }, [LINK_MODE_40GE_BASE_R4] = { .link_mode_bit_arr = spnic_mag_link_mode_40ge_base_r4, .arr_size = ARRAY_LEN(spnic_mag_link_mode_40ge_base_r4), .speed = SPEED_40000, }, [LINK_MODE_50GE_BASE_R] = { .link_mode_bit_arr = spnic_mag_link_mode_50ge_base_r, .arr_size = ARRAY_LEN(spnic_mag_link_mode_50ge_base_r), .speed = SPEED_50000, }, [LINK_MODE_50GE_BASE_R2] = { .link_mode_bit_arr = spnic_mag_link_mode_50ge_base_r2, .arr_size = ARRAY_LEN(spnic_mag_link_mode_50ge_base_r2), .speed = SPEED_50000, }, [LINK_MODE_100GE_BASE_R] = { .link_mode_bit_arr = spnic_mag_link_mode_100ge_base_r, .arr_size = ARRAY_LEN(spnic_mag_link_mode_100ge_base_r), .speed = SPEED_100000, }, [LINK_MODE_100GE_BASE_R2] = { .link_mode_bit_arr = spnic_mag_link_mode_100ge_base_r2, .arr_size = ARRAY_LEN(spnic_mag_link_mode_100ge_base_r2), .speed = SPEED_100000, }, [LINK_MODE_100GE_BASE_R4] = { .link_mode_bit_arr = spnic_mag_link_mode_100ge_base_r4, .arr_size = ARRAY_LEN(spnic_mag_link_mode_100ge_base_r4), .speed = SPEED_100000, }, [LINK_MODE_200GE_BASE_R2] = { .link_mode_bit_arr = spnic_mag_link_mode_200ge_base_r2, .arr_size = ARRAY_LEN(spnic_mag_link_mode_200ge_base_r2), .speed = SPEED_200000, }, [LINK_MODE_200GE_BASE_R4] = { .link_mode_bit_arr = spnic_mag_link_mode_200ge_base_r4, .arr_size = ARRAY_LEN(spnic_mag_link_mode_200ge_base_r4), .speed = SPEED_200000, }, }; #define GET_SUPPORTED_MODE 0 #define GET_ADVERTISED_MODE 1 struct cmd_link_settings { __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); __ETHTOOL_DECLARE_LINK_MODE_MASK(advertising); u32 speed; u8 duplex; u8 port; u8 autoneg; }; #define ETHTOOL_ADD_SUPPORTED_LINK_MODE(ecmd, mode) \ set_bit(ETHTOOL_LINK_MODE_##mode##_BIT, (ecmd)->supported) #define ETHTOOL_ADD_ADVERTISED_LINK_MODE(ecmd, mode) \ set_bit(ETHTOOL_LINK_MODE_##mode##_BIT, (ecmd)->advertising) #define ETHTOOL_ADD_SUPPORTED_SPEED_LINK_MODE(ecmd, mode) \ do { \ u32 i; \ for (i = 0; i < hw2ethtool_link_mode_table[mode].arr_size; i++) \ set_bit(hw2ethtool_link_mode_table[mode].link_mode_bit_arr[i], \ (ecmd)->supported); \ } while (0) #define ETHTOOL_ADD_ADVERTISED_SPEED_LINK_MODE(ecmd, mode) \ do { \ u32 i; \ for (i = 0; i < hw2ethtool_link_mode_table[mode].arr_size; i++) \ set_bit(hw2ethtool_link_mode_table[mode].link_mode_bit_arr[i], \ (ecmd)->advertising); \ } while (0) /* Related to enum mag_cmd_port_speed */ static u32 hw_to_ethtool_speed[] = { (u32)SPEED_UNKNOWN, SPEED_10, SPEED_100, SPEED_1000, SPEED_10000, SPEED_25000, SPEED_40000, SPEED_50000, SPEED_100000, SPEED_200000 }; static int spnic_ethtool_to_hw_speed_level(u32 speed) { int i; for (i = 0; i < ARRAY_LEN(hw_to_ethtool_speed); i++) { if (hw_to_ethtool_speed[i] == speed) break; } return i; } static void spnic_add_ethtool_link_mode(struct cmd_link_settings *link_settings, u32 hw_link_mode, u32 name) { u32 link_mode; for (link_mode = 0; link_mode < LINK_MODE_MAX_NUMBERS; link_mode++) { if (hw_link_mode & BIT(link_mode)) { if (name == GET_SUPPORTED_MODE) ETHTOOL_ADD_SUPPORTED_SPEED_LINK_MODE(link_settings, link_mode); else ETHTOOL_ADD_ADVERTISED_SPEED_LINK_MODE(link_settings, link_mode); } } } static int spnic_link_speed_set(struct spnic_nic_dev *nic_dev, struct cmd_link_settings *link_settings, struct nic_port_info *port_info) { u8 link_state = 0; int err; if (port_info->supported_mode != LINK_MODE_UNKNOWN) spnic_add_ethtool_link_mode(link_settings, port_info->supported_mode, GET_SUPPORTED_MODE); if (port_info->advertised_mode != LINK_MODE_UNKNOWN) spnic_add_ethtool_link_mode(link_settings, port_info->advertised_mode, GET_ADVERTISED_MODE); err = spnic_get_link_state(nic_dev->hwdev, &link_state); if (!err && link_state) { link_settings->speed = port_info->speed < ARRAY_LEN(hw_to_ethtool_speed) ? hw_to_ethtool_speed[port_info->speed] : (u32)SPEED_UNKNOWN; link_settings->duplex = port_info->duplex; } else { link_settings->speed = (u32)SPEED_UNKNOWN; link_settings->duplex = DUPLEX_UNKNOWN; } return 0; } static void spnic_link_port_type(struct cmd_link_settings *link_settings, u8 port_type) { switch (port_type) { case MAG_CMD_WIRE_TYPE_ELECTRIC: ETHTOOL_ADD_SUPPORTED_LINK_MODE(link_settings, TP); ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, TP); link_settings->port = PORT_TP; break; case MAG_CMD_WIRE_TYPE_AOC: case MAG_CMD_WIRE_TYPE_MM: case MAG_CMD_WIRE_TYPE_SM: ETHTOOL_ADD_SUPPORTED_LINK_MODE(link_settings, FIBRE); ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, FIBRE); link_settings->port = PORT_FIBRE; break; case MAG_CMD_WIRE_TYPE_COPPER: ETHTOOL_ADD_SUPPORTED_LINK_MODE(link_settings, FIBRE); ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, FIBRE); link_settings->port = PORT_DA; break; case MAG_CMD_WIRE_TYPE_BACKPLANE: ETHTOOL_ADD_SUPPORTED_LINK_MODE(link_settings, Backplane); ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, Backplane); link_settings->port = PORT_NONE; break; default: link_settings->port = PORT_OTHER; break; } } static int get_link_pause_settings(struct spnic_nic_dev *nic_dev, struct cmd_link_settings *link_settings) { struct nic_pause_config nic_pause = {0}; int err; err = spnic_get_pause_info(nic_dev->hwdev, &nic_pause); if (err) { nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to get pauseparam from hw\n"); return err; } ETHTOOL_ADD_SUPPORTED_LINK_MODE(link_settings, Pause); if (nic_pause.rx_pause && nic_pause.tx_pause) { ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, Pause); } else if (nic_pause.tx_pause) { ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, Asym_Pause); } else if (nic_pause.rx_pause) { ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, Pause); ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, Asym_Pause); } return 0; } int get_link_settings(struct net_device *netdev, struct cmd_link_settings *link_settings) { struct spnic_nic_dev *nic_dev = netdev_priv(netdev); struct nic_port_info port_info = {0}; int err; err = spnic_get_port_info(nic_dev->hwdev, &port_info, SPHW_CHANNEL_NIC); if (err) { nicif_err(nic_dev, drv, netdev, "Failed to get port info\n"); return err; } err = spnic_link_speed_set(nic_dev, link_settings, &port_info); if (err) return err; spnic_link_port_type(link_settings, port_info.port_type); link_settings->autoneg = port_info.autoneg_state == PORT_CFG_AN_ON ? AUTONEG_ENABLE : AUTONEG_DISABLE; if (port_info.autoneg_cap) ETHTOOL_ADD_SUPPORTED_LINK_MODE(link_settings, Autoneg); if (port_info.autoneg_state == PORT_CFG_AN_ON) ETHTOOL_ADD_ADVERTISED_LINK_MODE(link_settings, Autoneg); if (!SPNIC_FUNC_IS_VF(nic_dev->hwdev)) err = get_link_pause_settings(nic_dev, link_settings); return err; } int spnic_get_link_ksettings(struct net_device *netdev, struct ethtool_link_ksettings *link_settings) { struct cmd_link_settings settings = { { 0 } }; struct ethtool_link_settings *base = &link_settings->base; int err; ethtool_link_ksettings_zero_link_mode(link_settings, supported); ethtool_link_ksettings_zero_link_mode(link_settings, advertising); err = get_link_settings(netdev, &settings); if (err) return err; bitmap_copy(link_settings->link_modes.supported, settings.supported, __ETHTOOL_LINK_MODE_MASK_NBITS); bitmap_copy(link_settings->link_modes.advertising, settings.advertising, __ETHTOOL_LINK_MODE_MASK_NBITS); base->autoneg = settings.autoneg; base->speed = settings.speed; base->duplex = settings.duplex; base->port = settings.port; return 0; } static bool spnic_is_support_speed(u32 supported_link, u32 speed) { u32 link_mode; for (link_mode = 0; link_mode < LINK_MODE_MAX_NUMBERS; link_mode++) { if (!(supported_link & BIT(link_mode))) continue; if (hw2ethtool_link_mode_table[link_mode].speed == speed) return true; } return false; } static int spnic_is_speed_legal(struct spnic_nic_dev *nic_dev, struct nic_port_info *port_info, u32 speed) { struct net_device *netdev = nic_dev->netdev; int speed_level = 0; if (port_info->supported_mode == LINK_MODE_UNKNOWN || port_info->advertised_mode == LINK_MODE_UNKNOWN) { nicif_err(nic_dev, drv, netdev, "Unknown supported link modes\n"); return -EAGAIN; } speed_level = spnic_ethtool_to_hw_speed_level(speed); if (speed_level >= PORT_SPEED_UNKNOWN || !spnic_is_support_speed(port_info->supported_mode, speed)) { nicif_err(nic_dev, drv, netdev, "Not supported speed: %u\n", speed); return -EINVAL; } return 0; } static int get_link_settings_type(struct spnic_nic_dev *nic_dev, u8 autoneg, u32 speed, u32 *set_settings) { struct nic_port_info port_info = {0}; int err; err = spnic_get_port_info(nic_dev->hwdev, &port_info, SPHW_CHANNEL_NIC); if (err) { nicif_err(nic_dev, drv, nic_dev->netdev, "Failed to get current settings\n"); return -EAGAIN; } /* Alwayse set autonegation */ if (port_info.autoneg_cap) *set_settings |= HILINK_LINK_SET_AUTONEG; if (autoneg == AUTONEG_ENABLE) { if (!port_info.autoneg_cap) { nicif_err(nic_dev, drv, nic_dev->netdev, "Not support autoneg\n"); return -EOPNOTSUPP; } } else if (speed != (u32)SPEED_UNKNOWN) { /* Set speed only when autoneg is disable */ err = spnic_is_speed_legal(nic_dev, &port_info, speed); if (err) return err; *set_settings |= HILINK_LINK_SET_SPEED; } else { nicif_err(nic_dev, drv, nic_dev->netdev, "Need to set speed when autoneg is off\n"); return -EOPNOTSUPP; } return 0; } static int spnic_set_settings_to_hw(struct spnic_nic_dev *nic_dev, u32 set_settings, u8 autoneg, u32 speed) { struct net_device *netdev = nic_dev->netdev; struct spnic_link_ksettings settings = {0}; int speed_level = 0; char set_link_str[128] = {0}; int err = 0; snprintf(set_link_str, sizeof(set_link_str), "%s", (set_settings & HILINK_LINK_SET_AUTONEG) ? (autoneg ? "autong enable " : "autong disable ") : ""); if (set_settings & HILINK_LINK_SET_SPEED) { speed_level = spnic_ethtool_to_hw_speed_level(speed); snprintf(set_link_str, sizeof(set_link_str), "%sspeed %u ", set_link_str, speed); } settings.valid_bitmap = set_settings; settings.autoneg = autoneg ? PORT_CFG_AN_ON : PORT_CFG_AN_OFF; settings.speed = (u8)speed_level; err = spnic_set_link_settings(nic_dev->hwdev, &settings); if (err) nicif_err(nic_dev, drv, netdev, "Set %sfailed\n", set_link_str); else nicif_info(nic_dev, drv, netdev, "Set %ssuccess\n", set_link_str); return err; } int set_link_settings(struct net_device *netdev, u8 autoneg, u32 speed) { struct spnic_nic_dev *nic_dev = netdev_priv(netdev); u32 set_settings = 0; int err = 0; err = get_link_settings_type(nic_dev, autoneg, speed, &set_settings); if (err) return err; if (set_settings) err = spnic_set_settings_to_hw(nic_dev, set_settings, autoneg, speed); else nicif_info(nic_dev, drv, netdev, "Nothing changed, exiting without setting anything\n"); return err; } int spnic_set_link_ksettings(struct net_device *netdev, const struct ethtool_link_ksettings *link_settings) { /* Only support to set autoneg and speed */ return set_link_settings(netdev, link_settings->base.autoneg, link_settings->base.speed); }