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