// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2017 - 2019 Pensando Systems, Inc */
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/sfp.h>
#include "ionic.h"
#include "ionic_bus.h"
#include "ionic_lif.h"
#include "ionic_ethtool.h"
#include "ionic_stats.h"
static const char ionic_priv_flags_strings[][ETH_GSTRING_LEN] = {
#define IONIC_PRIV_F_SW_DBG_STATS BIT(0)
"sw-dbg-stats",
};
#define IONIC_PRIV_FLAGS_COUNT ARRAY_SIZE(ionic_priv_flags_strings)
static void ionic_get_stats_strings(struct ionic_lif *lif, u8 *buf)
{
u32 i;
for (i = 0; i < ionic_num_stats_grps; i++)
ionic_stats_groups[i].get_strings(lif, &buf);
}
static void ionic_get_stats(struct net_device *netdev,
struct ethtool_stats *stats, u64 *buf)
{
struct ionic_lif *lif;
u32 i;
lif = netdev_priv(netdev);
memset(buf, 0, stats->n_stats * sizeof(*buf));
for (i = 0; i < ionic_num_stats_grps; i++)
ionic_stats_groups[i].get_values(lif, &buf);
}
static int ionic_get_stats_count(struct ionic_lif *lif)
{
int i, num_stats = 0;
for (i = 0; i < ionic_num_stats_grps; i++)
num_stats += ionic_stats_groups[i].get_count(lif);
return num_stats;
}
static int ionic_get_sset_count(struct net_device *netdev, int sset)
{
struct ionic_lif *lif = netdev_priv(netdev);
int count = 0;
switch (sset) {
case ETH_SS_STATS:
count = ionic_get_stats_count(lif);
break;
case ETH_SS_PRIV_FLAGS:
count = IONIC_PRIV_FLAGS_COUNT;
break;
}
return count;
}
static void ionic_get_strings(struct net_device *netdev,
u32 sset, u8 *buf)
{
struct ionic_lif *lif = netdev_priv(netdev);
switch (sset) {
case ETH_SS_STATS:
ionic_get_stats_strings(lif, buf);
break;
case ETH_SS_PRIV_FLAGS:
memcpy(buf, ionic_priv_flags_strings,
IONIC_PRIV_FLAGS_COUNT * ETH_GSTRING_LEN);
break;
}
}
static void ionic_get_drvinfo(struct net_device *netdev,
struct ethtool_drvinfo *drvinfo)
{
struct ionic_lif *lif = netdev_priv(netdev);
struct ionic *ionic = lif->ionic;
strlcpy(drvinfo->driver, IONIC_DRV_NAME, sizeof(drvinfo->driver));
strlcpy(drvinfo->fw_version, ionic->idev.dev_info.fw_version,
sizeof(drvinfo->fw_version));
strlcpy(drvinfo->bus_info, ionic_bus_info(ionic),
sizeof(drvinfo->bus_info));
}
static int ionic_get_regs_len(struct net_device *netdev)
{
return (IONIC_DEV_INFO_REG_COUNT + IONIC_DEV_CMD_REG_COUNT) * sizeof(u32);
}
static void ionic_get_regs(struct net_device *netdev, struct ethtool_regs *regs,
void *p)
{
struct ionic_lif *lif = netdev_priv(netdev);
unsigned int offset;
unsigned int size;
regs->version = IONIC_DEV_CMD_REG_VERSION;
offset = 0;
size = IONIC_DEV_INFO_REG_COUNT * sizeof(u32);
memcpy_fromio(p + offset, lif->ionic->idev.dev_info_regs->words, size);
offset += size;
size = IONIC_DEV_CMD_REG_COUNT * sizeof(u32);
memcpy_fromio(p + offset, lif->ionic->idev.dev_cmd_regs->words, size);
}
static int ionic_get_link_ksettings(struct net_device *netdev,
struct ethtool_link_ksettings *ks)
{
struct ionic_lif *lif = netdev_priv(netdev);
struct ionic_dev *idev = &lif->ionic->idev;
int copper_seen = 0;
ethtool_link_ksettings_zero_link_mode(ks, supported);
/* The port_info data is found in a DMA space that the NIC keeps
* up-to-date, so there's no need to request the data from the
* NIC, we already have it in our memory space.
*/
switch (le16_to_cpu(idev->port_info->status.xcvr.pid)) {
/* Copper */
case IONIC_XCVR_PID_QSFP_100G_CR4:
ethtool_link_ksettings_add_link_mode(ks, supported,
100000baseCR4_Full);
copper_seen++;
break;
case IONIC_XCVR_PID_QSFP_40GBASE_CR4:
ethtool_link_ksettings_add_link_mode(ks, supported,
40000baseCR4_Full);
copper_seen++;
break;
case IONIC_XCVR_PID_SFP_25GBASE_CR_S:
case IONIC_XCVR_PID_SFP_25GBASE_CR_L:
case IONIC_XCVR_PID_SFP_25GBASE_CR_N:
ethtool_link_ksettings_add_link_mode(ks, supported,
25000baseCR_Full);
copper_seen++;