Add support for ethtool selftest for MAC loopback. This includes the sanity check and helps in finding the misconfiguration of HW. Signed-off-by: Raju Rangoju --- Changes since v2: - fix build warnings for xtensa arch Changes since v1: - fix build warnings for s390 arch reported by kernel test robot drivers/net/ethernet/amd/xgbe/Makefile | 2 +- drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c | 7 + drivers/net/ethernet/amd/xgbe/xgbe-selftest.c | 402 ++++++++++++++++++ drivers/net/ethernet/amd/xgbe/xgbe.h | 5 + 4 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 drivers/net/ethernet/amd/xgbe/xgbe-selftest.c diff --git a/drivers/net/ethernet/amd/xgbe/Makefile b/drivers/net/ethernet/amd/xgbe/Makefile index 980e27652237..5992f7fd4d9b 100644 --- a/drivers/net/ethernet/amd/xgbe/Makefile +++ b/drivers/net/ethernet/amd/xgbe/Makefile @@ -5,7 +5,7 @@ amd-xgbe-objs := xgbe-main.o xgbe-drv.o xgbe-dev.o \ xgbe-desc.o xgbe-ethtool.o xgbe-mdio.o \ xgbe-hwtstamp.o xgbe-ptp.o xgbe-pps.o \ xgbe-i2c.o xgbe-phy-v1.o xgbe-phy-v2.o \ - xgbe-platform.o + xgbe-platform.o xgbe-selftest.o amd-xgbe-$(CONFIG_PCI) += xgbe-pci.o amd-xgbe-$(CONFIG_AMD_XGBE_DCB) += xgbe-dcb.o diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c b/drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c index b6e1b67a2d0e..0d19b09497a0 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c +++ b/drivers/net/ethernet/amd/xgbe/xgbe-ethtool.c @@ -85,6 +85,9 @@ static void xgbe_get_strings(struct net_device *netdev, u32 stringset, u8 *data) int i; switch (stringset) { + case ETH_SS_TEST: + xgbe_selftest_get_strings(pdata, data); + break; case ETH_SS_STATS: for (i = 0; i < XGBE_STATS_COUNT; i++) ethtool_puts(&data, xgbe_gstring_stats[i].stat_string); @@ -131,6 +134,9 @@ static int xgbe_get_sset_count(struct net_device *netdev, int stringset) int ret; switch (stringset) { + case ETH_SS_TEST: + ret = xgbe_selftest_get_count(pdata); + break; case ETH_SS_STATS: ret = XGBE_STATS_COUNT + (pdata->tx_ring_count * 2) + @@ -760,6 +766,7 @@ static const struct ethtool_ops xgbe_ethtool_ops = { .set_ringparam = xgbe_set_ringparam, .get_channels = xgbe_get_channels, .set_channels = xgbe_set_channels, + .self_test = xgbe_selftest_run, }; const struct ethtool_ops *xgbe_get_ethtool_ops(void) diff --git a/drivers/net/ethernet/amd/xgbe/xgbe-selftest.c b/drivers/net/ethernet/amd/xgbe/xgbe-selftest.c new file mode 100644 index 000000000000..54a08f5c4ed8 --- /dev/null +++ b/drivers/net/ethernet/amd/xgbe/xgbe-selftest.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause) +/* + * Copyright (c) 2014-2025, Advanced Micro Devices, Inc. + * Copyright (c) 2014, Synopsys, Inc. + * All rights reserved + * + * Author: Raju Rangoju + */ +#include +#include +#include +#include +#include +#include + +#include "xgbe.h" +#include "xgbe-common.h" + +#define XGBE_TEST_PKT_SIZE (sizeof(struct ethhdr) + \ + sizeof(struct iphdr) + \ + sizeof(struct xgbe_hdr)) + +#define XGBE_TEST_PKT_MAGIC 0xdeadbeefdeadfeedULL +#define XGBE_LB_TIMEOUT msecs_to_jiffies(200) + +#define XGBE_LOOPBACK_NONE 0 +#define XGBE_LOOPBACK_MAC 1 + +struct xgbe_hdr { + __be32 version; + __be64 magic; + u8 id; +} __packed; + +struct xgbe_pkt_attrs { + unsigned char *src; + const unsigned char *dst; + u32 ip_src; + u32 ip_dst; + int tcp; + int sport; + int dport; + int timeout; + int size; + int max_size; + u8 id; + u16 queue_mapping; + u64 timestamp; +}; + +struct xgbe_test_data { + struct xgbe_pkt_attrs *packet; + struct packet_type pt; + struct completion comp; + int ok; +}; + +struct xgbe_test { + char name[ETH_GSTRING_LEN]; + int lb; + int (*fn)(struct xgbe_prv_data *pdata); +}; + +static u8 xgbe_test_id; + +static int xgbe_config_mac_loopback(struct xgbe_prv_data *pdata, bool enable) +{ + XGMAC_IOWRITE_BITS(pdata, MAC_RCR, LM, enable ? 1 : 0); + return 0; +} + +static struct sk_buff *xgbe_test_get_skb(struct xgbe_prv_data *pdata, + struct xgbe_pkt_attrs *attr) +{ + struct sk_buff *skb = NULL; + struct udphdr *uh = NULL; + struct tcphdr *th = NULL; + struct xgbe_hdr *hdr; + struct ethhdr *eh; + struct iphdr *ih; + int iplen, size; + + size = attr->size + XGBE_TEST_PKT_SIZE; + + if (attr->tcp) + size += sizeof(struct tcphdr); + else + size += sizeof(struct udphdr); + + if (attr->max_size && attr->max_size > size) + size = attr->max_size; + + skb = netdev_alloc_skb(pdata->netdev, size); + if (!skb) + return NULL; + + prefetchw(skb->data); + + eh = skb_push(skb, ETH_HLEN); + skb_reset_mac_header(skb); + + skb_set_network_header(skb, skb->len); + ih = skb_put(skb, sizeof(*ih)); + + skb_set_transport_header(skb, skb->len); + if (attr->tcp) + th = skb_put(skb, sizeof(*th)); + else + uh = skb_put(skb, sizeof(*uh)); + + eth_zero_addr(eh->h_source); + eth_zero_addr(eh->h_dest); + if (attr->src) + ether_addr_copy(eh->h_source, attr->src); + if (attr->dst) + ether_addr_copy(eh->h_dest, attr->dst); + + eh->h_proto = htons(ETH_P_IP); + + if (attr->tcp) { + th->source = htons(attr->sport); + th->dest = htons(attr->dport); + th->doff = sizeof(struct tcphdr) / 4; + th->check = 0; + } else { + uh->source = htons(attr->sport); + uh->dest = htons(attr->dport); + uh->len = htons(sizeof(*hdr) + sizeof(*uh) + attr->size); + if (attr->max_size) + uh->len = htons(attr->max_size - + (sizeof(*ih) + sizeof(*eh))); + uh->check = 0; + } + + ih->ihl = 5; + ih->ttl = 32; + ih->version = IPVERSION; + if (attr->tcp) + ih->protocol = IPPROTO_TCP; + else + ih->protocol = IPPROTO_UDP; + iplen = sizeof(*ih) + sizeof(*hdr) + attr->size; + if (attr->tcp) + iplen += sizeof(*th); + else + iplen += sizeof(*uh); + + if (attr->max_size) + iplen = attr->max_size - sizeof(*eh); + + ih->tot_len = htons(iplen); + ih->frag_off = 0; + ih->saddr = htonl(attr->ip_src); + ih->daddr = htonl(attr->ip_dst); + ih->tos = 0; + ih->id = 0; + ih->check = 0; + ih->check = ip_fast_csum((unsigned char *)ih, ih->ihl); + + hdr = skb_put(skb, sizeof(*hdr)); + hdr->version = 0; + hdr->magic = cpu_to_be64(XGBE_TEST_PKT_MAGIC); + attr->id = xgbe_test_id; + hdr->id = xgbe_test_id++; + + if (attr->size) + skb_put(skb, attr->size); + if (attr->max_size && attr->max_size > skb->len) + skb_put(skb, attr->max_size - skb->len); + + skb->csum = 0; + skb->ip_summed = CHECKSUM_PARTIAL; + if (attr->tcp) { + th->check = ~tcp_v4_check(skb->len, ih->saddr, ih->daddr, 0); + skb->csum_start = skb_transport_header(skb) - skb->head; + skb->csum_offset = offsetof(struct tcphdr, check); + } else { + uh->check = 0; + uh->check = csum_tcpudp_magic(ih->saddr, ih->daddr, + skb->len, IPPROTO_UDP, + csum_partial(uh, skb->len, 0)); + if (uh->check == 0) + uh->check = CSUM_MANGLED_0; + } + + skb->protocol = htons(ETH_P_IP); + skb->pkt_type = PACKET_HOST; + skb->dev = pdata->netdev; + + if (attr->timestamp) + skb->tstamp = ns_to_ktime(attr->timestamp); + + return skb; +} + +static int xgbe_test_loopback_validate(struct sk_buff *skb, + struct net_device *ndev, + struct packet_type *pt, + struct net_device *orig_ndev) +{ + struct xgbe_test_data *tdata = pt->af_packet_priv; + const unsigned char *dst = tdata->packet->dst; + unsigned char *src = tdata->packet->src; + struct xgbe_hdr *hdr; + struct ethhdr *eh; + struct iphdr *ih; + struct tcphdr *th; + struct udphdr *uh; + + skb = skb_unshare(skb, GFP_ATOMIC); + if (!skb) + goto out; + + if (skb_linearize(skb)) + goto out; + + if (skb_headlen(skb) < (XGBE_TEST_PKT_SIZE - ETH_HLEN)) + goto out; + + eh = (struct ethhdr *)skb_mac_header(skb); + if (dst) { + if (!ether_addr_equal_unaligned(eh->h_dest, dst)) + goto out; + } + if (src) { + if (!ether_addr_equal_unaligned(eh->h_source, src)) + goto out; + } + + ih = ip_hdr(skb); + + if (tdata->packet->tcp) { + if (ih->protocol != IPPROTO_TCP) + goto out; + + th = (struct tcphdr *)((u8 *)ih + 4 * ih->ihl); + if (th->dest != htons(tdata->packet->dport)) + goto out; + + hdr = (struct xgbe_hdr *)((u8 *)th + sizeof(*th)); + } else { + if (ih->protocol != IPPROTO_UDP) + goto out; + + uh = (struct udphdr *)((u8 *)ih + 4 * ih->ihl); + if (uh->dest != htons(tdata->packet->dport)) + goto out; + + hdr = (struct xgbe_hdr *)((u8 *)uh + sizeof(*uh)); + } + + if (hdr->magic != cpu_to_be64(XGBE_TEST_PKT_MAGIC)) + goto out; + if (tdata->packet->id != hdr->id) + goto out; + + tdata->ok = true; + complete(&tdata->comp); +out: + kfree_skb(skb); + return 0; +} + +static int __xgbe_test_loopback(struct xgbe_prv_data *pdata, + struct xgbe_pkt_attrs *attr) +{ + struct xgbe_test_data *tdata; + struct sk_buff *skb = NULL; + int ret = 0; + + tdata = kzalloc(sizeof(*tdata), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + tdata->ok = false; + init_completion(&tdata->comp); + + tdata->pt.type = htons(ETH_P_IP); + tdata->pt.func = xgbe_test_loopback_validate; + tdata->pt.dev = pdata->netdev; + tdata->pt.af_packet_priv = tdata; + tdata->packet = attr; + + dev_add_pack(&tdata->pt); + + skb = xgbe_test_get_skb(pdata, attr); + if (!skb) { + ret = -ENOMEM; + goto cleanup; + } + + ret = dev_direct_xmit(skb, attr->queue_mapping); + if (ret) + goto cleanup; + + if (!attr->timeout) + attr->timeout = XGBE_LB_TIMEOUT; + + wait_for_completion_timeout(&tdata->comp, attr->timeout); + ret = tdata->ok ? 0 : -ETIMEDOUT; + + if (ret) + netdev_err(pdata->netdev, "Response timedout: ret %d\n", ret); +cleanup: + dev_remove_pack(&tdata->pt); + kfree(tdata); + return ret; +} + +static int xgbe_test_mac_loopback(struct xgbe_prv_data *pdata) +{ + struct xgbe_pkt_attrs attr = {}; + + attr.dst = pdata->netdev->dev_addr; + return __xgbe_test_loopback(pdata, &attr); +} + +static const struct xgbe_test xgbe_selftests[] = { + { + .name = "MAC Loopback ", + .lb = XGBE_LOOPBACK_MAC, + .fn = xgbe_test_mac_loopback, + }, +}; + +void xgbe_selftest_run(struct net_device *dev, + struct ethtool_test *etest, u64 *buf) +{ + struct xgbe_prv_data *pdata = netdev_priv(dev); + int count = xgbe_selftest_get_count(pdata); + int i, ret; + + memset(buf, 0, sizeof(*buf) * count); + xgbe_test_id = 0; + + if (etest->flags != ETH_TEST_FL_OFFLINE) { + netdev_err(pdata->netdev, "Only offline tests are supported\n"); + etest->flags |= ETH_TEST_FL_FAILED; + return; + } else if (!netif_carrier_ok(dev)) { + netdev_err(pdata->netdev, + "Invalid link, cannot execute tests\n"); + etest->flags |= ETH_TEST_FL_FAILED; + return; + } + + /* Wait for queues drain */ + msleep(200); + + for (i = 0; i < count; i++) { + ret = 0; + + switch (xgbe_selftests[i].lb) { + case XGBE_LOOPBACK_MAC: + ret = xgbe_config_mac_loopback(pdata, true); + break; + case XGBE_LOOPBACK_NONE: + break; + default: + ret = -EOPNOTSUPP; + break; + } + + /* + * First tests will always be MAC / PHY loopback. + * If any of them is not supported we abort earlier. + */ + if (ret) { + netdev_err(pdata->netdev, "Loopback not supported\n"); + etest->flags |= ETH_TEST_FL_FAILED; + break; + } + + ret = xgbe_selftests[i].fn(pdata); + if (ret && (ret != -EOPNOTSUPP)) + etest->flags |= ETH_TEST_FL_FAILED; + buf[i] = ret; + + switch (xgbe_selftests[i].lb) { + case XGBE_LOOPBACK_MAC: + xgbe_config_mac_loopback(pdata, false); + break; + default: + break; + } + } +} + +void xgbe_selftest_get_strings(struct xgbe_prv_data *pdata, u8 *data) +{ + u8 *p = data; + int i; + + for (i = 0; i < xgbe_selftest_get_count(pdata); i++) + ethtool_puts(&p, xgbe_selftests[i].name); +} + +int xgbe_selftest_get_count(struct xgbe_prv_data *pdata) +{ + return ARRAY_SIZE(xgbe_selftests); +} + diff --git a/drivers/net/ethernet/amd/xgbe/xgbe.h b/drivers/net/ethernet/amd/xgbe/xgbe.h index e8bbb6805901..f4da4d834e0d 100644 --- a/drivers/net/ethernet/amd/xgbe/xgbe.h +++ b/drivers/net/ethernet/amd/xgbe/xgbe.h @@ -1321,6 +1321,11 @@ void xgbe_update_tstamp_time(struct xgbe_prv_data *pdata, unsigned int sec, int xgbe_pps_config(struct xgbe_prv_data *pdata, struct xgbe_pps_config *cfg, int index, bool on); +void xgbe_selftest_run(struct net_device *dev, + struct ethtool_test *etest, u64 *buf); +void xgbe_selftest_get_strings(struct xgbe_prv_data *pdata, u8 *data); +int xgbe_selftest_get_count(struct xgbe_prv_data *pdata); + #ifdef CONFIG_DEBUG_FS void xgbe_debugfs_init(struct xgbe_prv_data *); void xgbe_debugfs_exit(struct xgbe_prv_data *); -- 2.34.1