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