| /* |
| * Copyright (c) 2015 - 2017, The Linux Foundation. All rights reserved. |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all copies. |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT |
| * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| #include <linux/ethtool.h> |
| #include <linux/netdevice.h> |
| #include <linux/string.h> |
| #include "edma.h" |
| |
| struct edma_ethtool_stats { |
| uint8_t stat_string[ETH_GSTRING_LEN]; |
| uint32_t stat_offset; |
| }; |
| |
| extern struct edma_video_delay_stats edma_iad_stats_tx[EDMA_MAX_IAD_FLOW_STATS_SUPPORTED]; |
| extern struct edma_video_delay_stats edma_iad_stats_rx[EDMA_MAX_IAD_FLOW_STATS_SUPPORTED]; |
| |
| #define EDMA_STAT(m) offsetof(struct edma_ethtool_statistics, m) |
| #define DRVINFO_LEN 32 |
| |
| /* Array of strings describing statistics */ |
| static const struct edma_ethtool_stats edma_gstrings_stats[] = { |
| {"tx_q0_pkt", EDMA_STAT(tx_q0_pkt)}, |
| {"tx_q1_pkt", EDMA_STAT(tx_q1_pkt)}, |
| {"tx_q2_pkt", EDMA_STAT(tx_q2_pkt)}, |
| {"tx_q3_pkt", EDMA_STAT(tx_q3_pkt)}, |
| {"tx_q4_pkt", EDMA_STAT(tx_q4_pkt)}, |
| {"tx_q5_pkt", EDMA_STAT(tx_q5_pkt)}, |
| {"tx_q6_pkt", EDMA_STAT(tx_q6_pkt)}, |
| {"tx_q7_pkt", EDMA_STAT(tx_q7_pkt)}, |
| {"tx_q8_pkt", EDMA_STAT(tx_q8_pkt)}, |
| {"tx_q9_pkt", EDMA_STAT(tx_q9_pkt)}, |
| {"tx_q10_pkt", EDMA_STAT(tx_q10_pkt)}, |
| {"tx_q11_pkt", EDMA_STAT(tx_q11_pkt)}, |
| {"tx_q12_pkt", EDMA_STAT(tx_q12_pkt)}, |
| {"tx_q13_pkt", EDMA_STAT(tx_q13_pkt)}, |
| {"tx_q14_pkt", EDMA_STAT(tx_q14_pkt)}, |
| {"tx_q15_pkt", EDMA_STAT(tx_q15_pkt)}, |
| {"tx_q0_byte", EDMA_STAT(tx_q0_byte)}, |
| {"tx_q1_byte", EDMA_STAT(tx_q1_byte)}, |
| {"tx_q2_byte", EDMA_STAT(tx_q2_byte)}, |
| {"tx_q3_byte", EDMA_STAT(tx_q3_byte)}, |
| {"tx_q4_byte", EDMA_STAT(tx_q4_byte)}, |
| {"tx_q5_byte", EDMA_STAT(tx_q5_byte)}, |
| {"tx_q6_byte", EDMA_STAT(tx_q6_byte)}, |
| {"tx_q7_byte", EDMA_STAT(tx_q7_byte)}, |
| {"tx_q8_byte", EDMA_STAT(tx_q8_byte)}, |
| {"tx_q9_byte", EDMA_STAT(tx_q9_byte)}, |
| {"tx_q10_byte", EDMA_STAT(tx_q10_byte)}, |
| {"tx_q11_byte", EDMA_STAT(tx_q11_byte)}, |
| {"tx_q12_byte", EDMA_STAT(tx_q12_byte)}, |
| {"tx_q13_byte", EDMA_STAT(tx_q13_byte)}, |
| {"tx_q14_byte", EDMA_STAT(tx_q14_byte)}, |
| {"tx_q15_byte", EDMA_STAT(tx_q15_byte)}, |
| {"rx_q0_pkt", EDMA_STAT(rx_q0_pkt)}, |
| {"rx_q1_pkt", EDMA_STAT(rx_q1_pkt)}, |
| {"rx_q2_pkt", EDMA_STAT(rx_q2_pkt)}, |
| {"rx_q3_pkt", EDMA_STAT(rx_q3_pkt)}, |
| {"rx_q4_pkt", EDMA_STAT(rx_q4_pkt)}, |
| {"rx_q5_pkt", EDMA_STAT(rx_q5_pkt)}, |
| {"rx_q6_pkt", EDMA_STAT(rx_q6_pkt)}, |
| {"rx_q7_pkt", EDMA_STAT(rx_q7_pkt)}, |
| {"rx_q0_byte", EDMA_STAT(rx_q0_byte)}, |
| {"rx_q1_byte", EDMA_STAT(rx_q1_byte)}, |
| {"rx_q2_byte", EDMA_STAT(rx_q2_byte)}, |
| {"rx_q3_byte", EDMA_STAT(rx_q3_byte)}, |
| {"rx_q4_byte", EDMA_STAT(rx_q4_byte)}, |
| {"rx_q5_byte", EDMA_STAT(rx_q5_byte)}, |
| {"rx_q6_byte", EDMA_STAT(rx_q6_byte)}, |
| {"rx_q7_byte", EDMA_STAT(rx_q7_byte)}, |
| {"tx_desc_error", EDMA_STAT(tx_desc_error)}, |
| {"rx_alloc_fail_ctr", EDMA_STAT(rx_alloc_fail_ctr)}, |
| {"rx_prec_0", EDMA_STAT(rx_prec[0])}, |
| {"rx_prec_1", EDMA_STAT(rx_prec[1])}, |
| {"rx_prec_2", EDMA_STAT(rx_prec[2])}, |
| {"rx_prec_3", EDMA_STAT(rx_prec[3])}, |
| {"rx_prec_4", EDMA_STAT(rx_prec[4])}, |
| {"rx_prec_5", EDMA_STAT(rx_prec[5])}, |
| {"rx_prec_6", EDMA_STAT(rx_prec[6])}, |
| {"rx_prec_7", EDMA_STAT(rx_prec[7])}, |
| {"rx_ac_bk", EDMA_STAT(rx_ac[EDMA_AC_BK])}, |
| {"rx_ac_be", EDMA_STAT(rx_ac[EDMA_AC_BE])}, |
| {"rx_ac_vi", EDMA_STAT(rx_ac[EDMA_AC_VI])}, |
| {"rx_ac_vo", EDMA_STAT(rx_ac[EDMA_AC_VO])}, |
| {"rx_flow1_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[0])}, |
| {"rx_flow1_max_ifd_usec", EDMA_STAT(rx_flow_iad[0])}, |
| {"rx_flow2_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[1])}, |
| {"rx_flow2_max_ifd_usec", EDMA_STAT(rx_flow_iad[1])}, |
| {"rx_flow3_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[2])}, |
| {"rx_flow3_max_ifd_usec", EDMA_STAT(rx_flow_iad[2])}, |
| {"rx_flow4_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[3])}, |
| {"rx_flow4_max_ifd_usec", EDMA_STAT(rx_flow_iad[3])}, |
| {"rx_flow5_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[4])}, |
| {"rx_flow5_max_ifd_usec", EDMA_STAT(rx_flow_iad[4])}, |
| {"rx_flow6_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[5])}, |
| {"rx_flow6_max_ifd_usec", EDMA_STAT(rx_flow_iad[5])}, |
| {"rx_flow7_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[6])}, |
| {"rx_flow7_max_ifd_usec", EDMA_STAT(rx_flow_iad[6])}, |
| {"rx_flow8_delta_start_ts", EDMA_STAT(rx_flow_delta_start_ts[7])}, |
| {"rx_flow8_max_ifd_usec", EDMA_STAT(rx_flow_iad[7])}, |
| {"tx_prec_0", EDMA_STAT(tx_prec[0])}, |
| {"tx_prec_1", EDMA_STAT(tx_prec[1])}, |
| {"tx_prec_2", EDMA_STAT(tx_prec[2])}, |
| {"tx_prec_3", EDMA_STAT(tx_prec[3])}, |
| {"tx_prec_4", EDMA_STAT(tx_prec[4])}, |
| {"tx_prec_5", EDMA_STAT(tx_prec[5])}, |
| {"tx_prec_6", EDMA_STAT(tx_prec[6])}, |
| {"tx_prec_7", EDMA_STAT(tx_prec[7])}, |
| {"tx_ac_bk", EDMA_STAT(tx_ac[EDMA_AC_BK])}, |
| {"tx_ac_be", EDMA_STAT(tx_ac[EDMA_AC_BE])}, |
| {"tx_ac_vi", EDMA_STAT(tx_ac[EDMA_AC_VI])}, |
| {"tx_ac_vo", EDMA_STAT(tx_ac[EDMA_AC_VO])}, |
| {"tx_flow1_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[0])}, |
| {"tx_flow1_max_ifd_usec", EDMA_STAT(tx_flow_iad[0])}, |
| {"tx_flow2_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[1])}, |
| {"tx_flow2_max_ifd_usec", EDMA_STAT(tx_flow_iad[1])}, |
| {"tx_flow3_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[2])}, |
| {"tx_flow3_max_ifd_usec", EDMA_STAT(tx_flow_iad[2])}, |
| {"tx_flow4_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[3])}, |
| {"tx_flow4_max_ifd_usec", EDMA_STAT(tx_flow_iad[3])}, |
| {"tx_flow5_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[4])}, |
| {"tx_flow5_max_ifd_usec", EDMA_STAT(tx_flow_iad[4])}, |
| {"tx_flow6_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[5])}, |
| {"tx_flow6_max_ifd_usec", EDMA_STAT(tx_flow_iad[5])}, |
| {"tx_flow7_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[6])}, |
| {"tx_flow7_max_ifd_usec", EDMA_STAT(tx_flow_iad[6])}, |
| {"tx_flow8_delta_start_ts", EDMA_STAT(tx_flow_delta_start_ts[7])}, |
| {"tx_flow8_max_ifd_usec", EDMA_STAT(tx_flow_iad[7])}, |
| }; |
| |
| #define EDMA_STATS_LEN ARRAY_SIZE(edma_gstrings_stats) |
| |
| /* edma_get_strset_count() |
| * Get strset count |
| */ |
| static int edma_get_strset_count(struct net_device *netdev, |
| int sset) |
| { |
| switch (sset) { |
| case ETH_SS_STATS: |
| return EDMA_STATS_LEN; |
| default: |
| netdev_dbg(netdev, "%s: Invalid string set", __func__); |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| |
| /* edma_get_strings() |
| * get stats string |
| */ |
| static void edma_get_strings(struct net_device *netdev, uint32_t stringset, |
| uint8_t *data) |
| { |
| uint8_t *p = data; |
| uint32_t i; |
| |
| switch (stringset) { |
| case ETH_SS_STATS: |
| for (i = 0; i < EDMA_STATS_LEN; i++) { |
| memcpy(p, edma_gstrings_stats[i].stat_string, |
| min((size_t)ETH_GSTRING_LEN, |
| strlen(edma_gstrings_stats[i].stat_string) |
| + 1)); |
| p += ETH_GSTRING_LEN; |
| } |
| break; |
| } |
| } |
| |
| /* edma_get_ethtool_stats() |
| * Get ethtool statistics |
| */ |
| static void edma_get_ethtool_stats(struct net_device *netdev, |
| struct ethtool_stats *stats, uint64_t *data) |
| { |
| struct edma_adapter *adapter = netdev_priv(netdev); |
| struct edma_common_info *edma_cinfo = adapter->edma_cinfo; |
| int i; |
| uint8_t *p = NULL; |
| |
| edma_read_append_stats(edma_cinfo); |
| |
| for (i = 0; i < EDMA_STATS_LEN; i++) { |
| p = (uint8_t *)&(edma_cinfo->edma_ethstats) + |
| edma_gstrings_stats[i].stat_offset; |
| data[i] = *(uint64_t *)p; |
| } |
| } |
| |
| /* edma_get_drvinfo() |
| * get edma driver info |
| */ |
| static void edma_get_drvinfo(struct net_device *dev, |
| struct ethtool_drvinfo *info) |
| { |
| strlcpy(info->driver, "ess_edma", DRVINFO_LEN); |
| strlcpy(info->bus_info, "axi", ETHTOOL_BUSINFO_LEN); |
| } |
| |
| /* edma_nway_reset() |
| * Reset the phy, if available. |
| */ |
| static int edma_nway_reset(struct net_device *netdev) |
| { |
| return -EINVAL; |
| } |
| |
| /* edma_get_wol() |
| * get wake on lan info |
| */ |
| static void edma_get_wol(struct net_device *netdev, |
| struct ethtool_wolinfo *wol) |
| { |
| wol->supported = 0; |
| wol->wolopts = 0; |
| } |
| |
| /* edma_get_msglevel() |
| * get message level. |
| */ |
| static uint32_t edma_get_msglevel(struct net_device *netdev) |
| { |
| return 0; |
| } |
| |
| /* edma_get_settings() |
| * Get edma settings |
| */ |
| static int edma_get_settings(struct net_device *netdev, |
| struct ethtool_cmd *ecmd) |
| { |
| struct edma_adapter *adapter = netdev_priv(netdev); |
| |
| mutex_lock(&adapter->poll_mutex); |
| if (!(adapter->poll_required)) { |
| /* If the speed/duplex for this GMAC is forced and we |
| * are not polling for link state changes, return the |
| * values as specified by platform. This will be true |
| * for GMACs connected to switch, and interfaces that |
| * do not use a PHY. |
| */ |
| if (adapter->forced_speed != SPEED_UNKNOWN) { |
| /* set speed and duplex */ |
| ethtool_cmd_speed_set(ecmd, SPEED_1000); |
| ecmd->duplex = DUPLEX_FULL; |
| |
| /* Populate capabilities advertised by self */ |
| ecmd->advertising = 0; |
| ecmd->autoneg = 0; |
| ecmd->port = PORT_TP; |
| ecmd->transceiver = XCVR_EXTERNAL; |
| } else { |
| /* non link polled and non |
| * forced speed/duplex interface |
| */ |
| mutex_unlock(&adapter->poll_mutex); |
| return -EIO; |
| } |
| } else { |
| struct phy_device *phydev = NULL; |
| uint16_t phyreg; |
| |
| if ((adapter->forced_speed != SPEED_UNKNOWN) |
| && !(adapter->poll_required)) { |
| mutex_unlock(&adapter->poll_mutex); |
| return -EPERM; |
| } |
| |
| phydev = adapter->phydev; |
| |
| ecmd->advertising = phydev->advertising; |
| ecmd->autoneg = phydev->autoneg; |
| |
| if (adapter->link_state == __EDMA_LINKDOWN) { |
| ecmd->speed = SPEED_UNKNOWN; |
| ecmd->duplex = DUPLEX_UNKNOWN; |
| } else { |
| ecmd->speed = phydev->speed; |
| ecmd->duplex = phydev->duplex; |
| } |
| |
| ecmd->phy_address = adapter->phy_mdio_addr; |
| |
| phyreg = (uint16_t)phy_read(adapter->phydev, MII_LPA); |
| if (phyreg & LPA_10HALF) |
| ecmd->lp_advertising |= ADVERTISED_10baseT_Half; |
| |
| if (phyreg & LPA_10FULL) |
| ecmd->lp_advertising |= ADVERTISED_10baseT_Full; |
| |
| if (phyreg & LPA_100HALF) |
| ecmd->lp_advertising |= ADVERTISED_100baseT_Half; |
| |
| if (phyreg & LPA_100FULL) |
| ecmd->lp_advertising |= ADVERTISED_100baseT_Full; |
| |
| phyreg = (uint16_t)phy_read(adapter->phydev, MII_STAT1000); |
| if (phyreg & LPA_1000HALF) |
| ecmd->lp_advertising |= ADVERTISED_1000baseT_Half; |
| |
| if (phyreg & LPA_1000FULL) |
| ecmd->lp_advertising |= ADVERTISED_1000baseT_Full; |
| } |
| mutex_unlock(&adapter->poll_mutex); |
| |
| return 0; |
| } |
| |
| /* edma_set_settings() |
| * Set EDMA settings |
| */ |
| static int edma_set_settings(struct net_device *netdev, |
| struct ethtool_cmd *ecmd) |
| { |
| struct edma_adapter *adapter = netdev_priv(netdev); |
| struct phy_device *phydev = NULL; |
| |
| mutex_lock(&adapter->poll_mutex); |
| if ((adapter->forced_speed != SPEED_UNKNOWN) && |
| !adapter->poll_required) { |
| mutex_unlock(&adapter->poll_mutex); |
| return -EPERM; |
| } |
| |
| phydev = adapter->phydev; |
| phydev->advertising = ecmd->advertising; |
| phydev->autoneg = ecmd->autoneg; |
| phydev->speed = ethtool_cmd_speed(ecmd); |
| phydev->duplex = ecmd->duplex; |
| |
| genphy_config_aneg(phydev); |
| mutex_unlock(&adapter->poll_mutex); |
| |
| return 0; |
| } |
| |
| /* edma_get_coalesce |
| * get interrupt mitigation |
| */ |
| static int edma_get_coalesce(struct net_device *netdev, |
| struct ethtool_coalesce *ec) |
| { |
| u32 reg_val; |
| |
| edma_get_tx_rx_coalesce(®_val); |
| |
| /* We read the Interrupt Moderation Timer(IMT) register value, |
| * use lower 16 bit for rx and higher 16 bit for Tx. We do a |
| * left shift by 1, because IMT resolution timer is 2usecs. |
| * Hence the value given by the register is multiplied by 2 to |
| * get the actual time in usecs. |
| */ |
| ec->tx_coalesce_usecs = (((reg_val >> 16) & 0xffff) << 1); |
| ec->rx_coalesce_usecs = ((reg_val & 0xffff) << 1); |
| |
| return 0; |
| } |
| |
| /* edma_set_coalesce |
| * set interrupt mitigation |
| */ |
| static int edma_set_coalesce(struct net_device *netdev, |
| struct ethtool_coalesce *ec) |
| { |
| if (ec->tx_coalesce_usecs) |
| edma_change_tx_coalesce(ec->tx_coalesce_usecs); |
| if (ec->rx_coalesce_usecs) |
| edma_change_rx_coalesce(ec->rx_coalesce_usecs); |
| |
| return 0; |
| } |
| |
| /* edma_set_priv_flags() |
| * Set EDMA private flags |
| */ |
| static int edma_set_priv_flags(struct net_device *netdev, u32 flags) |
| { |
| return 0; |
| } |
| |
| /* edma_get_priv_flags() |
| * get edma driver flags |
| */ |
| static u32 edma_get_priv_flags(struct net_device *netdev) |
| { |
| return 0; |
| } |
| |
| /* edma_get_ringparam() |
| * get ring size |
| */ |
| static void edma_get_ringparam(struct net_device *netdev, |
| struct ethtool_ringparam *ring) |
| { |
| struct edma_adapter *adapter = netdev_priv(netdev); |
| struct edma_common_info *edma_cinfo = adapter->edma_cinfo; |
| |
| ring->tx_max_pending = edma_cinfo->tx_ring_count; |
| ring->rx_max_pending = edma_cinfo->rx_ring_count; |
| } |
| |
| /* Ethtool operations |
| */ |
| static const struct ethtool_ops edma_ethtool_ops = { |
| .get_drvinfo = &edma_get_drvinfo, |
| .get_link = ðtool_op_get_link, |
| .get_msglevel = &edma_get_msglevel, |
| .nway_reset = &edma_nway_reset, |
| .get_wol = &edma_get_wol, |
| .get_settings = &edma_get_settings, |
| .set_settings = &edma_set_settings, |
| .get_strings = &edma_get_strings, |
| .get_sset_count = &edma_get_strset_count, |
| .get_ethtool_stats = &edma_get_ethtool_stats, |
| .get_coalesce = &edma_get_coalesce, |
| .set_coalesce = &edma_set_coalesce, |
| .get_priv_flags = edma_get_priv_flags, |
| .set_priv_flags = edma_set_priv_flags, |
| .get_ringparam = edma_get_ringparam, |
| }; |
| |
| /* edma_set_ethtool_ops |
| * Set ethtool operations |
| */ |
| void edma_set_ethtool_ops(struct net_device *netdev) |
| { |
| netdev->ethtool_ops = &edma_ethtool_ops; |
| } |