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