mlx5_fwdump.c revision 353218
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 353218 2019-10-07 09:15:47Z 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/mlx5_core/mlx5_core.h>
36#include <dev/mlx5/mlx5io.h>
37
38static MALLOC_DEFINE(M_MLX5_DUMP, "MLX5DUMP", "MLX5 Firmware dump");
39
40static unsigned
41mlx5_fwdump_getsize(const struct mlx5_crspace_regmap *rege)
42{
43	const struct mlx5_crspace_regmap *r;
44	unsigned sz;
45
46	for (sz = 0, r = rege; r->cnt != 0; r++)
47		sz += r->cnt;
48	return (sz);
49}
50
51static void
52mlx5_fwdump_destroy_dd(struct mlx5_core_dev *mdev)
53{
54
55	mtx_assert(&mdev->dump_lock, MA_OWNED);
56	free(mdev->dump_data, M_MLX5_DUMP);
57	mdev->dump_data = NULL;
58}
59
60void
61mlx5_fwdump_prep(struct mlx5_core_dev *mdev)
62{
63	device_t dev;
64	int error, vsc_addr;
65	unsigned i, sz;
66	u32 addr, in, out, next_addr;
67
68	mdev->dump_data = NULL;
69	error = mlx5_vsc_find_cap(mdev);
70	if (error != 0) {
71		/* Inability to create a firmware dump is not fatal. */
72		device_printf((&mdev->pdev->dev)->bsddev, "WARN: "
73		    "mlx5_fwdump_prep failed %d\n", error);
74		return;
75	}
76	error = mlx5_vsc_lock(mdev);
77	if (error != 0)
78		return;
79	error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_SCAN_CRSPACE);
80	if (error != 0) {
81		mlx5_core_warn(mdev, "VSC scan space is not supported\n");
82		goto unlock_vsc;
83	}
84	dev = mdev->pdev->dev.bsddev;
85	vsc_addr = mdev->vsc_addr;
86	if (vsc_addr == 0) {
87		mlx5_core_warn(mdev, "Cannot read vsc, no address\n");
88		goto unlock_vsc;
89	}
90
91	in = 0;
92	for (sz = 1, addr = 0;;) {
93		MLX5_VSC_SET(vsc_addr, &in, address, addr);
94		pci_write_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, in, 4);
95		error = mlx5_vsc_wait_on_flag(mdev, 1);
96		if (error != 0) {
97			mlx5_core_warn(mdev,
98		    "Failed waiting for read complete flag, error %d\n", error);
99			goto unlock_vsc;
100		}
101		pci_read_config(dev, vsc_addr + MLX5_VSC_DATA_OFFSET, 4);
102		out = pci_read_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, 4);
103		next_addr = MLX5_VSC_GET(vsc_addr, &out, address);
104		if (next_addr == 0 || next_addr == addr)
105			break;
106		if (next_addr != addr + 4)
107			sz++;
108		addr = next_addr;
109	}
110	mdev->dump_rege = malloc(sz * sizeof(struct mlx5_crspace_regmap),
111	    M_MLX5_DUMP, M_WAITOK | M_ZERO);
112
113	for (i = 0, addr = 0;;) {
114		MPASS(i < sz);
115		mdev->dump_rege[i].cnt++;
116		MLX5_VSC_SET(vsc_addr, &in, address, addr);
117		pci_write_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, in, 4);
118		error = mlx5_vsc_wait_on_flag(mdev, 1);
119		if (error != 0) {
120			mlx5_core_warn(mdev,
121		    "Failed waiting for read complete flag, error %d\n", error);
122			free(mdev->dump_rege, M_MLX5_DUMP);
123			mdev->dump_rege = NULL;
124			goto unlock_vsc;
125		}
126		pci_read_config(dev, vsc_addr + MLX5_VSC_DATA_OFFSET, 4);
127		out = pci_read_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, 4);
128		next_addr = MLX5_VSC_GET(vsc_addr, &out, address);
129		if (next_addr == 0 || next_addr == addr)
130			break;
131		if (next_addr != addr + 4)
132			mdev->dump_rege[++i].addr = next_addr;
133		addr = next_addr;
134	}
135	KASSERT(i + 1 == sz,
136	    ("inconsistent hw crspace reads: sz %u i %u addr %#lx",
137	    sz, i, (unsigned long)addr));
138
139	mdev->dump_size = mlx5_fwdump_getsize(mdev->dump_rege);
140	mdev->dump_data = malloc(mdev->dump_size * sizeof(uint32_t),
141	    M_MLX5_DUMP, M_WAITOK | M_ZERO);
142	mdev->dump_valid = false;
143	mdev->dump_copyout = false;
144
145unlock_vsc:
146	mlx5_vsc_unlock(mdev);
147}
148
149void
150mlx5_fwdump(struct mlx5_core_dev *mdev)
151{
152	const struct mlx5_crspace_regmap *r;
153	uint32_t i, ri;
154	int error;
155
156	dev_info(&mdev->pdev->dev, "Issuing FW dump\n");
157	mtx_lock(&mdev->dump_lock);
158	if (mdev->dump_data == NULL)
159		goto failed;
160	if (mdev->dump_valid) {
161		/* only one dump */
162		dev_warn(&mdev->pdev->dev,
163		    "Only one FW dump can be captured aborting FW dump\n");
164		goto failed;
165	}
166
167	/* mlx5_vsc already warns, be silent. */
168	error = mlx5_vsc_lock(mdev);
169	if (error != 0)
170		goto failed;
171	error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_PROTECTED_CRSPACE);
172	if (error != 0)
173		goto unlock_vsc;
174	for (i = 0, r = mdev->dump_rege; r->cnt != 0; r++) {
175		for (ri = 0; ri < r->cnt; ri++) {
176			error = mlx5_vsc_read(mdev, r->addr + ri * 4,
177			    &mdev->dump_data[i]);
178			if (error != 0)
179				goto unlock_vsc;
180			i++;
181		}
182	}
183	mdev->dump_valid = true;
184unlock_vsc:
185	mlx5_vsc_unlock(mdev);
186failed:
187	mtx_unlock(&mdev->dump_lock);
188}
189
190void
191mlx5_fwdump_clean(struct mlx5_core_dev *mdev)
192{
193
194	mtx_lock(&mdev->dump_lock);
195	while (mdev->dump_copyout)
196		msleep(&mdev->dump_copyout, &mdev->dump_lock, 0, "mlx5fwc", 0);
197	mlx5_fwdump_destroy_dd(mdev);
198	mtx_unlock(&mdev->dump_lock);
199	free(mdev->dump_rege, M_MLX5_DUMP);
200}
201
202static int
203mlx5_fwdump_reset(struct mlx5_core_dev *mdev)
204{
205	int error;
206
207	error = 0;
208	mtx_lock(&mdev->dump_lock);
209	if (mdev->dump_data != NULL) {
210		while (mdev->dump_copyout) {
211			msleep(&mdev->dump_copyout, &mdev->dump_lock,
212			    0, "mlx5fwr", 0);
213		}
214		mdev->dump_valid = false;
215	} else {
216		error = ENOENT;
217	}
218	mtx_unlock(&mdev->dump_lock);
219	return (error);
220}
221
222static int
223mlx5_dbsf_to_core(const struct mlx5_tool_addr *devaddr,
224    struct mlx5_core_dev **mdev)
225{
226	device_t dev;
227	struct pci_dev *pdev;
228
229	dev = pci_find_dbsf(devaddr->domain, devaddr->bus, devaddr->slot,
230	    devaddr->func);
231	if (dev == NULL)
232		return (ENOENT);
233	if (device_get_devclass(dev) != mlx5_core_driver.bsdclass)
234		return (EINVAL);
235	pdev = device_get_softc(dev);
236	*mdev = pci_get_drvdata(pdev);
237	if (*mdev == NULL)
238		return (ENOENT);
239	return (0);
240}
241
242static int
243mlx5_fwdump_copyout(struct mlx5_core_dev *mdev, struct mlx5_fwdump_get *fwg)
244{
245	const struct mlx5_crspace_regmap *r;
246	struct mlx5_fwdump_reg rv, *urv;
247	uint32_t i, ri;
248	int error;
249
250	mtx_lock(&mdev->dump_lock);
251	if (mdev->dump_data == NULL) {
252		mtx_unlock(&mdev->dump_lock);
253		return (ENOENT);
254	}
255	if (fwg->buf == NULL) {
256		fwg->reg_filled = mdev->dump_size;
257		mtx_unlock(&mdev->dump_lock);
258		return (0);
259	}
260	if (!mdev->dump_valid) {
261		mtx_unlock(&mdev->dump_lock);
262		return (ENOENT);
263	}
264	mdev->dump_copyout = true;
265	mtx_unlock(&mdev->dump_lock);
266
267	urv = fwg->buf;
268	for (i = 0, r = mdev->dump_rege; r->cnt != 0; r++) {
269		for (ri = 0; ri < r->cnt; ri++) {
270			if (i >= fwg->reg_cnt)
271				goto out;
272			rv.addr = r->addr + ri * 4;
273			rv.val = mdev->dump_data[i];
274			error = copyout(&rv, urv, sizeof(rv));
275			if (error != 0)
276				return (error);
277			urv++;
278			i++;
279		}
280	}
281out:
282	fwg->reg_filled = i;
283	mtx_lock(&mdev->dump_lock);
284	mdev->dump_copyout = false;
285	wakeup(&mdev->dump_copyout);
286	mtx_unlock(&mdev->dump_lock);
287	return (0);
288}
289
290static int
291mlx5_fw_reset(struct mlx5_core_dev *mdev)
292{
293	device_t dev, bus;
294	int error;
295
296	error = -mlx5_set_mfrl_reg(mdev, MLX5_FRL_LEVEL3);
297	if (error == 0) {
298		dev = mdev->pdev->dev.bsddev;
299		mtx_lock(&Giant);
300		bus = device_get_parent(dev);
301		error = BUS_RESET_CHILD(device_get_parent(bus), bus,
302		    DEVF_RESET_DETACH);
303		mtx_unlock(&Giant);
304	}
305	return (error);
306}
307
308static int
309mlx5_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
310    struct thread *td)
311{
312	struct mlx5_core_dev *mdev;
313	struct mlx5_fwdump_get *fwg;
314	struct mlx5_tool_addr *devaddr;
315	struct mlx5_fw_update *fu;
316	struct firmware fake_fw;
317	int error;
318
319	error = 0;
320	switch (cmd) {
321	case MLX5_FWDUMP_GET:
322		if ((fflag & FREAD) == 0) {
323			error = EBADF;
324			break;
325		}
326		fwg = (struct mlx5_fwdump_get *)data;
327		devaddr = &fwg->devaddr;
328		error = mlx5_dbsf_to_core(devaddr, &mdev);
329		if (error != 0)
330			break;
331		error = mlx5_fwdump_copyout(mdev, fwg);
332		break;
333	case MLX5_FWDUMP_RESET:
334		if ((fflag & FWRITE) == 0) {
335			error = EBADF;
336			break;
337		}
338		devaddr = (struct mlx5_tool_addr *)data;
339		error = mlx5_dbsf_to_core(devaddr, &mdev);
340		if (error == 0)
341			error = mlx5_fwdump_reset(mdev);
342		break;
343	case MLX5_FWDUMP_FORCE:
344		if ((fflag & FWRITE) == 0) {
345			error = EBADF;
346			break;
347		}
348		devaddr = (struct mlx5_tool_addr *)data;
349		error = mlx5_dbsf_to_core(devaddr, &mdev);
350		if (error != 0)
351			break;
352		mlx5_fwdump(mdev);
353		break;
354	case MLX5_FW_UPDATE:
355		if ((fflag & FWRITE) == 0) {
356			error = EBADF;
357			break;
358		}
359		fu = (struct mlx5_fw_update *)data;
360		if (fu->img_fw_data_len > 10 * 1024 * 1024) {
361			error = EINVAL;
362			break;
363		}
364		devaddr = &fu->devaddr;
365		error = mlx5_dbsf_to_core(devaddr, &mdev);
366		if (error != 0)
367			break;
368		bzero(&fake_fw, sizeof(fake_fw));
369		fake_fw.name = "umlx_fw_up";
370		fake_fw.datasize = fu->img_fw_data_len;
371		fake_fw.version = 1;
372		fake_fw.data = (void *)kmem_malloc(kmem_arena, fu->img_fw_data_len,
373		    M_WAITOK);
374		if (fake_fw.data == NULL) {
375			error = ENOMEM;
376			break;
377		}
378		error = copyin(fu->img_fw_data, __DECONST(void *, fake_fw.data),
379		    fu->img_fw_data_len);
380		if (error == 0)
381			error = -mlx5_firmware_flash(mdev, &fake_fw);
382		kmem_free(kmem_arena, (vm_offset_t)fake_fw.data, fu->img_fw_data_len);
383		break;
384	case MLX5_FW_RESET:
385		if ((fflag & FWRITE) == 0) {
386			error = EBADF;
387			break;
388		}
389		devaddr = (struct mlx5_tool_addr *)data;
390		error = mlx5_dbsf_to_core(devaddr, &mdev);
391		if (error != 0)
392			break;
393		error = mlx5_fw_reset(mdev);
394		break;
395	default:
396		error = ENOTTY;
397		break;
398	}
399	return (error);
400}
401
402static struct cdevsw mlx5_ctl_devsw = {
403	.d_version =	D_VERSION,
404	.d_ioctl =	mlx5_ctl_ioctl,
405};
406
407static struct cdev *mlx5_ctl_dev;
408
409int
410mlx5_ctl_init(void)
411{
412	struct make_dev_args mda;
413	int error;
414
415	make_dev_args_init(&mda);
416	mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
417	mda.mda_devsw = &mlx5_ctl_devsw;
418	mda.mda_uid = UID_ROOT;
419	mda.mda_gid = GID_OPERATOR;
420	mda.mda_mode = 0640;
421	error = make_dev_s(&mda, &mlx5_ctl_dev, "mlx5ctl");
422	return (-error);
423}
424
425void
426mlx5_ctl_fini(void)
427{
428
429	if (mlx5_ctl_dev != NULL)
430		destroy_dev(mlx5_ctl_dev);
431
432}
433