From 0d329bb26019de8480eca77ee2623694c049dbc0 Mon Sep 17 00:00:00 2001 From: Bhadram Varka Date: Tue, 2 Jun 2020 19:32:22 +0530 Subject: [PATCH] nvethernet: Support for Ethernet selftests Adds selftest support for - 1) MAC loopback 2) PHY loopback 3) MMC Counters Bug 200622869 Change-Id: Ib476afa342c283b53d5ab74cc50a1458183ab16d Signed-off-by: Bhadram Varka Reviewed-on: https://git-master.nvidia.com/r/c/linux-nvidia/+/2354887 Tested-by: mobile promotions Reviewed-by: Rakesh Goyal Reviewed-by: Srinivas Ramachandran Reviewed-by: Ashutosh Jha Reviewed-by: mobile promotions GVS: Gerrit_Virtual_Submit --- .../net/ethernet/nvidia/nvethernet/Kconfig | 7 + .../net/ethernet/nvidia/nvethernet/Makefile | 2 + .../ethernet/nvidia/nvethernet/ether_linux.h | 20 + .../net/ethernet/nvidia/nvethernet/ethtool.c | 5 + .../ethernet/nvidia/nvethernet/selftests.c | 467 ++++++++++++++++++ 5 files changed, 501 insertions(+) create mode 100644 drivers/net/ethernet/nvidia/nvethernet/selftests.c diff --git a/drivers/net/ethernet/nvidia/nvethernet/Kconfig b/drivers/net/ethernet/nvidia/nvethernet/Kconfig index d8c1af9e..7aec80fd 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/Kconfig +++ b/drivers/net/ethernet/nvidia/nvethernet/Kconfig @@ -10,3 +10,10 @@ config NVETHERNET To compile this driver as a module, choose M here: the module will be called nvethernet. +config NVETHERNET_SELFTESTS + bool "Support for NVETHERNET Selftests" + depends on NVETHERNET + default n + ---help--- + This adds support for NVETHERNET Selftests using ethtool. Enable this + feature if you are facing problems with your HW. diff --git a/drivers/net/ethernet/nvidia/nvethernet/Makefile b/drivers/net/ethernet/nvidia/nvethernet/Makefile index 96ff42cb..e6876e46 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/Makefile +++ b/drivers/net/ethernet/nvidia/nvethernet/Makefile @@ -33,3 +33,5 @@ nvethernet-objs:= ether_linux.o \ $(OSI_CORE)/eqos_core.o \ $(OSI_CORE)/eqos_mmc.o \ $(OSI_DMA)/eqos_dma.o + +nvethernet-$(CONFIG_NVETHERNET_SELFTESTS) += selftests.o diff --git a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h index d8abe2d7..8b1f8df0 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h +++ b/drivers/net/ethernet/nvidia/nvethernet/ether_linux.h @@ -457,4 +457,24 @@ int ether_handle_hwtstamp_ioctl(struct ether_priv_data *pdata, int ether_handle_priv_ts_ioctl(struct ether_priv_data *pdata, struct ifreq *ifr); int ether_conf_eee(struct ether_priv_data *pdata, unsigned int tx_lpi_enable); + +#if IS_ENABLED(CONFIG_NVETHERNET_SELFTESTS) +void ether_selftest_run(struct net_device *dev, + struct ethtool_test *etest, u64 *buf); +void ether_selftest_get_strings(struct ether_priv_data *pdata, u8 *data); +int ether_selftest_get_count(struct ether_priv_data *pdata); +#else +static inline void ether_selftest_run(struct net_device *dev, + struct ethtool_test *etest, u64 *buf) +{ +} +static inline void ether_selftest_get_strings(struct ether_priv_data *pdata, + u8 *data) +{ +} +static inline int ether_selftest_get_count(struct ether_priv_data *pdata) +{ + return -EOPNOTSUPP; +} +#endif /* CONFIG_NVETHERNET_SELFTESTS */ #endif /* ETHER_LINUX_H */ diff --git a/drivers/net/ethernet/nvidia/nvethernet/ethtool.c b/drivers/net/ethernet/nvidia/nvethernet/ethtool.c index 3066d53c..c55f4a69 100644 --- a/drivers/net/ethernet/nvidia/nvethernet/ethtool.c +++ b/drivers/net/ethernet/nvidia/nvethernet/ethtool.c @@ -402,6 +402,8 @@ static int ether_get_sset_count(struct net_device *dev, int sset) } else { len += ETHER_PKT_ERR_STAT_LEN; } + } else if (sset == ETH_SS_TEST) { + len = ether_selftest_get_count(pdata); } else { len = -EOPNOTSUPP; } @@ -464,6 +466,8 @@ static void ether_get_strings(struct net_device *dev, u32 stringset, u8 *data) p += ETH_GSTRING_LEN; } } + } else if (stringset == (u32)ETH_SS_TEST) { + ether_selftest_get_strings(pdata, p); } else { dev_err(pdata->dev, "%s() Unsupported stringset\n", __func__); } @@ -1109,6 +1113,7 @@ static const struct ethtool_ops ether_ethtool_ops = { .set_wol = ether_set_wol, .get_eee = ether_get_eee, .set_eee = ether_set_eee, + .self_test = ether_selftest_run, }; void ether_set_ethtool_ops(struct net_device *ndev) diff --git a/drivers/net/ethernet/nvidia/nvethernet/selftests.c b/drivers/net/ethernet/nvidia/nvethernet/selftests.c new file mode 100644 index 00000000..b3178187 --- /dev/null +++ b/drivers/net/ethernet/nvidia/nvethernet/selftests.c @@ -0,0 +1,467 @@ +#include "ether_linux.h" +#include + +/** + * @brief Ethernet packet context for loopback packet + */ +struct ether_packet_ctxt { + /** Destination MAC address in Ethernet header */ + unsigned char *dst; +}; + +/** + * @brief Ethernet selftests private data + */ +struct ether_test_priv_data { + /** Ethernet selftest packet context */ + struct ether_packet_ctxt *ctxt; + /** Ethernet packet type to handover the packet to Rx handler */ + struct packet_type pt; + /** Packet reception completion indication */ + struct completion comp; + /** Indication for loopback packet reception process completed */ + bool completed; +}; + + +/** + * @brief Ethernet packet test header + */ +struct ether_testhdr { + /** Ethernet selftest data */ + __be64 magic; +}; + +/** + * @addtogroup Ethernet selftest helper macros + * + * @brief Helper macros for Ethernet selftests + * @{ + */ +#define ETHER_TEST_PKT_MAGIC 0xdeadcafecafedeadULL +#define ETHER_TEST_PKT_SIZE (sizeof(struct ethhdr) +\ + sizeof(struct iphdr) +\ + sizeof(struct udphdr) +\ + sizeof(struct ether_testhdr)) +/* UDP discard protocol port */ +#define ETHER_UDP_TEST_PORT 9 +#define ETHER_IP_IHL 5 +#define ETHER_IP_TTL 32 +/** @} */ + +/** + * @brief ether_test_get_udp_skb - Fill socket buffer with UDP packet + * + * Algorithm: Fills socket buffer with UDP packet. + * + * @param[in] pdata: Ethernet OSD private data + * @param[in] ctxt: Ethernet packet context + * + * @retval skb pointer on success + * @retval NULL on failure. + */ +static struct sk_buff *ether_test_get_udp_skb(struct ether_priv_data *pdata, + struct ether_packet_ctxt *ctxt) +{ + struct sk_buff *skb = NULL; + struct ether_testhdr *testhdr; + struct ethhdr *ethh; + struct udphdr *udph; + struct iphdr *iph; + int iplen; + + skb = netdev_alloc_skb(pdata->ndev, ETHER_TEST_PKT_SIZE); + if (!skb) { + netdev_err(pdata->ndev, "Failed to allocate loopback skb\n"); + return NULL; + } + + skb_reserve(skb, NET_IP_ALIGN); + + /* Reserve for ethernet and IP header */ + ethh = skb_push(skb, ETH_HLEN); + skb_reset_mac_header(skb); + + skb_set_network_header(skb, skb->len); + iph = skb_put(skb, sizeof(struct iphdr)); + + skb_set_transport_header(skb, skb->len); + udph = skb_put(skb, sizeof(struct udphdr)); + + /* Fill ETH header */ + ether_addr_copy(ethh->h_dest, ctxt->dst); + eth_zero_addr(ethh->h_source); + ethh->h_proto = htons(ETH_P_IP); + + /* Fill UDP header */ + udph->source = htons(ETHER_UDP_TEST_PORT); + udph->dest = htons(ETHER_UDP_TEST_PORT); /* Discard Protocol */ + udph->len = htons(sizeof(struct ether_testhdr) + sizeof(struct udphdr)); + udph->check = OSI_NONE; + + /* Fill IP header */ + iph->ihl = ETHER_IP_IHL; + iph->ttl = ETHER_IP_TTL; + iph->version = IPVERSION; + iph->protocol = IPPROTO_UDP; + iplen = sizeof(struct iphdr) + sizeof(struct udphdr) + + sizeof(struct ether_testhdr); + iph->tot_len = htons(iplen); + iph->frag_off = OSI_NONE; + iph->saddr = OSI_NONE; + iph->daddr = OSI_NONE; + iph->tos = OSI_NONE; + iph->id = OSI_NONE; + ip_send_check(iph); + + /* Fill test header and data */ + testhdr = skb_put(skb, sizeof(*testhdr)); + testhdr->magic = cpu_to_be64(ETHER_TEST_PKT_MAGIC); + + skb->csum = OSI_NONE; + skb->ip_summed = CHECKSUM_PARTIAL; + udp4_hwcsum(skb, iph->saddr, iph->daddr); + skb->protocol = htons(ETH_P_IP); + skb->pkt_type = PACKET_HOST; + skb->dev = pdata->ndev; + + return skb; +} + +/** + * @brief ether_test_loopback_validate - Loopback packet validation + * + * Algorithm: Validated received loopback packet with the packet sent. + * + * @param[in] skb: socket buffer pointer + * @param[in] ndev: Network device pointer + * @param[in] pt: Packet type for ethernet received packet + * @param[in] orig_dev: Original network device pointer + * + * @retval 0 always + */ +static int ether_test_loopback_validate(struct sk_buff *skb, + struct net_device *ndev, + struct packet_type *pt, + struct net_device *orig_ndev) +{ + struct ether_test_priv_data *tpdata = pt->af_packet_priv; + unsigned char *dst = tpdata->ctxt->dst; + struct ether_testhdr *thdr; + struct ethhdr *ehdr; + struct udphdr *uhdr; + struct iphdr *ihdr; + + skb = skb_unshare(skb, GFP_ATOMIC); + if (!skb) + goto out; + + if (skb_linearize(skb)) + goto out; + if (skb_headlen(skb) < (ETHER_TEST_PKT_SIZE - ETH_HLEN)) + goto out; + + ehdr = (struct ethhdr *)skb_mac_header(skb); + if (dst) { + if (!ether_addr_equal_unaligned(ehdr->h_dest, dst)) + goto out; + } + + ihdr = ip_hdr(skb); + if (ihdr->protocol != IPPROTO_UDP) + goto out; + + uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl); + if (uhdr->dest != htons(ETHER_UDP_TEST_PORT)) + goto out; + + thdr = (struct ether_testhdr *)((u8 *)uhdr + sizeof(*uhdr)); + + if (thdr->magic != cpu_to_be64(ETHER_TEST_PKT_MAGIC)) + goto out; + + tpdata->completed = true; + complete(&tpdata->comp); +out: + kfree_skb(skb); + return 0; +} + +/** + * @brief ether_test_loopback - Ethernet selftest for loopback + * + * Algorithm: + * 1) It registers Rx handler with network type for specifc packet type. + * 2) Gets a SKB with UDP/Ethernet headers updated + * 3) Transmits packet with dev_queue_xmit(). + * + * @param[in] pdata: Ethernet OSD private data + * @param[in] ctxt: Ethernet packet context + * + * @retval zero on success. + * @retval negative value on failure. + */ +static int ether_test_loopback(struct ether_priv_data *pdata, + struct ether_packet_ctxt *ctxt) +{ + struct ether_test_priv_data *tpdata; + struct sk_buff *skb = NULL; + int ret = 0; + + tpdata = kzalloc(sizeof(*tpdata), GFP_KERNEL); + if (!tpdata) + return -ENOMEM; + + tpdata->completed = false; + init_completion(&tpdata->comp); + + tpdata->pt.type = htons(ETH_P_IP); + tpdata->pt.func = ether_test_loopback_validate; + tpdata->pt.dev = pdata->ndev; + tpdata->pt.af_packet_priv = tpdata; + tpdata->ctxt = ctxt; + dev_add_pack(&tpdata->pt); + + skb = ether_test_get_udp_skb(pdata, ctxt); + if (!skb) { + ret = -ENOMEM; + goto cleanup; + } + + skb_set_queue_mapping(skb, 0); + ret = dev_queue_xmit(skb); + if (ret) + goto cleanup; + + wait_for_completion_timeout(&tpdata->comp, msecs_to_jiffies(200)); + ret = !tpdata->completed; +cleanup: + dev_remove_pack(&tpdata->pt); + kfree(tpdata); + return ret; +} + +/** + * @brief ether_test_mac_loopback - Ethernet selftest for MAC loopback + * + * @param[in] pdata: Ethernet OSD private data + * + * @retval zero on success + * @retval negative value on failure. + */ +static int ether_test_mac_loopback(struct ether_priv_data *pdata) +{ + struct ether_packet_ctxt ctxt = { }; + + ctxt.dst = pdata->ndev->dev_addr; + return ether_test_loopback(pdata, &ctxt); +} + +/** + * @brief ether_test_phy_loopback - Ethernet selftest for PHY loopback + * + * @param[in] pdata: Ethernet OSD private data + * + * @retval zero on success + * @retval negative value on failure. + */ +static int ether_test_phy_loopback(struct ether_priv_data *pdata) +{ + struct ether_packet_ctxt ctxt = { }; + int ret = 0; + + if (!pdata->phydev) + return -ENODEV; + + ret = phy_loopback(pdata->phydev, true); + if (ret != 0 && ret != -EBUSY) + return ret; + + ctxt.dst = pdata->ndev->dev_addr; + + return ether_test_loopback(pdata, &ctxt); +} + +/** + * @brief ether_test_mmc_counters - Ethernet selftest for MMC Counters + * + * @param[in] pdata: Ethernet OSD private data + * + * @retval zero on success + * @retval negative value on failure. + */ +static int ether_test_mmc_counters(struct ether_priv_data *pdata) +{ + struct osi_core_priv_data *osi_core = pdata->osi_core; + unsigned int mmc_tx_framecount_g = 0; + unsigned int mmc_rx_framecount_gb = 0; + unsigned int mmc_rx_ipv4_gd = 0; + unsigned int mmc_rx_udp_gd = 0; + int ret = 0; + + ret = osi_read_mmc(osi_core); + if (ret < 0) + return ret; + + mmc_tx_framecount_g = osi_core->mmc.mmc_tx_framecount_g; + mmc_rx_framecount_gb = osi_core->mmc.mmc_rx_framecount_gb; + mmc_rx_ipv4_gd = osi_core->mmc.mmc_rx_ipv4_gd; + mmc_rx_udp_gd = osi_core->mmc.mmc_rx_udp_gd; + + ret = ether_test_mac_loopback(pdata); + if (ret < 0) + return ret; + + ret = osi_read_mmc(osi_core); + if (ret < 0) + return ret; + + if (osi_core->mmc.mmc_tx_framecount_g <= mmc_tx_framecount_g) + return -1; + if (osi_core->mmc.mmc_rx_framecount_gb <= mmc_rx_framecount_gb) + return -1; + if (osi_core->mmc.mmc_rx_ipv4_gd <= mmc_rx_ipv4_gd) + return -1; + if (osi_core->mmc.mmc_rx_udp_gd <= mmc_rx_udp_gd) + return -1; + + return 0; +} + +#define ETHER_LOOPBACK_NONE 0 +#define ETHER_LOOPBACK_MAC 1 +#define ETHER_LOOPBACK_PHY 2 + +static const struct ether_test { + char name[ETH_GSTRING_LEN]; + int lb; + int (*fn)(struct ether_priv_data *pdata); +} ether_selftests[] = { + { + .name = "MAC Loopback ", + .lb = ETHER_LOOPBACK_MAC, + .fn = ether_test_mac_loopback, + }, { + .name = "PHY Loopback ", + .lb = ETHER_LOOPBACK_PHY, + .fn = ether_test_phy_loopback, + }, { + .name = "MMC Counters ", + .lb = ETHER_LOOPBACK_MAC, + .fn = ether_test_mmc_counters, + }, +}; + +/** + * @brief ether_selftest_run - Ethernet selftests. + * + * @param[in] dev: Network device pointer. + * @param[in] etest: Ethernet ethtool test pointer. + * @param[in] buf: Buffer pointer to hold test status. + * + * @retval zero on success. + * @retval negative value on failure. + */ +void ether_selftest_run(struct net_device *dev, + struct ethtool_test *etest, u64 *buf) +{ + struct ether_priv_data *pdata = netdev_priv(dev); + int count = ether_selftest_get_count(pdata); + int carrier = netif_carrier_ok(dev); + int i, ret; + + if (!netif_running(dev)) { + netdev_err(dev, "%s(): Interface is not up\n", __func__); + return; + } + + memset(buf, 0, sizeof(*buf) * count); + + netif_carrier_off(dev); + + for (i = 0; i < count; i++) { + ret = 0; + + switch (ether_selftests[i].lb) { + case ETHER_LOOPBACK_PHY: + ret = -EOPNOTSUPP; + if (dev->phydev) + ret = phy_loopback(dev->phydev, true); + if (!ret) + break; + /* Fallthrough */ + case ETHER_LOOPBACK_MAC: + if (pdata->osi_core) + ret = osi_config_mac_loopback(pdata->osi_core, + OSI_ENABLE); + if (!ret) + break; + default: + ret = -EOPNOTSUPP; + break; + } + + if (ret) { + netdev_err(dev, "Loopback is not supported\n"); + etest->flags |= ETH_TEST_FL_FAILED; + break; + } + + ret = ether_selftests[i].fn(pdata); + if (ret && (ret != -EOPNOTSUPP)) + etest->flags |= ETH_TEST_FL_FAILED; + buf[i] = ret; + + switch (ether_selftests[i].lb) { + case ETHER_LOOPBACK_PHY: + ret = -EOPNOTSUPP; + if (dev->phydev) + ret = phy_loopback(dev->phydev, false); + if (!ret) + break; + /* Fallthrough */ + case ETHER_LOOPBACK_MAC: + if (pdata->osi_core) + ret = osi_config_mac_loopback(pdata->osi_core, + OSI_DISABLE); + if (!ret) + break; + default: + break; + } + } + + /* Restart everything */ + if (carrier) + netif_carrier_on(dev); +} + +/** + * @brief ether_selftest_get_strings - Getting strings for selftests + * + * @param[in] pdata: Ethernet OSD private data + * @param[in] data: Data pointer to store strings for selftest names + */ +void ether_selftest_get_strings(struct ether_priv_data *pdata, u8 *data) +{ + u8 *p = data; + int i; + + for (i = 0; i < ether_selftest_get_count(pdata); i++) { + scnprintf(p, ETH_GSTRING_LEN, "%2d. %s", i + 1, + ether_selftests[i].name); + p += ETH_GSTRING_LEN; + } +} + +/** + * @brief ether_selftest_get_count - Count for Ethernet selftests + * + * @param[in] pdata: Ethernet OSD private data + * + * @retval Number of Ethernet selftests + */ +int ether_selftest_get_count(struct ether_priv_data *pdata) +{ + return ARRAY_SIZE(ether_selftests); +}