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