From: Omer Shpigelman <oshpigel...@habana.ai>

The driver supports ethtool callbacks and provides statistics using the
device's profiling infrastructure (coresight).

We support the basic ethtool functionality and counters, as far as our H/W
provides support.

A summary of the supported callbacks:

- get_drvinfo: fill some basic information regarding the driver
- get_link_ksettings: get basic settings like speed, duplex,
                      Auto-negotiation and link modes.
- set_link_ksettings: only speed and Auto-negotiation setting is supported.
- get_link: returns link indication.
- get_strings: get counters strings.
- get_sset_count: get counters number.
- get_ethtool_stats: get counters values.
- get_module_info: get EEPROM type and length.
- get_module_eeprom: get EEPROM (supported in raw mode only).

Signed-off-by: Omer Shpigelman <oshpigel...@habana.ai>
Reviewed-by: Oded Gabbay <oded.gab...@gmail.com>
Signed-off-by: Oded Gabbay <oded.gab...@gmail.com>
---
 drivers/misc/habanalabs/gaudi/Makefile        |   1 +
 drivers/misc/habanalabs/gaudi/gaudi.c         |   1 +
 drivers/misc/habanalabs/gaudi/gaudiP.h        |   7 +
 .../misc/habanalabs/gaudi/gaudi_coresight.c   | 144 +++++
 drivers/misc/habanalabs/gaudi/gaudi_nic.c     |   5 +
 .../misc/habanalabs/gaudi/gaudi_nic_ethtool.c | 582 ++++++++++++++++++
 6 files changed, 740 insertions(+)
 create mode 100644 drivers/misc/habanalabs/gaudi/gaudi_nic_ethtool.c

diff --git a/drivers/misc/habanalabs/gaudi/Makefile 
b/drivers/misc/habanalabs/gaudi/Makefile
index 437b21e54c95..e3002dc34a74 100644
--- a/drivers/misc/habanalabs/gaudi/Makefile
+++ b/drivers/misc/habanalabs/gaudi/Makefile
@@ -3,4 +3,5 @@ HL_GAUDI_FILES := gaudi/gaudi.o gaudi/gaudi_hwmgr.o 
gaudi/gaudi_security.o \
        gaudi/gaudi_coresight.o
 
 HL_GAUDI_FILES += gaudi/gaudi_nic.o gaudi/gaudi_phy.o \
+       gaudi/gaudi_nic_ethtool.o \
        gaudi/gaudi_nic_debugfs.o
diff --git a/drivers/misc/habanalabs/gaudi/gaudi.c 
b/drivers/misc/habanalabs/gaudi/gaudi.c
index 8fc2288fb424..eb733a48eb72 100644
--- a/drivers/misc/habanalabs/gaudi/gaudi.c
+++ b/drivers/misc/habanalabs/gaudi/gaudi.c
@@ -1043,6 +1043,7 @@ static int gaudi_sw_init(struct hl_device *hdev)
        gaudi->cpucp_info_get = gaudi_cpucp_info_get;
        gaudi->nic_handle_rx = gaudi_nic_handle_rx;
        gaudi->nic_handle_tx = gaudi_nic_handle_tx;
+       gaudi->nic_spmu_init = gaudi_nic_spmu_init;
 
        gaudi->max_freq_value = GAUDI_MAX_CLK_FREQ;
 
diff --git a/drivers/misc/habanalabs/gaudi/gaudiP.h 
b/drivers/misc/habanalabs/gaudi/gaudiP.h
index dc1dcff43cd6..c7dbca13f197 100644
--- a/drivers/misc/habanalabs/gaudi/gaudiP.h
+++ b/drivers/misc/habanalabs/gaudi/gaudiP.h
@@ -438,6 +438,7 @@ struct gaudi_internal_qman_info {
  * @cpucp_info_get: get information on device from CPU-CP
  * @nic_handle_rx: NIC handler for incoming packet.
  * @nic_handle_tx: NIC handler for outgoing packet.
+ * @nic_spmu_init: initialize NIC CoreSight spmu counters.
  * @nic_devices: array that holds all NIC ports manage structures.
  * @nic_macros: array that holds all NIC macros manage structures.
  * @nic_pam4_tx_taps: array that holds all PAM4 Tx taps of all NIC lanes.
@@ -502,6 +503,7 @@ struct gaudi_device {
        int (*cpucp_info_get)(struct hl_device *hdev);
        void (*nic_handle_rx)(struct gaudi_nic_device *gaudi_nic);
        int (*nic_handle_tx)(struct gaudi_nic_device *gaudi_nic, void *data);
+       void (*nic_spmu_init)(struct hl_device *hdev, int port);
        struct gaudi_nic_device         nic_devices[NIC_NUMBER_OF_PORTS];
        struct gaudi_nic_macro          nic_macros[NIC_NUMBER_OF_MACROS];
        struct gaudi_nic_tx_taps        nic_pam4_tx_taps[NIC_MAX_NUM_OF_LANES];
@@ -576,8 +578,13 @@ irqreturn_t gaudi_nic_rx_irq_handler(int irq, void *arg);
 irqreturn_t gaudi_nic_cq_irq_handler(int irq, void *arg);
 netdev_tx_t gaudi_nic_handle_tx_pkt(struct gaudi_nic_device *gaudi_nic,
                                        struct sk_buff *skb);
+void gaudi_nic_spmu_init(struct hl_device *hdev, int port);
 int gaudi_nic_sw_init(struct hl_device *hdev);
 void gaudi_nic_sw_fini(struct hl_device *hdev);
 void gaudi_nic_handle_qp_err(struct hl_device *hdev, u16 event_type);
+int gaudi_config_spmu_nic(struct hl_device *hdev, u32 port,
+               u32 num_event_types, u32 event_types[]);
+int gaudi_sample_spmu_nic(struct hl_device *hdev, u32 port,
+               u32 num_out_data, u64 out_data[]);
 
 #endif /* GAUDIP_H_ */
diff --git a/drivers/misc/habanalabs/gaudi/gaudi_coresight.c 
b/drivers/misc/habanalabs/gaudi/gaudi_coresight.c
index 881531d4d9da..c31953403d09 100644
--- a/drivers/misc/habanalabs/gaudi/gaudi_coresight.c
+++ b/drivers/misc/habanalabs/gaudi/gaudi_coresight.c
@@ -16,6 +16,11 @@
 #define SPMU_SECTION_SIZE              MME0_ACC_SPMU_MAX_OFFSET
 #define SPMU_EVENT_TYPES_OFFSET                0x400
 #define SPMU_MAX_COUNTERS              6
+#define PMSCR                          0x6F0   /* Snapshot Control */
+#define PMEVCNTSR0                     0x620   /* Event Counters Snapshot */
+#define PMOVSSR                                0x614   /* Overflow Status 
Snapshot */
+#define PMCCNTSR_L                     0x618   /* Cycle Counter Snapshot */
+#define PMCCNTSR_H                     0x61c   /* Cycle Counter Snapshot */
 
 static u64 debug_stm_regs[GAUDI_STM_LAST + 1] = {
        [GAUDI_STM_MME0_ACC]    = mmMME0_ACC_STM_BASE,
@@ -752,6 +757,27 @@ static int gaudi_config_bmon(struct hl_device *hdev,
        return 0;
 }
 
+static bool gaudi_reg_is_nic_spmu(enum gaudi_debug_spmu_regs_index reg_idx)
+{
+       switch (reg_idx) {
+       case GAUDI_SPMU_NIC0_0:
+       case GAUDI_SPMU_NIC0_1:
+       case GAUDI_SPMU_NIC1_0:
+       case GAUDI_SPMU_NIC1_1:
+       case GAUDI_SPMU_NIC2_0:
+       case GAUDI_SPMU_NIC2_1:
+       case GAUDI_SPMU_NIC3_0:
+       case GAUDI_SPMU_NIC3_1:
+       case GAUDI_SPMU_NIC4_0:
+       case GAUDI_SPMU_NIC4_1:
+               return true;
+       default:
+               break;
+       }
+
+       return false;
+}
+
 static int gaudi_config_spmu(struct hl_device *hdev,
                struct hl_debug_params *params)
 {
@@ -769,6 +795,16 @@ static int gaudi_config_spmu(struct hl_device *hdev,
                return -EINVAL;
        }
 
+       /*
+        * NIC spmus are now configured by driver at init
+        * and not accessible to user in dbg mode
+        */
+       if (hdev->in_debug && gaudi_reg_is_nic_spmu(params->reg_idx)) {
+               dev_err(hdev->dev,
+                       "Rejecting user debug configuration for NIC spmu\n");
+               return -EFAULT;
+       }
+
        base_reg = debug_spmu_regs[params->reg_idx] - CFG_BASE;
 
        if (params->enable) {
@@ -837,6 +873,114 @@ static int gaudi_config_spmu(struct hl_device *hdev,
        return 0;
 }
 
+static int gaudi_sample_spmu(struct hl_device *hdev,
+               struct hl_debug_params *params)
+{
+       u64 base_reg;
+       u64 *output;
+       u32 output_arr_len;
+       u32 events_num;
+       u32 overflow_idx;
+       u32 cycle_cnt_idx;
+       int i;
+
+       if (params->reg_idx >= ARRAY_SIZE(debug_spmu_regs)) {
+               dev_err(hdev->dev, "Invalid register index in SPMU\n");
+               return -EINVAL;
+       }
+
+       base_reg = debug_spmu_regs[params->reg_idx] - CFG_BASE;
+
+       output = params->output;
+       output_arr_len = params->output_size / 8;
+       events_num = output_arr_len - 2;
+       overflow_idx = output_arr_len - 2;
+       cycle_cnt_idx = output_arr_len - 1;
+
+       if (!output)
+               return -EINVAL;
+
+       if (output_arr_len < 1) {
+               dev_err(hdev->dev,
+                       "not enough values for SPMU sample\n");
+               return -EINVAL;
+       }
+
+       if (events_num > SPMU_MAX_COUNTERS) {
+               dev_err(hdev->dev,
+                       "too many events values for SPMU sample\n");
+               return -EINVAL;
+       }
+
+       /* capture */
+       WREG32(base_reg + PMSCR, 1);
+
+       /* read the shadow registers */
+       for (i = 0 ; i < events_num ; i++)
+               output[i] = RREG32(base_reg + PMEVCNTSR0 + i * 4);
+
+       /* also get overflow and cyclecount */
+       if (output_arr_len == SPMU_MAX_COUNTERS + 2) {
+               output[overflow_idx] = RREG32(base_reg + PMOVSSR);
+
+               output[cycle_cnt_idx] = RREG32(base_reg + PMCCNTSR_H);
+               output[cycle_cnt_idx] <<= 32;
+               output[cycle_cnt_idx] |= RREG32(base_reg + PMCCNTSR_L);
+       }
+
+       return 0;
+}
+
+int gaudi_config_spmu_nic(struct hl_device *hdev, u32 port,
+               u32 num_event_types, u32 event_types[])
+{
+       struct hl_debug_params params;
+       struct hl_debug_params_spmu spmu;
+       int i;
+
+       /* validate nic port */
+       if  (!gaudi_reg_is_nic_spmu(GAUDI_SPMU_NIC0_0 + port)) {
+               dev_err(hdev->dev, "Invalid nic port %u\n", port);
+               return -EFAULT;
+       }
+
+       memset(&params, 0, sizeof(struct hl_debug_params));
+       params.op = HL_DEBUG_OP_SPMU;
+       params.input = &spmu;
+       params.enable = true;
+       params.reg_idx = GAUDI_SPMU_NIC0_0 + port;
+
+       memset(&spmu, 0, sizeof(struct hl_debug_params_spmu));
+       spmu.event_types_num  = num_event_types;
+
+       for (i = 0 ; i < spmu.event_types_num ; i++)
+               spmu.event_types[i] = event_types[i];
+
+       return gaudi_config_spmu(hdev, &params);
+}
+
+int gaudi_sample_spmu_nic(struct hl_device *hdev, u32 port,
+               u32 num_out_data, u64 out_data[])
+{
+       struct hl_debug_params params;
+
+       if (!hdev->supports_coresight)
+               return 0;
+
+       /* validate nic port */
+       if  (!gaudi_reg_is_nic_spmu(GAUDI_SPMU_NIC0_0 + port)) {
+               dev_err(hdev->dev, "Invalid nic port %u\n", port);
+               return -EFAULT;
+       }
+
+       memset(&params, 0, sizeof(struct hl_debug_params));
+       params.output = out_data;
+       params.output_size = num_out_data * sizeof(uint64_t);
+       params.reg_idx = GAUDI_SPMU_NIC0_0 + port;
+
+       return gaudi_sample_spmu(hdev, &params);
+}
+
 int gaudi_debug_coresight(struct hl_device *hdev, void *data)
 {
        struct hl_debug_params *params = data;
diff --git a/drivers/misc/habanalabs/gaudi/gaudi_nic.c 
b/drivers/misc/habanalabs/gaudi/gaudi_nic.c
index a73635a4c44b..108db990efa8 100644
--- a/drivers/misc/habanalabs/gaudi/gaudi_nic.c
+++ b/drivers/misc/habanalabs/gaudi/gaudi_nic.c
@@ -2744,6 +2744,7 @@ static int port_register(struct hl_device *hdev, int port)
        ndev->dev_port = port;
 
        ndev->netdev_ops = &gaudi_nic_netdev_ops;
+       ndev->ethtool_ops = &gaudi_nic_ethtool_ops;
        ndev->watchdog_timeo = NIC_TX_TIMEOUT;
        ndev->min_mtu = ETH_MIN_MTU;
        ndev->max_mtu = NIC_MAX_MTU;
@@ -2769,6 +2770,8 @@ static int port_register(struct hl_device *hdev, int port)
                                port);
        }
 
+       gaudi->nic_spmu_init(hdev, port);
+
        if (register_netdev(ndev)) {
                dev_err(hdev->dev,
                        "Could not register netdevice, port: %d\n", port);
@@ -3233,6 +3236,8 @@ void gaudi_nic_ports_reopen(struct hl_device *hdev)
                        continue;
                }
 
+               gaudi->nic_spmu_init(hdev, port);
+
                schedule_delayed_work(&gaudi_nic->port_open_work,
                                        msecs_to_jiffies(1));
        }
diff --git a/drivers/misc/habanalabs/gaudi/gaudi_nic_ethtool.c 
b/drivers/misc/habanalabs/gaudi/gaudi_nic_ethtool.c
new file mode 100644
index 000000000000..28982192e98d
--- /dev/null
+++ b/drivers/misc/habanalabs/gaudi/gaudi_nic_ethtool.c
@@ -0,0 +1,582 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * Copyright 2018-2020 HabanaLabs, Ltd.
+ * All Rights Reserved.
+ */
+
+#include "gaudi_nic.h"
+#include "../include/gaudi/asic_reg/gaudi_regs.h"
+#include <linux/pci.h>
+
+#define NIC_STATS_LEN          ARRAY_SIZE(gaudi_nic_ethtool_stats)
+#define NIC_SPMU0_STATS_LEN    ARRAY_SIZE(gaudi_nic0_spmu_event_type)
+#define NIC_SPMU1_STATS_LEN    ARRAY_SIZE(gaudi_nic1_spmu_event_type)
+#define NIC_SPMU_STATS_LEN_MAX 6
+#define NIC_MAC_STATS_RX_LEN   ARRAY_SIZE(gaudi_nic_mac_stats_rx)
+#define NIC_MAC_STATS_TX_LEN   ARRAY_SIZE(gaudi_nic_mac_stats_tx)
+#define NIC_XPCS91_REGS_CNT_LEN        ARRAY_SIZE(gaudi_nic_xpcs91_reg_type)
+#define NIC_SW_CNT_LEN         ARRAY_SIZE(gaudi_nic_sw_cnt_type)
+
+#define NIC_MAC_STAT_BLOCK_SIZE        (mmNIC1_STAT_BASE - mmNIC0_STAT_BASE)
+#define NIC_MAC_STAT_HI_PART   mmNIC0_STAT_DATA_HI_REG
+#define NIC_MAC_RX_PORT0_OFFSET        mmNIC0_STAT_ETHERSTATSOCTETS
+#define NIC_MAC_RX_PORT1_OFFSET        mmNIC0_STAT_ETHERSTATSOCTETS_2
+#define NIC_MAC_TX_PORT0_OFFSET        mmNIC0_STAT_ETHERSTATSOCTETS_4
+#define NIC_MAC_TX_PORT1_OFFSET        mmNIC0_STAT_ETHERSTATSOCTETS_6
+
+#define NIC_MAC_STAT_BASE(port) \
+                       ((u64) (NIC_MAC_STAT_BLOCK_SIZE * (u64) ((port) >> 1)))
+
+#define NIC_MAC_STAT_RREG32(port, reg) \
+                       RREG32(NIC_MAC_STAT_BASE(port) + (reg))
+
+#define ethtool_add_mode ethtool_link_ksettings_add_link_mode
+
+struct gaudi_nic_ethtool_stats {
+       char stat_string[ETH_GSTRING_LEN];
+       int stat_offset;
+};
+
+struct gaudi_nic_spmu_event_type {
+       char stat_string[ETH_GSTRING_LEN];
+       int index;
+};
+
+struct gaudi_nic_xpcs91_reg_type {
+       char stat_string[ETH_GSTRING_LEN];
+       int lo_offset;
+       int hi_offset;
+};
+
+struct gaudi_nic_sw_cnt_type {
+       char stat_string[ETH_GSTRING_LEN];
+};
+
+#define NIC_STAT(m) {__stringify(m), offsetof(struct net_device, stats.m)}
+
+static struct gaudi_nic_ethtool_stats gaudi_nic_ethtool_stats[] = {
+       NIC_STAT(rx_packets),
+       NIC_STAT(tx_packets),
+       NIC_STAT(rx_bytes),
+       NIC_STAT(tx_bytes),
+       NIC_STAT(rx_errors),
+       NIC_STAT(tx_errors),
+       NIC_STAT(rx_dropped),
+       NIC_STAT(tx_dropped),
+       NIC_STAT(multicast),
+       NIC_STAT(collisions),
+       NIC_STAT(rx_length_errors),
+       NIC_STAT(rx_over_errors),
+       NIC_STAT(rx_crc_errors),
+       NIC_STAT(rx_frame_errors),
+       NIC_STAT(rx_fifo_errors),
+       NIC_STAT(rx_missed_errors),
+       NIC_STAT(tx_aborted_errors),
+       NIC_STAT(tx_carrier_errors),
+       NIC_STAT(tx_fifo_errors),
+       NIC_STAT(tx_heartbeat_errors),
+       NIC_STAT(tx_window_errors)
+};
+
+static struct gaudi_nic_ethtool_stats gaudi_nic_mac_stats_rx[] = {
+       {"Rx MAC counters", 0},
+       {"  etherStatsOctets", 0x0},
+       {"  OctetsReceivedOK", 0x4},
+       {"  aAlignmentErrors", 0x8},
+       {"  aPAUSEMACCtrlFramesReceived", 0xC},
+       {"  aFrameTooLongErrors", 0x10},
+       {"  aInRangeLengthErrors", 0x14},
+       {"  aFramesReceivedOK", 0x18},
+       {"  VLANReceivedOK", 0x1C},
+       {"  aFrameCheckSequenceErrors", 0x20},
+       {"  ifInErrors", 0x24},
+       {"  ifInUcastPkts", 0x28},
+       {"  ifInMulticastPkts", 0x2C},
+       {"  ifInBroadcastPkts", 0x30},
+       {"  etherStatsDropEvents", 0x34},
+       {"  etherStatsUndersizePkts", 0x38},
+       {"  etherStatsPkts", 0x3C},
+       {"  etherStatsPkts64Octets", 0x40},
+       {"  etherStatsPkts65to127Octets", 0x44},
+       {"  etherStatsPkts128to255Octets", 0x48},
+       {"  etherStatsPkts256to511Octets", 0x4C},
+       {"  etherStatsPkts512to1023Octets", 0x50},
+       {"  etherStatsPkts1024to1518Octets", 0x54},
+       {"  etherStatsPkts1519toMaxOctets", 0x58},
+       {"  etherStatsOversizePkts", 0x5C},
+       {"  etherStatsJabbers", 0x60},
+       {"  etherStatsFragments", 0x64},
+       {"  aCBFCPAUSEFramesReceived_0", 0x68},
+       {"  aCBFCPAUSEFramesReceived_1", 0x6C},
+       {"  aCBFCPAUSEFramesReceived_2", 0x70},
+       {"  aCBFCPAUSEFramesReceived_3", 0x74},
+       {"  aCBFCPAUSEFramesReceived_4", 0x78},
+       {"  aCBFCPAUSEFramesReceived_5", 0x7C},
+       {"  aCBFCPAUSEFramesReceived_6", 0x80},
+       {"  aCBFCPAUSEFramesReceived_7", 0x84},
+       {"  aMACControlFramesReceived", 0x88}
+};
+
+static struct gaudi_nic_ethtool_stats gaudi_nic_mac_stats_tx[] = {
+       {"Tx MAC counters", 0},
+       {"  etherStatsOctets", 0x0},
+       {"  OctetsTransmittedOK", 0x4},
+       {"  aPAUSEMACCtrlFramesTransmitted", 0x8},
+       {"  aFramesTransmittedOK", 0xC},
+       {"  VLANTransmittedOK", 0x10},
+       {"  ifOutErrors", 0x14},
+       {"  ifOutUcastPkts", 0x18},
+       {"  ifOutMulticastPkts", 0x1C},
+       {"  ifOutBroadcastPkts", 0x20},
+       {"  etherStatsPkts64Octets", 0x24},
+       {"  etherStatsPkts65to127Octets", 0x28},
+       {"  etherStatsPkts128to255Octets", 0x2C},
+       {"  etherStatsPkts256to511Octets", 0x30},
+       {"  etherStatsPkts512to1023Octets", 0x34},
+       {"  etherStatsPkts1024to1518Octets", 0x38},
+       {"  etherStatsPkts1519toMaxOctets", 0x3C},
+       {"  aCBFCPAUSEFramesTransmitted_0", 0x40},
+       {"  aCBFCPAUSEFramesTransmitted_1", 0x44},
+       {"  aCBFCPAUSEFramesTransmitted_2", 0x48},
+       {"  aCBFCPAUSEFramesTransmitted_3", 0x4C},
+       {"  aCBFCPAUSEFramesTransmitted_4", 0x50},
+       {"  aCBFCPAUSEFramesTransmitted_5", 0x54},
+       {"  aCBFCPAUSEFramesTransmitted_6", 0x58},
+       {"  aCBFCPAUSEFramesTransmitted_7", 0x5C},
+       {"  aMACControlFramesTransmitted", 0x60},
+       {"  etherStatsPkts", 0x64}
+};
+
+static struct gaudi_nic_spmu_event_type gaudi_nic0_spmu_event_type[] = {
+       {"requester_psn_out_of_range", 18},
+       {"responder_duplicate_psn", 21},
+       {"responder_out_of_sequence_psn", 22}
+};
+
+static struct gaudi_nic_spmu_event_type gaudi_nic1_spmu_event_type[] = {
+       {"requester_psn_out_of_range", 6},
+       {"responder_duplicate_psn", 9},
+       {"responder_out_of_sequence_psn", 10}
+};
+
+static struct gaudi_nic_xpcs91_reg_type gaudi_nic_xpcs91_reg_type[] = {
+       {"  correctable_errors", 0x2, 0x3},
+       {"  uncorrectable_errors", 0x4, 0x5}
+};
+
+static struct gaudi_nic_sw_cnt_type gaudi_nic_sw_cnt_type[] = {
+       {"  pcs_local_faults"},
+       {"  pcs_remote_faults"},
+};
+
+static void gaudi_nic_get_drvinfo(struct net_device *netdev,
+                                       struct ethtool_drvinfo *drvinfo)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct hl_device *hdev = gaudi_nic->hdev;
+
+       strlcpy(drvinfo->driver, HL_NAME, sizeof(drvinfo->driver));
+       strlcpy(drvinfo->fw_version, hdev->asic_prop.cpucp_info.cpucp_version,
+               sizeof(drvinfo->fw_version));
+       if (hdev->pdev)
+               strlcpy(drvinfo->bus_info, pci_name(hdev->pdev),
+                               sizeof(drvinfo->bus_info));
+}
+
+static int gaudi_nic_get_link_ksettings(struct net_device *netdev,
+                                       struct ethtool_link_ksettings *cmd)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct hl_device *hdev = gaudi_nic->hdev;
+       u32 port = gaudi_nic->port, speed = gaudi_nic->speed;
+
+       cmd->base.speed = speed;
+       cmd->base.duplex = DUPLEX_FULL;
+
+       ethtool_link_ksettings_zero_link_mode(cmd, supported);
+       ethtool_link_ksettings_zero_link_mode(cmd, advertising);
+
+       ethtool_add_mode(cmd, supported, 100000baseCR4_Full);
+       ethtool_add_mode(cmd, supported, 100000baseSR4_Full);
+       ethtool_add_mode(cmd, supported, 100000baseKR4_Full);
+       ethtool_add_mode(cmd, supported, 100000baseLR4_ER4_Full);
+
+       ethtool_add_mode(cmd, supported, 50000baseSR2_Full);
+       ethtool_add_mode(cmd, supported, 50000baseCR2_Full);
+       ethtool_add_mode(cmd, supported, 50000baseKR2_Full);
+
+       if (speed == SPEED_100000) {
+               ethtool_add_mode(cmd, advertising, 100000baseCR4_Full);
+               ethtool_add_mode(cmd, advertising, 100000baseSR4_Full);
+               ethtool_add_mode(cmd, advertising, 100000baseKR4_Full);
+               ethtool_add_mode(cmd, advertising, 100000baseLR4_ER4_Full);
+
+               cmd->base.port = PORT_FIBRE;
+
+               ethtool_add_mode(cmd, supported, FIBRE);
+               ethtool_add_mode(cmd, advertising, FIBRE);
+
+               ethtool_add_mode(cmd, supported, Backplane);
+               ethtool_add_mode(cmd, advertising, Backplane);
+       } else if (speed == SPEED_50000) {
+               ethtool_add_mode(cmd, advertising, 50000baseSR2_Full);
+               ethtool_add_mode(cmd, advertising, 50000baseCR2_Full);
+               ethtool_add_mode(cmd, advertising, 50000baseKR2_Full);
+       } else {
+               dev_err(hdev->dev, "unknown speed %d, port %d\n", speed, port);
+               return -EFAULT;
+       }
+
+       ethtool_add_mode(cmd, supported, Autoneg);
+
+       if (gaudi_nic->auto_neg_enable) {
+               ethtool_add_mode(cmd, advertising, Autoneg);
+               cmd->base.autoneg = AUTONEG_ENABLE;
+               if (gaudi_nic->auto_neg_resolved)
+                       ethtool_add_mode(cmd, lp_advertising, Autoneg);
+       } else {
+               cmd->base.autoneg = AUTONEG_DISABLE;
+       }
+
+       ethtool_add_mode(cmd, supported, Pause);
+
+       if (gaudi_nic->pfc_enable)
+               ethtool_add_mode(cmd, advertising, Pause);
+
+       return 0;
+}
+
+static int gaudi_nic_set_link_ksettings(struct net_device *netdev,
+                               const struct ethtool_link_ksettings *cmd)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct hl_device *hdev = gaudi_nic->hdev;
+       u32 port = gaudi_nic->port;
+       int rc = 0, speed = cmd->base.speed;
+       bool auto_neg = cmd->base.autoneg == AUTONEG_ENABLE;
+
+       switch (speed) {
+       case SPEED_10000:
+       case SPEED_25000:
+       case SPEED_50000:
+               if (gaudi_nic->nic_macro->num_of_lanes == NIC_LANES_4) {
+                       dev_err(hdev->dev,
+                               "NIC %d with 4 lanes should be used only with 
speed of 100000Mb/s\n",
+                               port);
+                       return -EFAULT;
+               }
+               break;
+       case SPEED_100000:
+               break;
+       default:
+               dev_err(hdev->dev, "got invalid speed %dMb/s for NIC %d",
+                       speed, port);
+               return -EINVAL;
+       }
+
+       if ((gaudi_nic->speed == speed) &&
+                       (gaudi_nic->auto_neg_enable == auto_neg))
+               return 0;
+
+       if (atomic_cmpxchg(&gaudi_nic->in_reset, 0, 1)) {
+               dev_err(hdev->dev, "port %d is in reset, can't change speed",
+                       port);
+               return -EBUSY;
+       }
+
+       gaudi_nic->speed = speed;
+       if (auto_neg)
+               hdev->nic_auto_neg_mask |= BIT(port);
+       else
+               hdev->nic_auto_neg_mask &= ~BIT(port);
+
+       if (gaudi_nic->enabled) {
+               rc = gaudi_nic_port_reset(gaudi_nic);
+               if (rc)
+                       dev_err(hdev->dev,
+                               "Failed to reset NIC %d for speed change, rc 
%d",
+                               port, rc);
+       }
+
+       atomic_set(&gaudi_nic->in_reset, 0);
+
+       return rc;
+}
+
+static u32 gaudi_nic_get_link(struct net_device *netdev)
+{
+       return netif_carrier_ok(netdev);
+}
+
+static void gaudi_nic_get_internal_strings(struct net_device *netdev,
+                                       u8 *data)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct gaudi_nic_spmu_event_type *spmu_stats;
+       u32 port = gaudi_nic->port;
+       u32 num_spmus;
+       u32 i;
+
+       if (port & 1) {
+               num_spmus = NIC_SPMU1_STATS_LEN;
+               spmu_stats = gaudi_nic1_spmu_event_type;
+       } else {
+               num_spmus = NIC_SPMU0_STATS_LEN;
+               spmu_stats = gaudi_nic0_spmu_event_type;
+       }
+
+       for (i = 0 ; i < num_spmus ; i++)
+               memcpy(data + i * ETH_GSTRING_LEN,
+                               spmu_stats[i].stat_string,
+                               ETH_GSTRING_LEN);
+       data += i * ETH_GSTRING_LEN;
+       for (i = 0 ; i < NIC_MAC_STATS_RX_LEN ; i++)
+               memcpy(data + i * ETH_GSTRING_LEN,
+                               gaudi_nic_mac_stats_rx[i].stat_string,
+                               ETH_GSTRING_LEN);
+       data += i * ETH_GSTRING_LEN;
+       for (i = 0 ; i < NIC_XPCS91_REGS_CNT_LEN ; i++)
+               memcpy(data + i * ETH_GSTRING_LEN,
+                               gaudi_nic_xpcs91_reg_type[i].stat_string,
+                               ETH_GSTRING_LEN);
+       data += i * ETH_GSTRING_LEN;
+       for (i = 0 ; i < NIC_SW_CNT_LEN ; i++)
+               memcpy(data + i * ETH_GSTRING_LEN,
+                               gaudi_nic_sw_cnt_type[i].stat_string,
+                               ETH_GSTRING_LEN);
+       data += i * ETH_GSTRING_LEN;
+       for (i = 0 ; i < NIC_MAC_STATS_TX_LEN ; i++)
+               memcpy(data + i * ETH_GSTRING_LEN,
+                               gaudi_nic_mac_stats_tx[i].stat_string,
+                               ETH_GSTRING_LEN);
+
+}
+
+static void gaudi_nic_get_strings(struct net_device *netdev, u32 stringset,
+                                       u8 *data)
+{
+       int i;
+
+       if (stringset == ETH_SS_STATS) {
+               for (i = 0; i < NIC_STATS_LEN; i++)
+                       memcpy(data + i * ETH_GSTRING_LEN,
+                                       gaudi_nic_ethtool_stats[i].stat_string,
+                                       ETH_GSTRING_LEN);
+               gaudi_nic_get_internal_strings(netdev,
+                                       data + i * ETH_GSTRING_LEN);
+       }
+}
+
+static int gaudi_nic_get_sset_count(struct net_device *netdev, int sset)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       u32 port = gaudi_nic->port;
+       int num_spmus, mac_counters, xpcs91_counters, sw_counetrs;
+
+       num_spmus = (port & 1) ? NIC_SPMU1_STATS_LEN : NIC_SPMU0_STATS_LEN;
+       mac_counters = NIC_MAC_STATS_RX_LEN + NIC_MAC_STATS_TX_LEN;
+       xpcs91_counters = NIC_XPCS91_REGS_CNT_LEN;
+       sw_counetrs = NIC_SW_CNT_LEN;
+
+       switch (sset) {
+       case ETH_SS_STATS:
+               return NIC_STATS_LEN + num_spmus + mac_counters +
+                       xpcs91_counters + sw_counetrs;
+       default:
+               return -EOPNOTSUPP;
+       }
+}
+static u64 gaudi_nic_read_mac_counter(struct hl_device *hdev, u32 port,
+                                               int offset, bool is_rx)
+{
+       u64 lo_part, hi_part;
+       u64 start_reg;
+
+       if (!hdev->supports_coresight)
+               return 0;
+
+       if (is_rx)
+               if (port & 1)
+                       start_reg = NIC_MAC_RX_PORT1_OFFSET;
+               else
+                       start_reg = NIC_MAC_RX_PORT0_OFFSET;
+       else
+               if (port & 1)
+                       start_reg = NIC_MAC_TX_PORT1_OFFSET;
+               else
+                       start_reg = NIC_MAC_TX_PORT0_OFFSET;
+
+       lo_part = NIC_MAC_STAT_RREG32(port, start_reg + offset);
+       /* Volatile read: MUST read high part after low */
+       hi_part = NIC_MAC_STAT_RREG32(port, NIC_MAC_STAT_HI_PART);
+
+       return lo_part | (hi_part << 32);
+}
+
+static void gaudi_nic_read_xpcs91_regs(struct gaudi_nic_device *gaudi_nic,
+                                       u64 *out_data)
+{
+       u32 lo_part, hi_part, start_lane = __ffs(gaudi_nic->fw_tuning_mask);
+
+       lo_part = gaudi_nic_mac_read(gaudi_nic, start_lane, "xpcs91",
+                       gaudi_nic_xpcs91_reg_type[0].lo_offset);
+       hi_part = gaudi_nic_mac_read(gaudi_nic, start_lane, "xpcs91",
+                       gaudi_nic_xpcs91_reg_type[0].hi_offset);
+       gaudi_nic->correctable_errors_cnt +=
+                                       (hi_part << 16) | lo_part;
+       out_data[0] = gaudi_nic->correctable_errors_cnt;
+
+       lo_part = gaudi_nic_mac_read(gaudi_nic, start_lane, "xpcs91",
+                       gaudi_nic_xpcs91_reg_type[1].lo_offset);
+       hi_part = gaudi_nic_mac_read(gaudi_nic, start_lane, "xpcs91",
+                       gaudi_nic_xpcs91_reg_type[1].hi_offset);
+       gaudi_nic->uncorrectable_errors_cnt +=
+                                       (hi_part << 16) | lo_part;
+       out_data[1] = gaudi_nic->uncorrectable_errors_cnt;
+}
+
+static void gaudi_nic_read_sw_counters(struct gaudi_nic_device *gaudi_nic,
+                                       u64 *out_data)
+{
+       out_data[0] = gaudi_nic->pcs_local_fault_cnt;
+       out_data[1] = gaudi_nic->pcs_remote_fault_cnt;
+}
+
+static void gaudi_nic_get_internal_stats(struct net_device *netdev, u64 *data)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct hl_device *hdev = gaudi_nic->hdev;
+       u32 port = gaudi_nic->port;
+       u32 num_spmus = (port & 1) ? NIC_SPMU1_STATS_LEN : NIC_SPMU0_STATS_LEN;
+       int i;
+
+       gaudi_sample_spmu_nic(hdev, port, num_spmus, data);
+       data += num_spmus;
+
+       /* first entry is title */
+       data[0] = 0;
+       for (i = 1 ; i < NIC_MAC_STATS_RX_LEN ; i++)
+               data[i] = gaudi_nic_read_mac_counter(hdev, port,
+                               gaudi_nic_mac_stats_rx[i].stat_offset, true);
+       data += i;
+
+       gaudi_nic_read_xpcs91_regs(gaudi_nic, data);
+       data += NIC_XPCS91_REGS_CNT_LEN;
+
+       gaudi_nic_read_sw_counters(gaudi_nic, data);
+       data += NIC_SW_CNT_LEN;
+
+       /* first entry is title */
+       data[0] = 0;
+       for (i = 1 ; i < NIC_MAC_STATS_TX_LEN ; i++)
+               data[i] = gaudi_nic_read_mac_counter(hdev, port,
+                               gaudi_nic_mac_stats_tx[i].stat_offset, false);
+}
+
+static void gaudi_nic_get_ethtool_stats(struct net_device *netdev,
+                                       struct ethtool_stats *stats, u64 *data)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct hl_device *hdev = gaudi_nic->hdev;
+       char *p;
+       u32 port = gaudi_nic->port;
+       int i;
+
+       if (disabled_or_in_reset(gaudi_nic)) {
+               dev_info_ratelimited(hdev->dev,
+                       "port %d is in reset, can't get ethtool stats", port);
+               return;
+       }
+
+       for (i = 0; i < NIC_STATS_LEN ; i++) {
+               p = (char *) netdev + gaudi_nic_ethtool_stats[i].stat_offset;
+               data[i] = *(u32 *) p;
+       }
+
+       gaudi_nic_get_internal_stats(netdev, data + i);
+}
+
+static int gaudi_nic_get_module_info(struct net_device *netdev,
+                                       struct ethtool_modinfo *modinfo)
+{
+       modinfo->type = ETH_MODULE_SFF_8636;
+       modinfo->eeprom_len = ETH_MODULE_SFF_8636_LEN;
+
+       return 0;
+}
+
+static int gaudi_nic_get_module_eeprom(struct net_device *netdev,
+                                       struct ethtool_eeprom *ee, u8 *data)
+{
+       struct gaudi_nic_device **ptr = netdev_priv(netdev);
+       struct gaudi_nic_device *gaudi_nic = *ptr;
+       struct hl_device *hdev = gaudi_nic->hdev;
+
+       if (!ee->len)
+               return -EINVAL;
+
+       memset(data, 0, ee->len);
+       memcpy(data, hdev->asic_prop.cpucp_nic_info.qsfp_eeprom, ee->len);
+
+       return 0;
+}
+
+/* enable spmus for ethtool monitoring */
+void gaudi_nic_spmu_init(struct hl_device *hdev, int port)
+{
+       struct gaudi_nic_spmu_event_type *event_types;
+       u32 spmu_events[NIC_SPMU_STATS_LEN_MAX], num_event_types;
+       int rc, i;
+
+       if (port & 1) {
+               num_event_types = NIC_SPMU1_STATS_LEN;
+               event_types = gaudi_nic1_spmu_event_type;
+       } else {
+               num_event_types = NIC_SPMU0_STATS_LEN;
+               event_types = gaudi_nic0_spmu_event_type;
+       }
+
+       if (num_event_types > NIC_SPMU_STATS_LEN_MAX)
+               num_event_types = NIC_SPMU_STATS_LEN_MAX;
+
+       for (i = 0 ; i < num_event_types ; i++)
+               spmu_events[i] = event_types[i].index;
+
+       rc = gaudi_config_spmu_nic(hdev, port, num_event_types,
+                       spmu_events);
+       if (rc)
+               dev_err(hdev->dev,
+                       "Failed to configure spmu for NIC port %d\n",
+                       port);
+}
+
+u64 gaudi_nic_read_mac_stat_counter(struct hl_device *hdev, u32 port, int idx,
+                                       bool is_rx)
+{
+       struct gaudi_nic_ethtool_stats *stat = is_rx ?
+                                               &gaudi_nic_mac_stats_rx[idx] :
+                                               &gaudi_nic_mac_stats_tx[idx];
+
+       return gaudi_nic_read_mac_counter(hdev, port, stat->stat_offset, is_rx);
+}
+
+const struct ethtool_ops gaudi_nic_ethtool_ops = {
+       .get_drvinfo = gaudi_nic_get_drvinfo,
+       .get_link_ksettings = gaudi_nic_get_link_ksettings,
+       .set_link_ksettings = gaudi_nic_set_link_ksettings,
+       .get_link = gaudi_nic_get_link,
+       .get_strings = gaudi_nic_get_strings,
+       .get_sset_count = gaudi_nic_get_sset_count,
+       .get_ethtool_stats = gaudi_nic_get_ethtool_stats,
+       .get_module_info   = gaudi_nic_get_module_info,
+       .get_module_eeprom = gaudi_nic_get_module_eeprom,
+};
+
-- 
2.17.1

Reply via email to