1 | /* |
2 | * Copyright (c) 2016, Mellanox Technologies, Ltd. All rights reserved. |
3 | * |
4 | * This software is available to you under a choice of one of two |
5 | * licenses. You may choose to be licensed under the terms of the GNU |
6 | * General Public License (GPL) Version 2, available from the file |
7 | * COPYING in the main directory of this source tree, or the |
8 | * OpenIB.org BSD license below: |
9 | * |
10 | * Redistribution and use in source and binary forms, with or |
11 | * without modification, are permitted provided that the following |
12 | * conditions are met: |
13 | * |
14 | * - Redistributions of source code must retain the above |
15 | * copyright notice, this list of conditions and the following |
16 | * disclaimer. |
17 | * |
18 | * - Redistributions in binary form must reproduce the above |
19 | * copyright notice, this list of conditions and the following |
20 | * disclaimer in the documentation and/or other materials |
21 | * provided with the distribution. |
22 | * |
23 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
24 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
25 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
26 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
27 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
28 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
29 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
30 | * SOFTWARE. |
31 | */ |
32 | |
33 | #include <linux/ip.h> |
34 | #include <linux/udp.h> |
35 | #include <net/udp.h> |
36 | #include "en.h" |
37 | #include "en/port.h" |
38 | #include "eswitch.h" |
39 | |
40 | static int mlx5e_test_health_info(struct mlx5e_priv *priv) |
41 | { |
42 | struct mlx5_core_health *health = &priv->mdev->priv.health; |
43 | |
44 | return health->fatal_error ? 1 : 0; |
45 | } |
46 | |
47 | static int mlx5e_test_link_state(struct mlx5e_priv *priv) |
48 | { |
49 | u8 port_state; |
50 | |
51 | if (!netif_carrier_ok(dev: priv->netdev)) |
52 | return 1; |
53 | |
54 | port_state = mlx5_query_vport_state(mdev: priv->mdev, opmod: MLX5_VPORT_STATE_OP_MOD_VNIC_VPORT, vport: 0); |
55 | return port_state == VPORT_STATE_UP ? 0 : 1; |
56 | } |
57 | |
58 | static int mlx5e_test_link_speed(struct mlx5e_priv *priv) |
59 | { |
60 | u32 speed; |
61 | |
62 | if (!netif_carrier_ok(dev: priv->netdev)) |
63 | return 1; |
64 | |
65 | return mlx5e_port_linkspeed(mdev: priv->mdev, speed: &speed); |
66 | } |
67 | |
68 | struct mlx5ehdr { |
69 | __be32 version; |
70 | __be64 magic; |
71 | }; |
72 | |
73 | #ifdef CONFIG_INET |
74 | /* loopback test */ |
75 | #define MLX5E_TEST_PKT_SIZE (sizeof(struct ethhdr) + sizeof(struct iphdr) +\ |
76 | sizeof(struct udphdr) + sizeof(struct mlx5ehdr)) |
77 | #define MLX5E_TEST_MAGIC 0x5AEED15C001ULL |
78 | |
79 | static struct sk_buff *mlx5e_test_get_udp_skb(struct mlx5e_priv *priv) |
80 | { |
81 | struct sk_buff *skb = NULL; |
82 | struct mlx5ehdr *mlxh; |
83 | struct ethhdr *ethh; |
84 | struct udphdr *udph; |
85 | struct iphdr *iph; |
86 | int iplen; |
87 | |
88 | skb = netdev_alloc_skb(dev: priv->netdev, MLX5E_TEST_PKT_SIZE); |
89 | if (!skb) { |
90 | netdev_err(dev: priv->netdev, format: "\tFailed to alloc loopback skb\n" ); |
91 | return NULL; |
92 | } |
93 | |
94 | net_prefetchw(p: skb->data); |
95 | skb_reserve(skb, NET_IP_ALIGN); |
96 | |
97 | /* Reserve for ethernet and IP header */ |
98 | ethh = skb_push(skb, ETH_HLEN); |
99 | skb_reset_mac_header(skb); |
100 | |
101 | skb_set_network_header(skb, offset: skb->len); |
102 | iph = skb_put(skb, len: sizeof(struct iphdr)); |
103 | |
104 | skb_set_transport_header(skb, offset: skb->len); |
105 | udph = skb_put(skb, len: sizeof(struct udphdr)); |
106 | |
107 | /* Fill ETH header */ |
108 | ether_addr_copy(dst: ethh->h_dest, src: priv->netdev->dev_addr); |
109 | eth_zero_addr(addr: ethh->h_source); |
110 | ethh->h_proto = htons(ETH_P_IP); |
111 | |
112 | /* Fill UDP header */ |
113 | udph->source = htons(9); |
114 | udph->dest = htons(9); /* Discard Protocol */ |
115 | udph->len = htons(sizeof(struct mlx5ehdr) + sizeof(struct udphdr)); |
116 | udph->check = 0; |
117 | |
118 | /* Fill IP header */ |
119 | iph->ihl = 5; |
120 | iph->ttl = 32; |
121 | iph->version = 4; |
122 | iph->protocol = IPPROTO_UDP; |
123 | iplen = sizeof(struct iphdr) + sizeof(struct udphdr) + |
124 | sizeof(struct mlx5ehdr); |
125 | iph->tot_len = htons(iplen); |
126 | iph->frag_off = 0; |
127 | iph->saddr = 0; |
128 | iph->daddr = 0; |
129 | iph->tos = 0; |
130 | iph->id = 0; |
131 | ip_send_check(ip: iph); |
132 | |
133 | /* Fill test header and data */ |
134 | mlxh = skb_put(skb, len: sizeof(*mlxh)); |
135 | mlxh->version = 0; |
136 | mlxh->magic = cpu_to_be64(MLX5E_TEST_MAGIC); |
137 | |
138 | skb->csum = 0; |
139 | skb->ip_summed = CHECKSUM_PARTIAL; |
140 | udp4_hwcsum(skb, src: iph->saddr, dst: iph->daddr); |
141 | |
142 | skb->protocol = htons(ETH_P_IP); |
143 | skb->pkt_type = PACKET_HOST; |
144 | skb->dev = priv->netdev; |
145 | |
146 | return skb; |
147 | } |
148 | |
149 | struct mlx5e_lbt_priv { |
150 | struct packet_type pt; |
151 | struct completion comp; |
152 | bool loopback_ok; |
153 | bool local_lb; |
154 | }; |
155 | |
156 | static int |
157 | mlx5e_test_loopback_validate(struct sk_buff *skb, |
158 | struct net_device *ndev, |
159 | struct packet_type *pt, |
160 | struct net_device *orig_ndev) |
161 | { |
162 | struct mlx5e_lbt_priv *lbtp = pt->af_packet_priv; |
163 | struct mlx5ehdr *mlxh; |
164 | struct ethhdr *ethh; |
165 | struct udphdr *udph; |
166 | struct iphdr *iph; |
167 | |
168 | /* We are only going to peek, no need to clone the SKB */ |
169 | if (MLX5E_TEST_PKT_SIZE - ETH_HLEN > skb_headlen(skb)) |
170 | goto out; |
171 | |
172 | ethh = (struct ethhdr *)skb_mac_header(skb); |
173 | if (!ether_addr_equal(addr1: ethh->h_dest, addr2: orig_ndev->dev_addr)) |
174 | goto out; |
175 | |
176 | iph = ip_hdr(skb); |
177 | if (iph->protocol != IPPROTO_UDP) |
178 | goto out; |
179 | |
180 | /* Don't assume skb_transport_header() was set */ |
181 | udph = (struct udphdr *)((u8 *)iph + 4 * iph->ihl); |
182 | if (udph->dest != htons(9)) |
183 | goto out; |
184 | |
185 | mlxh = (struct mlx5ehdr *)((char *)udph + sizeof(*udph)); |
186 | if (mlxh->magic != cpu_to_be64(MLX5E_TEST_MAGIC)) |
187 | goto out; /* so close ! */ |
188 | |
189 | /* bingo */ |
190 | lbtp->loopback_ok = true; |
191 | complete(&lbtp->comp); |
192 | out: |
193 | kfree_skb(skb); |
194 | return 0; |
195 | } |
196 | |
197 | static int mlx5e_test_loopback_setup(struct mlx5e_priv *priv, |
198 | struct mlx5e_lbt_priv *lbtp) |
199 | { |
200 | int err = 0; |
201 | |
202 | /* Temporarily enable local_lb */ |
203 | err = mlx5_nic_vport_query_local_lb(mdev: priv->mdev, status: &lbtp->local_lb); |
204 | if (err) |
205 | return err; |
206 | |
207 | if (!lbtp->local_lb) { |
208 | err = mlx5_nic_vport_update_local_lb(mdev: priv->mdev, enable: true); |
209 | if (err) |
210 | return err; |
211 | } |
212 | |
213 | err = mlx5e_refresh_tirs(priv, enable_uc_lb: true, enable_mc_lb: false); |
214 | if (err) |
215 | goto out; |
216 | |
217 | lbtp->loopback_ok = false; |
218 | init_completion(x: &lbtp->comp); |
219 | |
220 | lbtp->pt.type = htons(ETH_P_IP); |
221 | lbtp->pt.func = mlx5e_test_loopback_validate; |
222 | lbtp->pt.dev = priv->netdev; |
223 | lbtp->pt.af_packet_priv = lbtp; |
224 | dev_add_pack(pt: &lbtp->pt); |
225 | |
226 | return 0; |
227 | |
228 | out: |
229 | if (!lbtp->local_lb) |
230 | mlx5_nic_vport_update_local_lb(mdev: priv->mdev, enable: false); |
231 | |
232 | return err; |
233 | } |
234 | |
235 | static void mlx5e_test_loopback_cleanup(struct mlx5e_priv *priv, |
236 | struct mlx5e_lbt_priv *lbtp) |
237 | { |
238 | if (!lbtp->local_lb) |
239 | mlx5_nic_vport_update_local_lb(mdev: priv->mdev, enable: false); |
240 | |
241 | dev_remove_pack(pt: &lbtp->pt); |
242 | mlx5e_refresh_tirs(priv, enable_uc_lb: false, enable_mc_lb: false); |
243 | } |
244 | |
245 | static int mlx5e_cond_loopback(struct mlx5e_priv *priv) |
246 | { |
247 | if (is_mdev_switchdev_mode(dev: priv->mdev)) |
248 | return -EOPNOTSUPP; |
249 | |
250 | return 0; |
251 | } |
252 | |
253 | #define MLX5E_LB_VERIFY_TIMEOUT (msecs_to_jiffies(200)) |
254 | static int mlx5e_test_loopback(struct mlx5e_priv *priv) |
255 | { |
256 | struct mlx5e_lbt_priv *lbtp; |
257 | struct sk_buff *skb = NULL; |
258 | int err; |
259 | |
260 | if (!test_bit(MLX5E_STATE_OPENED, &priv->state)) { |
261 | netdev_err(dev: priv->netdev, |
262 | format: "\tCan't perform loopback test while device is down\n" ); |
263 | return -ENODEV; |
264 | } |
265 | |
266 | lbtp = kzalloc(size: sizeof(*lbtp), GFP_KERNEL); |
267 | if (!lbtp) |
268 | return -ENOMEM; |
269 | lbtp->loopback_ok = false; |
270 | |
271 | err = mlx5e_test_loopback_setup(priv, lbtp); |
272 | if (err) |
273 | goto out; |
274 | |
275 | skb = mlx5e_test_get_udp_skb(priv); |
276 | if (!skb) { |
277 | err = -ENOMEM; |
278 | goto cleanup; |
279 | } |
280 | |
281 | skb_set_queue_mapping(skb, queue_mapping: 0); |
282 | err = dev_queue_xmit(skb); |
283 | if (err) { |
284 | netdev_err(dev: priv->netdev, |
285 | format: "\tFailed to xmit loopback packet err(%d)\n" , |
286 | err); |
287 | goto cleanup; |
288 | } |
289 | |
290 | wait_for_completion_timeout(x: &lbtp->comp, MLX5E_LB_VERIFY_TIMEOUT); |
291 | err = !lbtp->loopback_ok; |
292 | |
293 | cleanup: |
294 | mlx5e_test_loopback_cleanup(priv, lbtp); |
295 | out: |
296 | kfree(objp: lbtp); |
297 | return err; |
298 | } |
299 | #endif |
300 | |
301 | typedef int (*mlx5e_st_func)(struct mlx5e_priv *); |
302 | |
303 | struct mlx5e_st { |
304 | char name[ETH_GSTRING_LEN]; |
305 | mlx5e_st_func st_func; |
306 | mlx5e_st_func cond_func; |
307 | }; |
308 | |
309 | static struct mlx5e_st mlx5e_sts[] = { |
310 | { "Link Test" , mlx5e_test_link_state }, |
311 | { "Speed Test" , mlx5e_test_link_speed }, |
312 | { "Health Test" , mlx5e_test_health_info }, |
313 | #ifdef CONFIG_INET |
314 | { "Loopback Test" , mlx5e_test_loopback, mlx5e_cond_loopback }, |
315 | #endif |
316 | }; |
317 | |
318 | #define MLX5E_ST_NUM ARRAY_SIZE(mlx5e_sts) |
319 | |
320 | void mlx5e_self_test(struct net_device *ndev, struct ethtool_test *etest, |
321 | u64 *buf) |
322 | { |
323 | struct mlx5e_priv *priv = netdev_priv(dev: ndev); |
324 | int i, count = 0; |
325 | |
326 | mutex_lock(&priv->state_lock); |
327 | netdev_info(dev: ndev, format: "Self test begin..\n" ); |
328 | |
329 | for (i = 0; i < MLX5E_ST_NUM; i++) { |
330 | struct mlx5e_st st = mlx5e_sts[i]; |
331 | |
332 | if (st.cond_func && st.cond_func(priv)) |
333 | continue; |
334 | netdev_info(dev: ndev, format: "\t[%d] %s start..\n" , i, st.name); |
335 | buf[count] = st.st_func(priv); |
336 | netdev_info(dev: ndev, format: "\t[%d] %s end: result(%lld)\n" , i, st.name, buf[count]); |
337 | count++; |
338 | } |
339 | |
340 | mutex_unlock(lock: &priv->state_lock); |
341 | |
342 | for (i = 0; i < count; i++) { |
343 | if (buf[i]) { |
344 | etest->flags |= ETH_TEST_FL_FAILED; |
345 | break; |
346 | } |
347 | } |
348 | netdev_info(dev: ndev, format: "Self test out: status flags(0x%x)\n" , |
349 | etest->flags); |
350 | } |
351 | |
352 | int mlx5e_self_test_fill_strings(struct mlx5e_priv *priv, u8 *data) |
353 | { |
354 | int i, count = 0; |
355 | |
356 | for (i = 0; i < MLX5E_ST_NUM; i++) { |
357 | struct mlx5e_st st = mlx5e_sts[i]; |
358 | |
359 | if (st.cond_func && st.cond_func(priv)) |
360 | continue; |
361 | if (data) |
362 | strcpy(p: data + count * ETH_GSTRING_LEN, q: st.name); |
363 | count++; |
364 | } |
365 | return count; |
366 | } |
367 | |
368 | int mlx5e_self_test_num(struct mlx5e_priv *priv) |
369 | { |
370 | return mlx5e_self_test_fill_strings(priv, NULL); |
371 | } |
372 | |