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