mlx5_fwdump.c revision 347871
1/*-
2 * Copyright (c) 2018, 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 347871 2019-05-16 18:22:02Z 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
38extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4117[];
39extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4115[];
40extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_connectx5[];
41
42struct mlx5_dump_data {
43	const struct mlx5_crspace_regmap *rege;
44	uint32_t *dump;
45	unsigned dump_size;
46	int dump_valid;
47	struct mtx dump_lock;
48};
49
50static MALLOC_DEFINE(M_MLX5_DUMP, "MLX5DUMP", "MLX5 Firmware dump");
51
52static unsigned
53mlx5_fwdump_getsize(const struct mlx5_crspace_regmap *rege)
54{
55	const struct mlx5_crspace_regmap *r;
56	unsigned sz;
57
58	for (sz = 0, r = rege; r->cnt != 0; r++)
59		sz += r->cnt;
60	return (sz);
61}
62
63static void
64mlx5_fwdump_destroy_dd(struct mlx5_dump_data *dd)
65{
66
67	mtx_destroy(&dd->dump_lock);
68	free(dd->dump, M_MLX5_DUMP);
69	free(dd, M_MLX5_DUMP);
70}
71
72void
73mlx5_fwdump_prep(struct mlx5_core_dev *mdev)
74{
75	struct mlx5_dump_data *dd;
76	int error;
77
78	error = mlx5_vsc_find_cap(mdev);
79	if (error != 0) {
80		/* Inability to create a firmware dump is not fatal. */
81		device_printf((&mdev->pdev->dev)->bsddev, "WARN: "
82		    "mlx5_fwdump_prep failed %d\n", error);
83		return;
84	}
85	dd = malloc(sizeof(struct mlx5_dump_data), M_MLX5_DUMP, M_WAITOK);
86	switch (pci_get_device(mdev->pdev->dev.bsddev)) {
87	case 0x1013:
88		dd->rege = mlx5_crspace_regmap_mt4115;
89		break;
90	case 0x1015:
91		dd->rege = mlx5_crspace_regmap_mt4117;
92		break;
93	case 0x1017:
94	case 0x1019:
95		dd->rege = mlx5_crspace_regmap_connectx5;
96		break;
97	default:
98		free(dd, M_MLX5_DUMP);
99		return; /* silently fail, do not prevent driver attach */
100	}
101	dd->dump_size = mlx5_fwdump_getsize(dd->rege);
102	dd->dump = malloc(dd->dump_size * sizeof(uint32_t), M_MLX5_DUMP,
103	    M_WAITOK | M_ZERO);
104	dd->dump_valid = 0;
105	mtx_init(&dd->dump_lock, "mlx5dmp", NULL, MTX_DEF | MTX_NEW);
106	if (atomic_cmpset_rel_ptr((uintptr_t *)&mdev->dump_data, 0,
107	    (uintptr_t)dd) == 0)
108		mlx5_fwdump_destroy_dd(dd);
109}
110
111void
112mlx5_fwdump(struct mlx5_core_dev *mdev)
113{
114	struct mlx5_dump_data *dd;
115	const struct mlx5_crspace_regmap *r;
116	uint32_t i, ri;
117	int error;
118
119	dev_info(&mdev->pdev->dev, "Issuing FW dump\n");
120	dd = (struct mlx5_dump_data *)atomic_load_acq_ptr((uintptr_t *)
121	    &mdev->dump_data);
122	if (dd == NULL)
123		return;
124	mtx_lock(&dd->dump_lock);
125	if (dd->dump_valid) {
126		/* only one dump */
127		dev_warn(&mdev->pdev->dev,
128		    "Only one FW dump can be captured aborting FW dump\n");
129		goto failed;
130	}
131
132	/* mlx5_vsc already warns, be silent. */
133	error = mlx5_vsc_lock(mdev);
134	if (error != 0)
135		goto failed;
136	error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_PROTECTED_CRSPACE);
137	if (error != 0)
138		goto unlock_vsc;
139	for (i = 0, r = dd->rege; r->cnt != 0; r++) {
140		for (ri = 0; ri < r->cnt; ri++) {
141			error = mlx5_vsc_read(mdev, r->addr + ri * 4,
142			    &dd->dump[i]);
143			if (error != 0)
144				goto unlock_vsc;
145			i++;
146		}
147	}
148	atomic_store_rel_int(&dd->dump_valid, 1);
149unlock_vsc:
150	mlx5_vsc_unlock(mdev);
151failed:
152	mtx_unlock(&dd->dump_lock);
153}
154
155void
156mlx5_fwdump_clean(struct mlx5_core_dev *mdev)
157{
158	struct mlx5_dump_data *dd;
159
160	for (;;) {
161		dd = mdev->dump_data;
162		if (dd == NULL)
163			return;
164		if (atomic_cmpset_ptr((uintptr_t *)&mdev->dump_data,
165		    (uintptr_t)dd, 0) == 1) {
166			mlx5_fwdump_destroy_dd(dd);
167			return;
168		}
169	}
170}
171
172static int
173mlx5_dbsf_to_core(const struct mlx5_tool_addr *devaddr,
174    struct mlx5_core_dev **mdev)
175{
176	device_t dev;
177	struct pci_dev *pdev;
178
179	dev = pci_find_dbsf(devaddr->domain, devaddr->bus, devaddr->slot,
180	    devaddr->func);
181	if (dev == NULL)
182		return (ENOENT);
183	if (device_get_devclass(dev) != mlx5_core_driver.bsdclass)
184		return (EINVAL);
185	pdev = device_get_softc(dev);
186	*mdev = pci_get_drvdata(pdev);
187	if (*mdev == NULL)
188		return (ENOENT);
189	return (0);
190}
191
192static int
193mlx5_fwdump_copyout(struct mlx5_dump_data *dd, struct mlx5_fwdump_get *fwg)
194{
195	const struct mlx5_crspace_regmap *r;
196	struct mlx5_fwdump_reg rv, *urv;
197	uint32_t i, ri;
198	int error;
199
200	if (dd == NULL)
201		return (ENOENT);
202	if (fwg->buf == NULL) {
203		fwg->reg_filled = dd->dump_size;
204		return (0);
205	}
206	if (atomic_load_acq_int(&dd->dump_valid) == 0)
207		return (ENOENT);
208
209	urv = fwg->buf;
210	for (i = 0, r = dd->rege; r->cnt != 0; r++) {
211		for (ri = 0; ri < r->cnt; ri++) {
212			if (i >= fwg->reg_cnt)
213				goto out;
214			rv.addr = r->addr + ri * 4;
215			rv.val = dd->dump[i];
216			error = copyout(&rv, urv, sizeof(rv));
217			if (error != 0)
218				return (error);
219			urv++;
220			i++;
221		}
222	}
223out:
224	fwg->reg_filled = i;
225	return (0);
226}
227
228static int
229mlx5_fw_reset(struct mlx5_core_dev *mdev)
230{
231	device_t dev, bus;
232	int error;
233
234	error = -mlx5_set_mfrl_reg(mdev, MLX5_FRL_LEVEL3);
235	if (error == 0) {
236		dev = mdev->pdev->dev.bsddev;
237		mtx_lock(&Giant);
238		bus = device_get_parent(dev);
239		error = BUS_RESET_CHILD(device_get_parent(bus), bus,
240		    DEVF_RESET_DETACH);
241		mtx_unlock(&Giant);
242	}
243	return (error);
244}
245
246static int
247mlx5_ctl_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
248    struct thread *td)
249{
250	struct mlx5_core_dev *mdev;
251	struct mlx5_fwdump_get *fwg;
252	struct mlx5_tool_addr *devaddr;
253	struct mlx5_dump_data *dd;
254	struct mlx5_fw_update *fu;
255	struct firmware fake_fw;
256	int error;
257
258	error = 0;
259	switch (cmd) {
260	case MLX5_FWDUMP_GET:
261		if ((fflag & FREAD) == 0) {
262			error = EBADF;
263			break;
264		}
265		fwg = (struct mlx5_fwdump_get *)data;
266		devaddr = &fwg->devaddr;
267		error = mlx5_dbsf_to_core(devaddr, &mdev);
268		if (error != 0)
269			break;
270		error = mlx5_fwdump_copyout(mdev->dump_data, fwg);
271		break;
272	case MLX5_FWDUMP_RESET:
273		if ((fflag & FWRITE) == 0) {
274			error = EBADF;
275			break;
276		}
277		devaddr = (struct mlx5_tool_addr *)data;
278		error = mlx5_dbsf_to_core(devaddr, &mdev);
279		if (error != 0)
280			break;
281		dd = mdev->dump_data;
282		if (dd != NULL)
283			atomic_store_rel_int(&dd->dump_valid, 0);
284		else
285			error = ENOENT;
286		break;
287	case MLX5_FWDUMP_FORCE:
288		if ((fflag & FWRITE) == 0) {
289			error = EBADF;
290			break;
291		}
292		devaddr = (struct mlx5_tool_addr *)data;
293		error = mlx5_dbsf_to_core(devaddr, &mdev);
294		if (error != 0)
295			break;
296		mlx5_fwdump(mdev);
297		break;
298	case MLX5_FW_UPDATE:
299		if ((fflag & FWRITE) == 0) {
300			error = EBADF;
301			break;
302		}
303		fu = (struct mlx5_fw_update *)data;
304		if (fu->img_fw_data_len > 10 * 1024 * 1024) {
305			error = EINVAL;
306			break;
307		}
308		devaddr = &fu->devaddr;
309		error = mlx5_dbsf_to_core(devaddr, &mdev);
310		if (error != 0)
311			break;
312		bzero(&fake_fw, sizeof(fake_fw));
313		fake_fw.name = "umlx_fw_up";
314		fake_fw.datasize = fu->img_fw_data_len;
315		fake_fw.version = 1;
316		fake_fw.data = (void *)kmem_malloc(kmem_arena, fu->img_fw_data_len,
317		    M_WAITOK);
318		if (fake_fw.data == NULL) {
319			error = ENOMEM;
320			break;
321		}
322		error = copyin(fu->img_fw_data, __DECONST(void *, fake_fw.data),
323		    fu->img_fw_data_len);
324		if (error == 0)
325			error = -mlx5_firmware_flash(mdev, &fake_fw);
326		kmem_free(kmem_arena, (vm_offset_t)fake_fw.data, fu->img_fw_data_len);
327		break;
328	case MLX5_FW_RESET:
329		if ((fflag & FWRITE) == 0) {
330			error = EBADF;
331			break;
332		}
333		devaddr = (struct mlx5_tool_addr *)data;
334		error = mlx5_dbsf_to_core(devaddr, &mdev);
335		if (error != 0)
336			break;
337		error = mlx5_fw_reset(mdev);
338		break;
339	default:
340		error = ENOTTY;
341		break;
342	}
343	return (error);
344}
345
346static struct cdevsw mlx5_ctl_devsw = {
347	.d_version =	D_VERSION,
348	.d_ioctl =	mlx5_ctl_ioctl,
349};
350
351static struct cdev *mlx5_ctl_dev;
352
353int
354mlx5_ctl_init(void)
355{
356	struct make_dev_args mda;
357	int error;
358
359	make_dev_args_init(&mda);
360	mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME;
361	mda.mda_devsw = &mlx5_ctl_devsw;
362	mda.mda_uid = UID_ROOT;
363	mda.mda_gid = GID_OPERATOR;
364	mda.mda_mode = 0640;
365	error = make_dev_s(&mda, &mlx5_ctl_dev, "mlx5ctl");
366	return (-error);
367}
368
369void
370mlx5_ctl_fini(void)
371{
372
373	if (mlx5_ctl_dev != NULL)
374		destroy_dev(mlx5_ctl_dev);
375
376}
377