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