xref: /freebsd/sys/dev/mlx5/mlx5_core/mlx5_fwdump.c (revision 4d846d26)
1 /*-
2  * Copyright (c) 2018, 2019 Mellanox Technologies, Ltd.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS `AS IS' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25 
26 #include "opt_rss.h"
27 #include "opt_ratelimit.h"
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/param.h>
33 #include <sys/systm.h>
34 #include <sys/conf.h>
35 #include <sys/fcntl.h>
36 #include <dev/mlx5/driver.h>
37 #include <dev/mlx5/device.h>
38 #include <dev/mlx5/port.h>
39 #include <dev/mlx5/mlx5_core/mlx5_core.h>
40 #include <dev/mlx5/mlx5io.h>
41 #include <dev/mlx5/diagnostics.h>
42 
43 static MALLOC_DEFINE(M_MLX5_DUMP, "MLX5DUMP", "MLX5 Firmware dump");
44 
45 static unsigned
46 mlx5_fwdump_getsize(const struct mlx5_crspace_regmap *rege)
47 {
48 	const struct mlx5_crspace_regmap *r;
49 	unsigned sz;
50 
51 	for (sz = 0, r = rege; r->cnt != 0; r++)
52 		sz += r->cnt;
53 	return (sz);
54 }
55 
56 static void
57 mlx5_fwdump_destroy_dd(struct mlx5_core_dev *mdev)
58 {
59 
60 	mtx_assert(&mdev->dump_lock, MA_OWNED);
61 	free(mdev->dump_data, M_MLX5_DUMP);
62 	mdev->dump_data = NULL;
63 }
64 
65 static int mlx5_fw_dump_enable = 1;
66 SYSCTL_INT(_hw_mlx5, OID_AUTO, fw_dump_enable, CTLFLAG_RDTUN | CTLFLAG_NOFETCH,
67     &mlx5_fw_dump_enable, 0,
68     "Enable fw dump setup and op");
69 
70 void
71 mlx5_fwdump_prep(struct mlx5_core_dev *mdev)
72 {
73 	device_t dev;
74 	int error, vsc_addr;
75 	unsigned i, sz;
76 	u32 addr, in, out, next_addr;
77 
78 	mdev->dump_data = NULL;
79 
80 	TUNABLE_INT_FETCH("hw.mlx5.fw_dump_enable", &mlx5_fw_dump_enable);
81 	if (!mlx5_fw_dump_enable) {
82 		mlx5_core_warn(mdev,
83 		    "Firmware dump administratively prohibited\n");
84 		return;
85 	}
86 
87 	DROP_GIANT();
88 
89 	error = mlx5_vsc_find_cap(mdev);
90 	if (error != 0) {
91 		/* Inability to create a firmware dump is not fatal. */
92 		mlx5_core_warn(mdev,
93 		    "Unable to find vendor-specific capability, error %d\n",
94 		    error);
95 		goto pickup_g;
96 	}
97 	error = mlx5_vsc_lock(mdev);
98 	if (error != 0)
99 		goto pickup_g;
100 	error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_SCAN_CRSPACE);
101 	if (error != 0) {
102 		mlx5_core_warn(mdev, "VSC scan space is not supported\n");
103 		goto unlock_vsc;
104 	}
105 	dev = mdev->pdev->dev.bsddev;
106 	vsc_addr = mdev->vsc_addr;
107 	if (vsc_addr == 0) {
108 		mlx5_core_warn(mdev, "Cannot read VSC, no address\n");
109 		goto unlock_vsc;
110 	}
111 
112 	in = 0;
113 	for (sz = 1, addr = 0;;) {
114 		MLX5_VSC_SET(vsc_addr, &in, address, addr);
115 		pci_write_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, in, 4);
116 		error = mlx5_vsc_wait_on_flag(mdev, 1);
117 		if (error != 0) {
118 			mlx5_core_warn(mdev,
119 		    "Failed waiting for read complete flag, error %d addr %#x\n",
120 			    error, addr);
121 			goto unlock_vsc;
122 		}
123 		pci_read_config(dev, vsc_addr + MLX5_VSC_DATA_OFFSET, 4);
124 		out = pci_read_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, 4);
125 		next_addr = MLX5_VSC_GET(vsc_addr, &out, address);
126 		if (next_addr == 0 || next_addr == addr)
127 			break;
128 		if (next_addr != addr + 4)
129 			sz++;
130 		addr = next_addr;
131 	}
132 	if (sz == 1) {
133 		mlx5_core_warn(mdev, "no output from scan space\n");
134 		goto unlock_vsc;
135 	}
136 
137 	/*
138 	 * We add a sentinel element at the end of the array to
139 	 * terminate the read loop in mlx5_fwdump(), so allocate sz + 1.
140 	 */
141 	mdev->dump_rege = malloc((sz + 1) * sizeof(struct mlx5_crspace_regmap),
142 	    M_MLX5_DUMP, M_WAITOK | M_ZERO);
143 
144 	for (i = 0, addr = 0;;) {
145 		mdev->dump_rege[i].cnt++;
146 		MLX5_VSC_SET(vsc_addr, &in, address, addr);
147 		pci_write_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, in, 4);
148 		error = mlx5_vsc_wait_on_flag(mdev, 1);
149 		if (error != 0) {
150 			mlx5_core_warn(mdev,
151 		    "Failed waiting for read complete flag, error %d addr %#x\n",
152 			    error, addr);
153 			free(mdev->dump_rege, M_MLX5_DUMP);
154 			mdev->dump_rege = NULL;
155 			goto unlock_vsc;
156 		}
157 		pci_read_config(dev, vsc_addr + MLX5_VSC_DATA_OFFSET, 4);
158 		out = pci_read_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, 4);
159 		next_addr = MLX5_VSC_GET(vsc_addr, &out, address);
160 		if (next_addr == 0 || next_addr == addr)
161 			break;
162 		if (next_addr != addr + 4) {
163 			if (++i == sz) {
164 				mlx5_core_err(mdev,
165 		    "Inconsistent hw crspace reads (1): sz %u i %u addr %#lx",
166 				    sz, i, (unsigned long)addr);
167 				break;
168 			}
169 			mdev->dump_rege[i].addr = next_addr;
170 		}
171 		addr = next_addr;
172 	}
173 	/* i == sz case already reported by loop above */
174 	if (i + 1 != sz && i != sz) {
175 		mlx5_core_err(mdev,
176 		    "Inconsistent hw crspace reads (2): sz %u i %u addr %#lx",
177 		    sz, i, (unsigned long)addr);
178 	}
179 
180 	mdev->dump_size = mlx5_fwdump_getsize(mdev->dump_rege);
181 	mdev->dump_data = malloc(mdev->dump_size * sizeof(uint32_t),
182 	    M_MLX5_DUMP, M_WAITOK | M_ZERO);
183 	mdev->dump_valid = false;
184 	mdev->dump_copyout = false;
185 
186 unlock_vsc:
187 	mlx5_vsc_unlock(mdev);
188 pickup_g:
189 	PICKUP_GIANT();
190 }
191 
192 int
193 mlx5_fwdump(struct mlx5_core_dev *mdev)
194 {
195 	const struct mlx5_crspace_regmap *r;
196 	uint32_t i, ri;
197 	int error;
198 
199 	mlx5_core_info(mdev, "Issuing FW dump\n");
200 	mtx_lock(&mdev->dump_lock);
201 	if (mdev->dump_data == NULL) {
202 		error = EIO;
203 		goto failed;
204 	}
205 	if (mdev->dump_valid) {
206 		/* only one dump */
207 		mlx5_core_warn(mdev,
208 		    "Only one FW dump can be captured aborting FW dump\n");
209 		error = EEXIST;
210 		goto failed;
211 	}
212 
213 	/* mlx5_vsc already warns, be silent. */
214 	error = mlx5_vsc_lock(mdev);
215 	if (error != 0)
216 		goto failed;
217 	error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_PROTECTED_CRSPACE);
218 	if (error != 0)
219 		goto unlock_vsc;
220 	for (i = 0, r = mdev->dump_rege; r->cnt != 0; r++) {
221 		for (ri = 0; ri < r->cnt; ri++) {
222 			error = mlx5_vsc_read(mdev, r->addr + ri * 4,
223 			    &mdev->dump_data[i]);
224 			if (error != 0)
225 				goto unlock_vsc;
226 			i++;
227 		}
228 	}
229 	mdev->dump_valid = true;
230 unlock_vsc:
231 	mlx5_vsc_unlock(mdev);
232 failed:
233 	mtx_unlock(&mdev->dump_lock);
234 	return (error);
235 }
236 
237 void
238 mlx5_fwdump_clean(struct mlx5_core_dev *mdev)
239 {
240 
241 	mtx_lock(&mdev->dump_lock);
242 	while (mdev->dump_copyout)
243 		msleep(&mdev->dump_copyout, &mdev->dump_lock, 0, "mlx5fwc", 0);
244 	mlx5_fwdump_destroy_dd(mdev);
245 	mtx_unlock(&mdev->dump_lock);
246 	free(mdev->dump_rege, M_MLX5_DUMP);
247 }
248 
249 static int
250 mlx5_fwdump_reset(struct mlx5_core_dev *mdev)
251 {
252 	int error;
253 
254 	error = 0;
255 	mtx_lock(&mdev->dump_lock);
256 	if (mdev->dump_data != NULL) {
257 		while (mdev->dump_copyout) {
258 			msleep(&mdev->dump_copyout, &mdev->dump_lock,
259 			    0, "mlx5fwr", 0);
260 		}
261 		mdev->dump_valid = false;
262 	} else {
263 		error = ENOENT;
264 	}
265 	mtx_unlock(&mdev->dump_lock);
266 	return (error);
267 }
268 
269 static int
270 mlx5_dbsf_to_core(const struct mlx5_tool_addr *devaddr,
271     struct mlx5_core_dev **mdev)
272 {
273 	device_t dev;
274 	struct pci_dev *pdev;
275 
276 	dev = pci_find_dbsf(devaddr->domain, devaddr->bus, devaddr->slot,
277 	    devaddr->func);
278 	if (dev == NULL)
279 		return (ENOENT);
280 	if (device_get_devclass(dev) != mlx5_core_driver.bsdclass)
281 		return (EINVAL);
282 	pdev = device_get_softc(dev);
283 	*mdev = pci_get_drvdata(pdev);
284 	if (*mdev == NULL)
285 		return (ENOENT);
286 	return (0);
287 }
288 
289 static int
290 mlx5_fwdump_copyout(struct mlx5_core_dev *mdev, struct mlx5_fwdump_get *fwg)
291 {
292 	const struct mlx5_crspace_regmap *r;
293 	struct mlx5_fwdump_reg rv, *urv;
294 	uint32_t i, ri;
295 	int error;
296 
297 	mtx_lock(&mdev->dump_lock);
298 	if (mdev->dump_data == NULL) {
299 		mtx_unlock(&mdev->dump_lock);
300 		return (ENOENT);
301 	}
302 	if (fwg->buf == NULL) {
303 		fwg->reg_filled = mdev->dump_size;
304 		mtx_unlock(&mdev->dump_lock);
305 		return (0);
306 	}
307 	if (!mdev->dump_valid) {
308 		mtx_unlock(&mdev->dump_lock);
309 		return (ENOENT);
310 	}
311 	mdev->dump_copyout = true;
312 	mtx_unlock(&mdev->dump_lock);
313 
314 	urv = fwg->buf;
315 	for (i = 0, r = mdev->dump_rege; r->cnt != 0; r++) {
316 		for (ri = 0; ri < r->cnt; ri++) {
317 			if (i >= fwg->reg_cnt)
318 				goto out;
319 			rv.addr = r->addr + ri * 4;
320 			rv.val = mdev->dump_data[i];
321 			error = copyout(&rv, urv, sizeof(rv));
322 			if (error != 0)
323 				return (error);
324 			urv++;
325 			i++;
326 		}
327 	}
328 out:
329 	fwg->reg_filled = i;
330 	mtx_lock(&mdev->dump_lock);
331 	mdev->dump_copyout = false;
332 	wakeup(&mdev->dump_copyout);
333 	mtx_unlock(&mdev->dump_lock);
334 	return (0);
335 }
336 
337 static int
338 mlx5_fw_reset(struct mlx5_core_dev *mdev)
339 {
340 	device_t dev, bus;
341 	int error;
342 
343 	error = -mlx5_set_mfrl_reg(mdev, MLX5_FRL_LEVEL3);
344 	if (error == 0) {
345 		dev = mdev->pdev->dev.bsddev;
346 		bus_topo_lock();
347 		bus = device_get_parent(dev);
348 		error = BUS_RESET_CHILD(device_get_parent(bus), bus,
349 		    DEVF_RESET_DETACH);
350 		bus_topo_unlock();
351 	}
352 	return (error);
353 }
354 
355 static int
356 mlx5_eeprom_copyout(struct mlx5_core_dev *dev, struct mlx5_eeprom_get *eeprom_info)
357 {
358 	struct mlx5_eeprom eeprom;
359 	int error;
360 
361 	eeprom.i2c_addr = MLX5_I2C_ADDR_LOW;
362 	eeprom.device_addr = 0;
363 	eeprom.page_num = MLX5_EEPROM_LOW_PAGE;
364 	eeprom.page_valid = 0;
365 
366 	/* Read three first bytes to get important info */
367 	error = mlx5_get_eeprom_info(dev, &eeprom);
368 	if (error != 0) {
369 		mlx5_core_err(dev,
370 		    "Failed reading EEPROM initial information\n");
371 		return (error);
372 	}
373 	eeprom_info->eeprom_info_page_valid = eeprom.page_valid;
374 	eeprom_info->eeprom_info_out_len = eeprom.len;
375 
376 	if (eeprom_info->eeprom_info_buf == NULL)
377 		return (0);
378 	/*
379 	 * Allocate needed length buffer and additional space for
380 	 * page 0x03
381 	 */
382 	eeprom.data = malloc(eeprom.len + MLX5_EEPROM_PAGE_LENGTH,
383 	    M_MLX5_EEPROM, M_WAITOK | M_ZERO);
384 
385 	/* Read the whole eeprom information */
386 	error = mlx5_get_eeprom(dev, &eeprom);
387 	if (error != 0) {
388 		mlx5_core_err(dev, "Failed reading EEPROM error = %d\n",
389 		    error);
390 		error = 0;
391 		/*
392 		 * Continue printing partial information in case of
393 		 * an error
394 		 */
395 	}
396 	error = copyout(eeprom.data, eeprom_info->eeprom_info_buf,
397 	    eeprom.len);
398 	free(eeprom.data, M_MLX5_EEPROM);
399 
400 	return (error);
401 }
402 
403 static int
404 mlx5_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
405     struct thread *td)
406 {
407 	struct mlx5_core_dev *mdev;
408 	struct mlx5_fwdump_get *fwg;
409 	struct mlx5_tool_addr *devaddr;
410 	struct mlx5_fw_update *fu;
411 	struct firmware fake_fw;
412 	struct mlx5_eeprom_get *eeprom_info;
413 	void *fw_data;
414 	int error;
415 
416 	error = 0;
417 	switch (cmd) {
418 	case MLX5_FWDUMP_GET:
419 		if ((fflag & FREAD) == 0) {
420 			error = EBADF;
421 			break;
422 		}
423 		fwg = (struct mlx5_fwdump_get *)data;
424 		devaddr = &fwg->devaddr;
425 		error = mlx5_dbsf_to_core(devaddr, &mdev);
426 		if (error != 0)
427 			break;
428 		error = mlx5_fwdump_copyout(mdev, fwg);
429 		break;
430 	case MLX5_FWDUMP_RESET:
431 		if ((fflag & FWRITE) == 0) {
432 			error = EBADF;
433 			break;
434 		}
435 		devaddr = (struct mlx5_tool_addr *)data;
436 		error = mlx5_dbsf_to_core(devaddr, &mdev);
437 		if (error == 0)
438 			error = mlx5_fwdump_reset(mdev);
439 		break;
440 	case MLX5_FWDUMP_FORCE:
441 		if ((fflag & FWRITE) == 0) {
442 			error = EBADF;
443 			break;
444 		}
445 		devaddr = (struct mlx5_tool_addr *)data;
446 		error = mlx5_dbsf_to_core(devaddr, &mdev);
447 		if (error != 0)
448 			break;
449 		error = mlx5_fwdump(mdev);
450 		break;
451 	case MLX5_FW_UPDATE:
452 		if ((fflag & FWRITE) == 0) {
453 			error = EBADF;
454 			break;
455 		}
456 		fu = (struct mlx5_fw_update *)data;
457 		if (fu->img_fw_data_len > 10 * 1024 * 1024) {
458 			error = EINVAL;
459 			break;
460 		}
461 		devaddr = &fu->devaddr;
462 		error = mlx5_dbsf_to_core(devaddr, &mdev);
463 		if (error != 0)
464 			break;
465 		fw_data = kmem_malloc(fu->img_fw_data_len, M_WAITOK);
466 		if (fake_fw.data == NULL) {
467 			error = ENOMEM;
468 			break;
469 		}
470 		error = copyin(fu->img_fw_data, fw_data, fu->img_fw_data_len);
471 		if (error == 0) {
472 			bzero(&fake_fw, sizeof(fake_fw));
473 			fake_fw.name = "umlx_fw_up";
474 			fake_fw.datasize = fu->img_fw_data_len;
475 			fake_fw.version = 1;
476 			fake_fw.data = fw_data;
477 			error = -mlx5_firmware_flash(mdev, &fake_fw);
478 		}
479 		kmem_free(fw_data, fu->img_fw_data_len);
480 		break;
481 	case MLX5_FW_RESET:
482 		if ((fflag & FWRITE) == 0) {
483 			error = EBADF;
484 			break;
485 		}
486 		devaddr = (struct mlx5_tool_addr *)data;
487 		error = mlx5_dbsf_to_core(devaddr, &mdev);
488 		if (error != 0)
489 			break;
490 		error = mlx5_fw_reset(mdev);
491 		break;
492 	case MLX5_EEPROM_GET:
493 		if ((fflag & FREAD) == 0) {
494 			error = EBADF;
495 			break;
496 		}
497 		eeprom_info = (struct mlx5_eeprom_get *)data;
498 		devaddr = &eeprom_info->devaddr;
499 		error = mlx5_dbsf_to_core(devaddr, &mdev);
500 		if (error != 0)
501 			break;
502 		error = mlx5_eeprom_copyout(mdev, eeprom_info);
503 		break;
504 	default:
505 		error = ENOTTY;
506 		break;
507 	}
508 	return (error);
509 }
510 
511 static struct cdevsw mlx5_ctl_devsw = {
512 	.d_version =	D_VERSION,
513 	.d_ioctl =	mlx5_ctl_ioctl,
514 };
515 
516 static struct cdev *mlx5_ctl_dev;
517 
518 int
519 mlx5_ctl_init(void)
520 {
521 	struct make_dev_args mda;
522 	int error;
523 
524 	make_dev_args_init(&mda);
525 	mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
526 	mda.mda_devsw = &mlx5_ctl_devsw;
527 	mda.mda_uid = UID_ROOT;
528 	mda.mda_gid = GID_OPERATOR;
529 	mda.mda_mode = 0640;
530 	error = make_dev_s(&mda, &mlx5_ctl_dev, "mlx5ctl");
531 	return (-error);
532 }
533 
534 void
535 mlx5_ctl_fini(void)
536 {
537 
538 	if (mlx5_ctl_dev != NULL)
539 		destroy_dev(mlx5_ctl_dev);
540 
541 }
542