1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2020 Oxide Computer Company 14 */ 15 16 /* 17 * fwflash(1M) backend for UFMs. 18 */ 19 20 #include <libdevinfo.h> 21 #include <strings.h> 22 #include <libintl.h> 23 #include <pcidb.h> 24 #include <sys/types.h> 25 #include <sys/stat.h> 26 #include <fcntl.h> 27 #include <unistd.h> 28 #include <libnvpair.h> 29 #include <sys/ddi_ufm.h> 30 #include <sys/sysmacros.h> 31 #include <fwflash/fwflash.h> 32 33 /* 34 * We pick a fixed size unit to work on. 35 */ 36 #define UFM_READ_BUFLEN (16 * 1024 * 1024) 37 38 /* 39 * These are indexes into the addresses array that we use. 40 */ 41 #define UFM_ADDR_PATH 0 42 #define UFM_ADDR_SUB 1 43 #define UFM_ADDR_CAP 2 44 45 typedef struct ufmfw_ident_arg { 46 uint_t uia_nfound; 47 int uia_index; 48 int uia_err; 49 } ufmfw_ident_arg_t; 50 51 /* 52 * fwflash requires we declare our driver name as data with this name. 53 */ 54 const char drivername[] = "ufm"; 55 const int plugin_version = FWPLUGIN_VERSION_2; 56 57 /* 58 * External data from fwflash. 59 */ 60 extern di_node_t rootnode; 61 extern struct fw_plugin *self; 62 63 /* 64 * Global, shared data. 65 */ 66 static int ufmfw_ufm_fd = -1; 67 static pcidb_hdl_t *ufmfw_pcidb; 68 static boolean_t ufmfw_ready = B_FALSE; 69 70 /* 71 * Read image zero and slot zero that we find. 72 */ 73 int 74 fw_readfw(struct devicelist *flashdev, const char *filename) 75 { 76 nvlist_t **images, **slots; 77 uint_t nimages, nslots, caps; 78 uint64_t imgsize, offset; 79 void *buf; 80 int fd; 81 nvlist_t *nvl = flashdev->ident->encap_ident; 82 83 caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP]; 84 if ((caps & DDI_UFM_CAP_READIMG) == 0) { 85 logmsg(MSG_ERROR, "%s: device %s does not support reading " 86 "images\n", flashdev->drvname, flashdev->access_devname); 87 return (FWFLASH_FAILURE); 88 } 89 90 if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images, 91 &nimages) != 0) { 92 logmsg(MSG_ERROR, gettext("%s: %s missing UFM image data\n"), 93 flashdev->drvname, flashdev->access_devname); 94 return (FWFLASH_FAILURE); 95 } 96 97 if (nimages == 0) { 98 logmsg(MSG_ERROR, gettext("%s: %s has no UFM images\n"), 99 flashdev->drvname, flashdev->access_devname); 100 return (FWFLASH_FAILURE); 101 } 102 103 if (nvlist_lookup_nvlist_array(images[0], DDI_UFM_NV_IMAGE_SLOTS, 104 &slots, &nslots) != 0) { 105 logmsg(MSG_ERROR, gettext("%s: image zero of %s has no " 106 "slots\n"), flashdev->drvname, flashdev->access_devname); 107 return (FWFLASH_FAILURE); 108 } 109 110 if (nvlist_lookup_uint64(slots[0], DDI_UFM_NV_SLOT_IMGSIZE, 111 &imgsize) != 0) { 112 logmsg(MSG_ERROR, gettext("%s: device %s doesn't have an image " 113 "size\n"), flashdev->drvname, flashdev->access_devname); 114 return (FWFLASH_FAILURE); 115 } 116 117 logmsg(MSG_INFO, gettext("%s: Need to read %" PRIu64 " bytes\n"), 118 flashdev->drvname, imgsize); 119 120 if ((buf = malloc(UFM_READ_BUFLEN)) == NULL) { 121 logmsg(MSG_ERROR, gettext("%s: Failed to allocate data " 122 "buffer\n"), flashdev->drvname); 123 return (FWFLASH_FAILURE); 124 } 125 126 if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) { 127 logmsg(MSG_ERROR, gettext("%s: failed to open file %s: %s\n"), 128 flashdev->drvname, filename, strerror(errno)); 129 free(buf); 130 return (FWFLASH_FAILURE); 131 } 132 133 offset = 0; 134 while (imgsize > 0) { 135 ufm_ioc_readimg_t rimg; 136 uint64_t toread = MIN(imgsize, UFM_READ_BUFLEN); 137 size_t woff; 138 139 bzero(&rimg, sizeof (rimg)); 140 rimg.ufri_version = DDI_UFM_CURRENT_VERSION; 141 rimg.ufri_imageno = 0; 142 rimg.ufri_slotno = 0; 143 rimg.ufri_offset = offset; 144 rimg.ufri_len = toread; 145 rimg.ufri_buf = buf; 146 (void) strlcpy(rimg.ufri_devpath, 147 flashdev->addresses[UFM_ADDR_PATH], 148 sizeof (rimg.ufri_devpath)); 149 logmsg(MSG_INFO, gettext("%s: want to read %" PRIu64 " bytes " 150 "at offset %" PRIu64 "\n"), flashdev->drvname, 151 rimg.ufri_len, rimg.ufri_offset); 152 153 if (ioctl(ufmfw_ufm_fd, UFM_IOC_READIMG, &rimg) != 0) { 154 logmsg(MSG_ERROR, gettext("%s: failed to read image: " 155 "%s\n"), flashdev->drvname, strerror(errno)); 156 free(buf); 157 (void) close(fd); 158 return (FWFLASH_FAILURE); 159 } 160 161 logmsg(MSG_INFO, gettext("%s: read %" PRIu64 " bytes at offset " 162 "%" PRIu64 "\n"), flashdev->drvname, rimg.ufri_nread, 163 offset); 164 offset += rimg.ufri_nread; 165 imgsize -= rimg.ufri_nread; 166 167 woff = 0; 168 while (rimg.ufri_nread > 0) { 169 size_t towrite = MIN(rimg.ufri_nread, UFM_READ_BUFLEN); 170 ssize_t ret = write(fd, buf + woff, towrite); 171 if (ret == -1) { 172 logmsg(MSG_ERROR, gettext("%s: failed to write " 173 "to %s: %s\n"), flashdev->drvname, filename, 174 strerror(errno)); 175 free(buf); 176 (void) close(fd); 177 return (FWFLASH_FAILURE); 178 } 179 180 rimg.ufri_nread -= ret; 181 woff += ret; 182 } 183 } 184 185 free(buf); 186 if (close(fd) != 0) { 187 logmsg(MSG_ERROR, gettext("%s: failed to finish writing to %s: " 188 "%s\n"), flashdev->drvname, filename, strerror(errno)); 189 return (FWFLASH_FAILURE); 190 } 191 logmsg(MSG_INFO, gettext("%s: successfully wrote image to %s\n"), 192 flashdev->drvname, filename); 193 return (FWFLASH_SUCCESS); 194 } 195 196 197 int 198 fw_writefw(struct devicelist *flashdev) 199 { 200 return (FWFLASH_SUCCESS); 201 } 202 203 static void 204 ufmfw_flashdev_free(struct devicelist *flashdev) 205 { 206 if (flashdev == NULL) 207 return; 208 if (flashdev->ident != NULL) { 209 free(flashdev->ident->vid); 210 free(flashdev->ident->pid); 211 nvlist_free(flashdev->ident->encap_ident); 212 } 213 free(flashdev->ident); 214 free(flashdev->drvname); 215 free(flashdev->classname); 216 free(flashdev->access_devname); 217 di_devfs_path_free(flashdev->addresses[UFM_ADDR_PATH]); 218 free(flashdev->addresses[UFM_ADDR_SUB]); 219 free(flashdev); 220 } 221 222 /* 223 * Check if a node is a PCI device. This is so we can deal with VPD information. 224 * Hopefully we'll have a generalized devinfo or fmtopo VPD section which we can 225 * then use for this instead. 226 */ 227 static boolean_t 228 ufmfw_node_pci(di_node_t node) 229 { 230 while (node != DI_NODE_NIL) { 231 char *strs; 232 int ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node, 233 "device_type", &strs); 234 235 if (ret > 0) { 236 if (strcmp(strs, "pci") == 0 || 237 strcmp(strs, "pciex") == 0) { 238 return (B_TRUE); 239 } 240 } 241 242 node = di_parent_node(node); 243 } 244 return (B_FALSE); 245 } 246 247 /* 248 * Cons up VPD information based on the PCI ID. Hopefully in time we'll use the 249 * actual PCI VPD information and more generally allow a device to specify its 250 * vpd automatically. 251 */ 252 static boolean_t 253 ufmfw_fill_vpd(struct devicelist *flashdev, di_node_t node) 254 { 255 int *vid, *did, *svid, *sdid; 256 pcidb_vendor_t *vend = NULL; 257 pcidb_device_t *dev = NULL; 258 pcidb_subvd_t *subdv = NULL; 259 char *vstr, *dstr; 260 261 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid) != 1) { 262 logmsg(MSG_ERROR, gettext("%s: %s missing 'vendor-id' " 263 "property\n"), flashdev->drvname, flashdev->access_devname); 264 return (B_FALSE); 265 } 266 267 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did) != 1) { 268 logmsg(MSG_ERROR, gettext("%s: %s missing 'device-id' " 269 "property\n"), flashdev->drvname, flashdev->access_devname); 270 return (B_FALSE); 271 } 272 273 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-vendor-id", 274 &svid) != 1 || di_prop_lookup_ints(DDI_DEV_T_ANY, node, 275 "subsystem-device-id", &sdid) != 1) { 276 svid = NULL; 277 sdid = NULL; 278 } 279 280 vend = pcidb_lookup_vendor(ufmfw_pcidb, vid[0]); 281 if (vend != NULL) { 282 dev = pcidb_lookup_device_by_vendor(vend, did[0]); 283 } 284 285 if (dev != NULL && svid != NULL && sdid != NULL) { 286 subdv = pcidb_lookup_subvd_by_device(dev, svid[0], sdid[0]); 287 } 288 289 if (vend != NULL) { 290 vstr = strdup(pcidb_vendor_name(vend)); 291 } else { 292 (void) asprintf(&vstr, "pci:%x", vid[0]); 293 } 294 295 if (vstr == NULL) { 296 logmsg(MSG_ERROR, gettext("%s: failed to allocate vid " 297 "string\n"), flashdev->drvname); 298 return (B_FALSE); 299 } 300 flashdev->ident->vid = vstr; 301 302 if (dstr != NULL) { 303 dstr = strdup(pcidb_device_name(dev)); 304 } else { 305 (void) asprintf(&dstr, "pci:%x", did[0]); 306 } 307 308 if (dstr == NULL) { 309 logmsg(MSG_ERROR, gettext("%s: failed to allocate pid " 310 "string\n"), flashdev->drvname); 311 return (B_FALSE); 312 } 313 flashdev->ident->pid = dstr; 314 315 if (subdv != NULL) { 316 /* 317 * Because this is optional, don't fail if we fail to duplicate 318 * this. 319 */ 320 flashdev->addresses[UFM_ADDR_SUB] = 321 strdup(pcidb_subvd_name(subdv)); 322 if (flashdev->addresses[UFM_ADDR_SUB] == NULL) { 323 logmsg(MSG_WARN, gettext("%s: failed to allocate vpd " 324 "subsystem name\n"), flashdev->drvname); 325 } 326 } 327 328 return (B_TRUE); 329 } 330 331 static int 332 ufmfw_di_walk_cb(di_node_t node, void *arg) 333 { 334 int ret; 335 boolean_t found = B_FALSE; 336 di_prop_t prop = DI_PROP_NIL; 337 ufmfw_ident_arg_t *uia = arg; 338 struct devicelist *flashdev = NULL; 339 ufm_ioc_getcaps_t caps; 340 ufm_ioc_bufsz_t bufsz; 341 ufm_ioc_report_t rep; 342 char *devfs, *packnvl; 343 nvlist_t *nvl = NULL; 344 345 while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) { 346 const char *pname = di_prop_name(prop); 347 if (strcmp(pname, "ddi-ufm-capable") == 0) { 348 found = B_TRUE; 349 break; 350 } 351 } 352 353 if (!found) { 354 return (DI_WALK_CONTINUE); 355 } 356 357 if (!ufmfw_node_pci(node)) { 358 return (DI_WALK_CONTINUE); 359 } 360 361 if ((devfs = di_devfs_path(node)) == NULL) { 362 logmsg(MSG_ERROR, gettext("%s: failed to get device node " 363 "path\n"), drivername); 364 goto err; 365 } 366 367 bzero(&caps, sizeof (caps)); 368 caps.ufmg_version = DDI_UFM_CURRENT_VERSION; 369 (void) strlcpy(caps.ufmg_devpath, devfs, sizeof (caps.ufmg_devpath)); 370 if (ioctl(ufmfw_ufm_fd, UFM_IOC_GETCAPS, &caps) != 0) { 371 logmsg(MSG_ERROR, gettext("%s: failed to get UFM caps for " 372 "UFM compatible device %s: %s\n"), drivername, devfs, 373 strerror(errno)); 374 di_devfs_path_free(devfs); 375 return (DI_WALK_CONTINUE); 376 } 377 378 /* 379 * If nothing is supported just leave it be. 380 */ 381 if (caps.ufmg_caps == 0) { 382 di_devfs_path_free(devfs); 383 return (DI_WALK_CONTINUE); 384 } 385 386 bzero(&bufsz, sizeof (bufsz)); 387 bufsz.ufbz_version = DDI_UFM_CURRENT_VERSION; 388 (void) strlcpy(bufsz.ufbz_devpath, devfs, sizeof (bufsz.ufbz_devpath)); 389 if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORTSZ, &bufsz) != 0) { 390 logmsg(MSG_ERROR, gettext("%s: failed to get UFM report size " 391 "for device %s: %s\n"), drivername, devfs, 392 strerror(errno)); 393 di_devfs_path_free(devfs); 394 return (DI_WALK_CONTINUE); 395 } 396 397 if ((packnvl = malloc(bufsz.ufbz_size)) == NULL) { 398 logmsg(MSG_ERROR, gettext("%s: failed to allocate %zu bytes " 399 "for report buffer\n"), drivername, bufsz.ufbz_size); 400 di_devfs_path_free(devfs); 401 goto err; 402 } 403 bzero(&rep, sizeof (rep)); 404 rep.ufmr_version = DDI_UFM_CURRENT_VERSION; 405 rep.ufmr_bufsz = bufsz.ufbz_size; 406 rep.ufmr_buf = packnvl; 407 (void) strlcpy(rep.ufmr_devpath, devfs, sizeof (rep.ufmr_devpath)); 408 if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORT, &rep) != 0) { 409 logmsg(MSG_ERROR, gettext("%s: failed to get UFM report " 410 "for device %s: %s\n"), drivername, devfs, 411 strerror(errno)); 412 free(packnvl); 413 di_devfs_path_free(devfs); 414 return (DI_WALK_CONTINUE); 415 } 416 417 if ((ret = nvlist_unpack(packnvl, rep.ufmr_bufsz, &nvl, 0)) != 0) { 418 logmsg(MSG_ERROR, gettext("%s: failed to unpack UFM report " 419 "for device %s: %s\n"), drivername, devfs, strerror(ret)); 420 free(packnvl); 421 di_devfs_path_free(devfs); 422 return (DI_WALK_CONTINUE); 423 424 } 425 free(packnvl); 426 427 if ((flashdev = calloc(1, sizeof (*flashdev))) == NULL) { 428 logmsg(MSG_ERROR, gettext("%s: failed to allocate new " 429 "device entry for node %s\n"), drivername, devfs); 430 di_devfs_path_free(devfs); 431 goto err; 432 } 433 434 flashdev->addresses[UFM_ADDR_PATH] = devfs; 435 436 if (asprintf(&flashdev->access_devname, "/devices%s", 437 flashdev->addresses[UFM_ADDR_PATH]) == -1) { 438 logmsg(MSG_ERROR, gettext("%s: failed to construct device " 439 "path\n"), drivername); 440 goto err; 441 } 442 if ((flashdev->drvname = strdup(drivername)) == NULL) { 443 logmsg(MSG_ERROR, gettext("%s: failed to construct driver " 444 "name\n"), drivername); 445 goto err; 446 } 447 if ((flashdev->classname = strdup(drivername)) == NULL) { 448 logmsg(MSG_ERROR, gettext("%s: failed to allocate vpd " 449 "data\n"), drivername); 450 goto err; 451 } 452 453 if ((flashdev->ident = calloc(1, sizeof (struct vpr))) == NULL) { 454 logmsg(MSG_ERROR, gettext("%s: failed to construct class " 455 "name\n"), drivername); 456 goto err; 457 } 458 if (!ufmfw_fill_vpd(flashdev, node)) { 459 goto err; 460 } 461 462 flashdev->ident->encap_ident = nvl; 463 464 flashdev->index = uia->uia_index; 465 uia->uia_index++; 466 flashdev->addresses[UFM_ADDR_CAP] = (void *)(uintptr_t)caps.ufmg_caps; 467 flashdev->plugin = self; 468 uia->uia_nfound++; 469 470 TAILQ_INSERT_TAIL(fw_devices, flashdev, nextdev); 471 472 return (DI_WALK_CONTINUE); 473 474 err: 475 nvlist_free(nvl); 476 uia->uia_err = FWFLASH_FAILURE; 477 ufmfw_flashdev_free(flashdev); 478 return (DI_WALK_TERMINATE); 479 } 480 481 int 482 fw_identify(int start) 483 { 484 ufmfw_ident_arg_t uia; 485 486 if (!ufmfw_ready) { 487 return (FWFLASH_FAILURE); 488 } 489 490 uia.uia_nfound = 0; 491 uia.uia_index = start; 492 uia.uia_err = FWFLASH_SUCCESS; 493 (void) di_walk_node(rootnode, DI_WALK_CLDFIRST, &uia, 494 ufmfw_di_walk_cb); 495 if (uia.uia_nfound == 0) { 496 return (FWFLASH_FAILURE); 497 } 498 499 return (uia.uia_err); 500 } 501 502 int 503 fw_devinfo(struct devicelist *flashdev) 504 { 505 nvlist_t *nvl, **images; 506 uint_t nimages, img, caps; 507 508 (void) printf(gettext("Device[%d] %s\n"), flashdev->index, 509 flashdev->access_devname); 510 (void) printf(gettext("Class [%s]\n"), flashdev->classname); 511 (void) printf(gettext("\tVendor: %s\n\tDevice: %s\n"), 512 flashdev->ident->vid, flashdev->ident->pid); 513 if (flashdev->addresses[UFM_ADDR_SUB] != NULL) { 514 (void) printf(gettext("\tSubsystem: %s\n"), 515 flashdev->addresses[UFM_ADDR_SUB]); 516 } 517 518 caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP]; 519 if (caps != 0) { 520 boolean_t first = B_TRUE; 521 (void) printf(gettext("\tCapabilities: ")); 522 if (caps & DDI_UFM_CAP_REPORT) { 523 (void) printf(gettext("Report")); 524 first = B_FALSE; 525 } 526 527 if (caps & DDI_UFM_CAP_READIMG) { 528 (void) printf(gettext("%sRead Image"), 529 first ? "" : ", "); 530 } 531 (void) printf("\n"); 532 } 533 534 nvl = flashdev->ident->encap_ident; 535 if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images, 536 &nimages) != 0) { 537 goto done; 538 } 539 540 for (img = 0; img < nimages; img++) { 541 nvlist_t **slots; 542 uint_t nslots, s; 543 char *desc; 544 545 if (nvlist_lookup_nvlist_array(images[img], 546 DDI_UFM_NV_IMAGE_SLOTS, &slots, &nslots) != 0) { 547 goto done; 548 } 549 550 if (nvlist_lookup_string(images[img], DDI_UFM_NV_IMAGE_DESC, 551 &desc) != 0) { 552 desc = NULL; 553 } 554 555 if (desc != NULL) { 556 (void) printf(gettext("\tImage %d: %s\n"), img, desc); 557 } else { 558 (void) printf(gettext("\tImage %d:\n"), img); 559 } 560 561 for (s = 0; s < nslots; s++) { 562 uint32_t attr; 563 char *version; 564 565 if (nvlist_lookup_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR, 566 &attr) != 0) { 567 attr = 0; 568 } 569 570 if (nvlist_lookup_string(slots[s], 571 DDI_UFM_NV_SLOT_VERSION, &version) != 0) { 572 version = "<unknown>"; 573 } 574 575 printf(gettext("\t Slot %d (%c|%c|%c): %s\n"), s, 576 attr & DDI_UFM_ATTR_READABLE ? 'r' : '-', 577 attr & DDI_UFM_ATTR_WRITEABLE ? 'w' : '-', 578 attr & DDI_UFM_ATTR_ACTIVE ? 'a' : '-', 579 attr & DDI_UFM_ATTR_EMPTY ? "<empty>" : version); 580 581 } 582 } 583 584 done: 585 (void) printf("\n\n"); 586 return (FWFLASH_SUCCESS); 587 } 588 589 void 590 fw_cleanup(struct devicelist *flashdev) 591 { 592 ufmfw_flashdev_free(flashdev); 593 } 594 595 #pragma init(ufmfw_init) 596 static void 597 ufmfw_init(void) 598 { 599 ufmfw_ufm_fd = open("/dev/ufm", O_RDONLY); 600 if (ufmfw_ufm_fd < 0) { 601 logmsg(MSG_ERROR, gettext("%s: failed to open /dev/ufm: %s\n"), 602 drivername, strerror(errno)); 603 return; 604 } 605 606 ufmfw_pcidb = pcidb_open(PCIDB_VERSION); 607 if (ufmfw_pcidb == NULL) { 608 logmsg(MSG_ERROR, gettext("%s: failed to open libpcidb: %s\n"), 609 drivername, strerror(errno)); 610 return; 611 } 612 ufmfw_ready = B_TRUE; 613 } 614 615 #pragma fini(ufmfw_fini) 616 static void 617 ufmfw_fini(void) 618 { 619 pcidb_close(ufmfw_pcidb); 620 if (ufmfw_ufm_fd >= 0) { 621 (void) close(ufmfw_ufm_fd); 622 } 623 ufmfw_ready = B_FALSE; 624 } 625