xref: /linux/drivers/net/ethernet/amd/xgbe/xgbe-selftest.c (revision 84318277d6334c6981ab326d4acc87c6a6ddc9b8)
1 // SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-3-Clause)
2 /*
3  * Copyright (c) 2014-2025, Advanced Micro Devices, Inc.
4  * Copyright (c) 2014, Synopsys, Inc.
5  * All rights reserved
6  *
7  * Author: Raju Rangoju <Raju.Rangoju@amd.com>
8  */
9 #include <linux/crc32.h>
10 #include <linux/ip.h>
11 #include <linux/udp.h>
12 #include <net/tcp.h>
13 #include <net/udp.h>
14 #include <net/checksum.h>
15 #include <net/selftests.h>
16 
17 #include "xgbe.h"
18 #include "xgbe-common.h"
19 
20 #define XGBE_LOOPBACK_NONE	0
21 #define XGBE_LOOPBACK_MAC	1
22 #define XGBE_LOOPBACK_PHY	2
23 
24 struct xgbe_test {
25 	char name[ETH_GSTRING_LEN];
26 	int lb;
27 	int (*fn)(struct xgbe_prv_data *pdata);
28 };
29 
30 static u8 xgbe_test_id;
31 
32 static int xgbe_test_loopback_validate(struct sk_buff *skb,
33 				       struct net_device *ndev,
34 				       struct packet_type *pt,
35 				       struct net_device *orig_ndev)
36 {
37 	struct net_test_priv *tdata = pt->af_packet_priv;
38 	const unsigned char *dst = tdata->packet->dst;
39 	const unsigned char *src = tdata->packet->src;
40 	struct netsfhdr *hdr;
41 	struct ethhdr *eh;
42 	struct tcphdr *th;
43 	struct udphdr *uh;
44 	struct iphdr *ih;
45 	int eat;
46 
47 	skb = skb_unshare(skb, GFP_ATOMIC);
48 	if (!skb)
49 		goto out;
50 
51 	eat = (skb->tail + skb->data_len) - skb->end;
52 	if (eat > 0 && skb_shared(skb)) {
53 		skb = skb_share_check(skb, GFP_ATOMIC);
54 		if (!skb)
55 			goto out;
56 	}
57 
58 	if (skb_linearize(skb))
59 		goto out;
60 
61 	if (skb_headlen(skb) < (NET_TEST_PKT_SIZE - ETH_HLEN))
62 		goto out;
63 
64 	eh = (struct ethhdr *)skb_mac_header(skb);
65 	if (dst) {
66 		if (!ether_addr_equal_unaligned(eh->h_dest, dst))
67 			goto out;
68 	}
69 	if (src) {
70 		if (!ether_addr_equal_unaligned(eh->h_source, src))
71 			goto out;
72 	}
73 
74 	ih = ip_hdr(skb);
75 
76 	if (tdata->packet->tcp) {
77 		if (ih->protocol != IPPROTO_TCP)
78 			goto out;
79 
80 		th = (struct tcphdr *)((u8 *)ih + 4 * ih->ihl);
81 		if (th->dest != htons(tdata->packet->dport))
82 			goto out;
83 
84 		hdr = (struct netsfhdr *)((u8 *)th + sizeof(*th));
85 	} else {
86 		if (ih->protocol != IPPROTO_UDP)
87 			goto out;
88 
89 		uh = (struct udphdr *)((u8 *)ih + 4 * ih->ihl);
90 		if (uh->dest != htons(tdata->packet->dport))
91 			goto out;
92 
93 		hdr = (struct netsfhdr *)((u8 *)uh + sizeof(*uh));
94 	}
95 
96 	if (hdr->magic != cpu_to_be64(NET_TEST_PKT_MAGIC))
97 		goto out;
98 	if (tdata->packet->id != hdr->id)
99 		goto out;
100 
101 	tdata->ok = true;
102 	complete(&tdata->comp);
103 out:
104 	kfree_skb(skb);
105 	return 0;
106 }
107 
108 static int __xgbe_test_loopback(struct xgbe_prv_data *pdata,
109 				struct net_packet_attrs *attr)
110 {
111 	struct net_test_priv *tdata;
112 	struct sk_buff *skb = NULL;
113 	int ret = 0;
114 
115 	tdata = kzalloc(sizeof(*tdata), GFP_KERNEL);
116 	if (!tdata)
117 		return -ENOMEM;
118 
119 	tdata->ok = false;
120 	init_completion(&tdata->comp);
121 
122 	tdata->pt.type = htons(ETH_P_IP);
123 	tdata->pt.func = xgbe_test_loopback_validate;
124 	tdata->pt.dev = pdata->netdev;
125 	tdata->pt.af_packet_priv = tdata;
126 	tdata->packet = attr;
127 
128 	dev_add_pack(&tdata->pt);
129 
130 	skb = net_test_get_skb(pdata->netdev, xgbe_test_id, attr);
131 	if (!skb) {
132 		ret = -ENOMEM;
133 		goto cleanup;
134 	}
135 
136 	xgbe_test_id++;
137 	ret = dev_direct_xmit(skb, attr->queue_mapping);
138 	if (ret)
139 		goto cleanup;
140 
141 	if (!attr->timeout)
142 		attr->timeout = NET_LB_TIMEOUT;
143 
144 	wait_for_completion_timeout(&tdata->comp, attr->timeout);
145 	ret = tdata->ok ? 0 : -ETIMEDOUT;
146 
147 	if (ret)
148 		netdev_err(pdata->netdev, "Response timedout: ret %d\n", ret);
149 cleanup:
150 	dev_remove_pack(&tdata->pt);
151 	kfree(tdata);
152 	return ret;
153 }
154 
155 static int xgbe_test_mac_loopback(struct xgbe_prv_data *pdata)
156 {
157 	struct net_packet_attrs attr = {};
158 
159 	attr.dst = pdata->netdev->dev_addr;
160 	return __xgbe_test_loopback(pdata, &attr);
161 }
162 
163 static int xgbe_test_phy_loopback(struct xgbe_prv_data *pdata)
164 {
165 	struct net_packet_attrs attr = {};
166 	int ret;
167 
168 	if (!pdata->netdev->phydev) {
169 		netdev_err(pdata->netdev, "phydev not found: cannot start PHY loopback test\n");
170 		return -EOPNOTSUPP;
171 	}
172 
173 	ret = phy_loopback(pdata->netdev->phydev, true, 0);
174 	if (ret)
175 		return ret;
176 
177 	attr.dst = pdata->netdev->dev_addr;
178 	ret = __xgbe_test_loopback(pdata, &attr);
179 
180 	phy_loopback(pdata->netdev->phydev, false, 0);
181 	return ret;
182 }
183 
184 static int xgbe_test_sph(struct xgbe_prv_data *pdata)
185 {
186 	struct net_packet_attrs attr = {};
187 	unsigned long cnt_end, cnt_start;
188 	int ret;
189 
190 	cnt_start = pdata->ext_stats.rx_split_header_packets;
191 
192 	if (!pdata->sph) {
193 		netdev_err(pdata->netdev, "Split Header not enabled\n");
194 		return -EOPNOTSUPP;
195 	}
196 
197 	/* UDP test */
198 	attr.dst = pdata->netdev->dev_addr;
199 	attr.tcp = false;
200 
201 	ret = __xgbe_test_loopback(pdata, &attr);
202 	if (ret)
203 		return ret;
204 
205 	cnt_end = pdata->ext_stats.rx_split_header_packets;
206 	if (cnt_end <= cnt_start)
207 		return -EINVAL;
208 
209 	/* TCP test */
210 	cnt_start = cnt_end;
211 
212 	attr.dst = pdata->netdev->dev_addr;
213 	attr.tcp = true;
214 
215 	ret = __xgbe_test_loopback(pdata, &attr);
216 	if (ret)
217 		return ret;
218 
219 	cnt_end = pdata->ext_stats.rx_split_header_packets;
220 	if (cnt_end <= cnt_start)
221 		return -EINVAL;
222 
223 	return 0;
224 }
225 
226 static int xgbe_test_jumbo(struct xgbe_prv_data *pdata)
227 {
228 	struct net_packet_attrs attr = {};
229 	int size = pdata->rx_buf_size;
230 
231 	attr.dst = pdata->netdev->dev_addr;
232 	attr.max_size = size - ETH_FCS_LEN;
233 
234 	return __xgbe_test_loopback(pdata, &attr);
235 }
236 
237 static const struct xgbe_test xgbe_selftests[] = {
238 	{
239 		.name = "MAC Loopback   ",
240 		.lb = XGBE_LOOPBACK_MAC,
241 		.fn = xgbe_test_mac_loopback,
242 	}, {
243 		.name = "PHY Loopback   ",
244 		.lb = XGBE_LOOPBACK_NONE,
245 		.fn = xgbe_test_phy_loopback,
246 	}, {
247 		.name = "Split Header   ",
248 		.lb = XGBE_LOOPBACK_PHY,
249 		.fn = xgbe_test_sph,
250 	}, {
251 		.name = "Jumbo Frame    ",
252 		.lb = XGBE_LOOPBACK_PHY,
253 		.fn = xgbe_test_jumbo,
254 	},
255 };
256 
257 void xgbe_selftest_run(struct net_device *dev,
258 		       struct ethtool_test *etest, u64 *buf)
259 {
260 	struct xgbe_prv_data *pdata = netdev_priv(dev);
261 	int count = xgbe_selftest_get_count(pdata);
262 	int i, ret;
263 
264 	memset(buf, 0, sizeof(*buf) * count);
265 	xgbe_test_id = 0;
266 
267 	if (etest->flags != ETH_TEST_FL_OFFLINE) {
268 		netdev_err(pdata->netdev, "Only offline tests are supported\n");
269 		etest->flags |= ETH_TEST_FL_FAILED;
270 		return;
271 	} else if (!netif_carrier_ok(dev)) {
272 		netdev_err(pdata->netdev,
273 			   "Invalid link, cannot execute tests\n");
274 		etest->flags |= ETH_TEST_FL_FAILED;
275 		return;
276 	}
277 
278 	/* Wait for queues drain */
279 	msleep(200);
280 
281 	for (i = 0; i < count; i++) {
282 		ret = 0;
283 
284 		switch (xgbe_selftests[i].lb) {
285 		case XGBE_LOOPBACK_PHY:
286 			ret = -EOPNOTSUPP;
287 			if (dev->phydev)
288 				ret = phy_loopback(dev->phydev, true, 0);
289 			if (!ret)
290 				break;
291 			fallthrough;
292 		case XGBE_LOOPBACK_MAC:
293 			ret = xgbe_enable_mac_loopback(pdata);
294 			break;
295 		case XGBE_LOOPBACK_NONE:
296 			break;
297 		default:
298 			ret = -EOPNOTSUPP;
299 			break;
300 		}
301 
302 		/*
303 		 * First tests will always be MAC / PHY loopback.
304 		 * If any of them is not supported we abort earlier.
305 		 */
306 		if (ret) {
307 			netdev_err(pdata->netdev, "Loopback not supported\n");
308 			etest->flags |= ETH_TEST_FL_FAILED;
309 			break;
310 		}
311 
312 		ret = xgbe_selftests[i].fn(pdata);
313 		if (ret && (ret != -EOPNOTSUPP))
314 			etest->flags |= ETH_TEST_FL_FAILED;
315 		buf[i] = ret;
316 
317 		switch (xgbe_selftests[i].lb) {
318 		case XGBE_LOOPBACK_PHY:
319 			ret = -EOPNOTSUPP;
320 			if (dev->phydev)
321 				ret = phy_loopback(dev->phydev, false, 0);
322 			if (!ret)
323 				break;
324 			fallthrough;
325 		case XGBE_LOOPBACK_MAC:
326 			xgbe_disable_mac_loopback(pdata);
327 			break;
328 		default:
329 			break;
330 		}
331 	}
332 }
333 
334 void xgbe_selftest_get_strings(struct xgbe_prv_data *pdata, u8 *data)
335 {
336 	u8 *p = data;
337 	int i;
338 
339 	for (i = 0; i < xgbe_selftest_get_count(pdata); i++)
340 		ethtool_puts(&p, xgbe_selftests[i].name);
341 }
342 
343 int xgbe_selftest_get_count(struct xgbe_prv_data *pdata)
344 {
345 	return ARRAY_SIZE(xgbe_selftests);
346 }
347