// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019 Synopsys, Inc. and/or its affiliates. * aicmac Selftests Support * * Author: Jose Abreu */ #include #include #include #include #include #include #include #include #include #include #include #include #include "aicmac.h" #include "aicmac_gmac_reg.h" struct aicmachdr { __be32 version; __be64 magic; u8 id; } __packed; #define AICMAC_SELFTEST_MAC_LOOPBACK 0 #define AICMAC_TEST_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) + \ sizeof(struct aicmachdr)) #define AICMAC_TEST_PKT_MAGIC 0xdeadcafecafedeadULL #define AICMAC_LB_TIMEOUT msecs_to_jiffies(2000) struct aicmac_packet_attrs { int vlan; int vlan_id_in; int vlan_id_out; unsigned char *src; unsigned char *dst; u32 ip_src; u32 ip_dst; int tcp; int sport; int dport; u32 exp_hash; int dont_wait; int timeout; int size; int max_size; int remove_sa; u8 id; int sarc; u16 queue_mapping; u64 timestamp; }; static u8 aicmac_test_next_id; static struct sk_buff *aicmac_test_get_udp_skb(struct aicmac_priv *priv, struct aicmac_packet_attrs *attr) { struct sk_buff *skb = NULL; struct udphdr *uhdr = NULL; struct tcphdr *thdr = NULL; struct aicmachdr *shdr; struct ethhdr *ehdr; struct iphdr *ihdr; int iplen, size; size = attr->size + AICMAC_TEST_PKT_SIZE; if (attr->vlan) { size += 4; if (attr->vlan > 1) size += 4; } 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(priv->dev, size); if (!skb) return NULL; prefetchw(skb->data); if (attr->vlan > 1) ehdr = skb_push(skb, ETH_HLEN + 8); else if (attr->vlan) ehdr = skb_push(skb, ETH_HLEN + 4); else if (attr->remove_sa) ehdr = skb_push(skb, ETH_HLEN - 6); else ehdr = skb_push(skb, ETH_HLEN); skb_reset_mac_header(skb); skb_set_network_header(skb, skb->len); ihdr = skb_put(skb, sizeof(*ihdr)); skb_set_transport_header(skb, skb->len); if (attr->tcp) thdr = skb_put(skb, sizeof(*thdr)); else uhdr = skb_put(skb, sizeof(*uhdr)); if (!attr->remove_sa) eth_zero_addr(ehdr->h_source); eth_zero_addr(ehdr->h_dest); if (attr->src && !attr->remove_sa) ether_addr_copy(ehdr->h_source, attr->src); if (attr->dst) ether_addr_copy(ehdr->h_dest, attr->dst); if (!attr->remove_sa) { ehdr->h_proto = htons(ETH_P_IP); } else { __be16 *ptr = (__be16 *)ehdr; /* HACK */ ptr[3] = htons(ETH_P_IP); } if (attr->vlan) { __be16 *tag, *proto; if (!attr->remove_sa) { tag = (void *)ehdr + ETH_HLEN; proto = (void *)ehdr + (2 * ETH_ALEN); } else { tag = (void *)ehdr + ETH_HLEN - 6; proto = (void *)ehdr + ETH_ALEN; } proto[0] = htons(ETH_P_8021Q); tag[0] = htons(attr->vlan_id_out); tag[1] = htons(ETH_P_IP); if (attr->vlan > 1) { proto[0] = htons(ETH_P_8021AD); tag[1] = htons(ETH_P_8021Q); tag[2] = htons(attr->vlan_id_in); tag[3] = htons(ETH_P_IP); } } if (attr->tcp) { thdr->source = htons(attr->sport); thdr->dest = htons(attr->dport); thdr->doff = sizeof(struct tcphdr) / 4; thdr->check = 0; } else { uhdr->source = htons(attr->sport); uhdr->dest = htons(attr->dport); uhdr->len = htons(sizeof(*shdr) + sizeof(*uhdr) + attr->size); if (attr->max_size) uhdr->len = htons(attr->max_size - (sizeof(*ihdr) + sizeof(*ehdr))); uhdr->check = 0; } ihdr->ihl = 5; ihdr->ttl = 32; ihdr->version = 4; if (attr->tcp) ihdr->protocol = IPPROTO_TCP; else ihdr->protocol = IPPROTO_UDP; iplen = sizeof(*ihdr) + sizeof(*shdr) + attr->size; if (attr->tcp) iplen += sizeof(*thdr); else iplen += sizeof(*uhdr); if (attr->max_size) iplen = attr->max_size - sizeof(*ehdr); ihdr->tot_len = htons(iplen); ihdr->frag_off = 0; ihdr->saddr = htonl(attr->ip_src); ihdr->daddr = htonl(attr->ip_dst); ihdr->tos = 0; ihdr->id = 0; ip_send_check(ihdr); shdr = skb_put(skb, sizeof(*shdr)); shdr->version = 0; shdr->magic = cpu_to_be64(AICMAC_TEST_PKT_MAGIC); attr->id = aicmac_test_next_id; shdr->id = aicmac_test_next_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) { thdr->check = ~tcp_v4_check(skb->len, ihdr->saddr, ihdr->daddr, 0); skb->csum_start = skb_transport_header(skb) - skb->head; skb->csum_offset = offsetof(struct tcphdr, check); } else { udp4_hwcsum(skb, ihdr->saddr, ihdr->daddr); } skb->protocol = htons(ETH_P_IP); skb->pkt_type = PACKET_HOST; skb->dev = priv->dev; if (attr->timestamp) skb->tstamp = ns_to_ktime(attr->timestamp); return skb; } struct aicmac_test_priv { struct aicmac_packet_attrs *packet; struct packet_type pt; struct completion comp; int double_vlan; int vlan_id; int ok; }; static int aicmac_test_loopback_validate(struct sk_buff *skb, struct net_device *ndev, struct packet_type *pt, struct net_device *orig_ndev) { struct aicmac_test_priv *tpriv = pt->af_packet_priv; unsigned char *src = tpriv->packet->src; unsigned char *dst = tpriv->packet->dst; struct aicmachdr *shdr; struct ethhdr *ehdr; struct udphdr *uhdr; struct tcphdr *thdr; struct iphdr *ihdr; skb = skb_unshare(skb, GFP_ATOMIC); if (!skb) goto out; if (skb_linearize(skb)) goto out; if (skb_headlen(skb) < (AICMAC_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; } if (tpriv->packet->sarc) { if (!ether_addr_equal_unaligned(ehdr->h_source, ehdr->h_dest)) goto out; } else if (src) { if (!ether_addr_equal_unaligned(ehdr->h_source, src)) goto out; } ihdr = ip_hdr(skb); if (tpriv->double_vlan) ihdr = (struct iphdr *)(skb_network_header(skb) + 4); if (tpriv->packet->tcp) { if (ihdr->protocol != IPPROTO_TCP) goto out; thdr = (struct tcphdr *)((u8 *)ihdr + 4 * ihdr->ihl); if (thdr->dest != htons(tpriv->packet->dport)) goto out; shdr = (struct aicmachdr *)((u8 *)thdr + sizeof(*thdr)); } else { if (ihdr->protocol != IPPROTO_UDP) goto out; uhdr = (struct udphdr *)((u8 *)ihdr + 4 * ihdr->ihl); if (uhdr->dest != htons(tpriv->packet->dport)) goto out; shdr = (struct aicmachdr *)((u8 *)uhdr + sizeof(*uhdr)); } if (shdr->magic != cpu_to_be64(AICMAC_TEST_PKT_MAGIC)) goto out; if (tpriv->packet->exp_hash && !skb->hash) goto out; if (tpriv->packet->id != shdr->id) goto out; tpriv->ok = true; complete(&tpriv->comp); out: kfree_skb(skb); return 0; } static int __aicmac_test_loopback(struct aicmac_priv *priv, struct aicmac_packet_attrs *attr) { struct aicmac_test_priv *tpriv; struct sk_buff *skb = NULL; int ret = 0; tpriv = kzalloc(sizeof(*tpriv), GFP_KERNEL); if (!tpriv) return -ENOMEM; tpriv->ok = false; init_completion(&tpriv->comp); tpriv->pt.type = htons(ETH_P_IP); tpriv->pt.func = aicmac_test_loopback_validate; tpriv->pt.dev = priv->dev; tpriv->pt.af_packet_priv = tpriv; tpriv->packet = attr; if (!attr->dont_wait) dev_add_pack(&tpriv->pt); skb = aicmac_test_get_udp_skb(priv, attr); if (!skb) { ret = -ENOMEM; goto cleanup; } ret = dev_direct_xmit(skb, attr->queue_mapping); if (ret) goto cleanup; if (attr->dont_wait) goto cleanup; if (!attr->timeout) attr->timeout = AICMAC_LB_TIMEOUT; wait_for_completion_timeout(&tpriv->comp, attr->timeout); ret = tpriv->ok ? 0 : -ETIMEDOUT; cleanup: if (!attr->dont_wait) dev_remove_pack(&tpriv->pt); kfree(tpriv); return ret; } #if AICMAC_SELFTEST_MAC_LOOPBACK static int aicmac_test_mac_loopback(struct aicmac_priv *priv) { struct aicmac_packet_attrs attr = { }; int ret; aicmac_mac_set_mac_loopback(priv->resource->ioaddr, true); attr.dst = priv->dev->dev_addr; ret = __aicmac_test_loopback(priv, &attr); aicmac_mac_set_mac_loopback(priv->resource->ioaddr, false); return ret; } #endif static int aicmac_test_phy_loopback(struct aicmac_priv *priv) { struct aicmac_packet_attrs attr = { }; int ret; if (!priv->dev->phydev) return -EOPNOTSUPP; ret = phy_loopback(priv->dev->phydev, true); if (ret) return ret; msleep(3000); attr.dst = priv->dev->dev_addr; ret = __aicmac_test_loopback(priv, &attr); phy_loopback(priv->dev->phydev, false); return ret; } #define AICMAC_LOOPBACK_NONE 0 #define AICMAC_LOOPBACK_MAC 1 #define AICMAC_LOOPBACK_PHY 2 static const struct aicmac_test { char name[ETH_GSTRING_LEN]; int lb; int (*fn)(struct aicmac_priv *priv); } aicmac_selftests[] = { #if AICMAC_SELFTEST_MAC_LOOPBACK { .name = "MAC Loopback ", .lb = AICMAC_LOOPBACK_MAC, .fn = aicmac_test_mac_loopback, }, #endif { .name = "PHY Loopback ", .lb = AICMAC_LOOPBACK_PHY, /* Test will handle it */ .fn = aicmac_test_phy_loopback, } }; int aicmac_selftest_get_count(struct aicmac_priv *priv) { return ARRAY_SIZE(aicmac_selftests); } void aicmac_selftest_run(struct net_device *dev, struct ethtool_test *etest, u64 *buf) { struct aicmac_priv *priv = netdev_priv(dev); int count = aicmac_selftest_get_count(priv); int i, ret; memset(buf, 0, sizeof(*buf) * count); aicmac_test_next_id = 0; if (etest->flags != ETH_TEST_FL_OFFLINE) { netdev_err(priv->dev, "Only offline tests are supported\n"); etest->flags |= ETH_TEST_FL_FAILED; return; } else if (!netif_carrier_ok(dev)) { netdev_err(priv->dev, "You need valid Link to execute tests\n"); etest->flags |= ETH_TEST_FL_FAILED; return; } /* Wait for queues drain */ msleep(200); for (i = 0; i < count; i++) { ret = 0; if (aicmac_selftests[i].fn) ret = aicmac_selftests[i].fn(priv); if (ret && (ret != -EOPNOTSUPP)) { etest->flags |= ETH_TEST_FL_FAILED; netdev_err(priv->dev, "%s test Failed\n", aicmac_selftests[i].name); } buf[i] = ret; } }