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