]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/mlx5/mlx5_core/mlx5_fwdump.c
Implement firmware reset from userspace in mlx5tool(8).
[FreeBSD/FreeBSD.git] / sys / dev / mlx5 / mlx5_core / mlx5_fwdump.c
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$");
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
38 extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4117[];
39 extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4115[];
40 extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_connectx5[];
41
42 struct 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
50 static MALLOC_DEFINE(M_MLX5_DUMP, "MLX5DUMP", "MLX5 Firmware dump");
51
52 static unsigned
53 mlx5_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
63 static void
64 mlx5_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
72 void
73 mlx5_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
111 void
112 mlx5_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);
149 unlock_vsc:
150         mlx5_vsc_unlock(mdev);
151 failed:
152         mtx_unlock(&dd->dump_lock);
153 }
154
155 void
156 mlx5_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
172 static int
173 mlx5_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
192 static int
193 mlx5_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         }
223 out:
224         fwg->reg_filled = i;
225         return (0);
226 }
227
228 static int
229 mlx5_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
246 static int
247 mlx5_fwdump_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(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((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
346 static struct cdevsw mlx5_fwdump_devsw = {
347         .d_version =    D_VERSION,
348         .d_ioctl =      mlx5_fwdump_ioctl,
349 };
350
351 static struct cdev *mlx5_fwdump_dev;
352
353 int
354 mlx5_fwdump_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_fwdump_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_fwdump_dev, "mlx5ctl");
366         return (-error);
367 }
368
369 void
370 mlx5_fwdump_fini(void)
371 {
372
373         if (mlx5_fwdump_dev != NULL)
374                 destroy_dev(mlx5_fwdump_dev);
375
376 }