/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright 2022 Oxide Computer Company */ /* * fwflash(8) backend for UFMs. */ #include #include #include #include #include #include #include #include #include #include #include #include /* * We pick a fixed size unit to work on. */ #define UFM_READ_BUFLEN (16 * 1024 * 1024) /* * These are indexes into the addresses array that we use. */ #define UFM_ADDR_PATH 0 #define UFM_ADDR_SUB 1 #define UFM_ADDR_CAP 2 typedef struct ufmfw_ident_arg { uint_t uia_nfound; int uia_index; int uia_err; } ufmfw_ident_arg_t; /* * fwflash requires we declare our driver name as data with this name. */ const char drivername[] = "ufm"; const int plugin_version = FWPLUGIN_VERSION_2; /* * External data from fwflash. */ extern di_node_t rootnode; extern struct fw_plugin *self; /* * Global, shared data. */ static int ufmfw_ufm_fd = -1; static pcidb_hdl_t *ufmfw_pcidb; static boolean_t ufmfw_ready = B_FALSE; /* * Read image zero and slot zero that we find. */ int fw_readfw(struct devicelist *flashdev, const char *filename) { nvlist_t **images, **slots; uint_t nimages, nslots, caps; uint64_t imgsize, offset; void *buf; int fd; nvlist_t *nvl = flashdev->ident->encap_ident; caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP]; if ((caps & DDI_UFM_CAP_READIMG) == 0) { logmsg(MSG_ERROR, "%s: device %s does not support reading " "images\n", flashdev->drvname, flashdev->access_devname); return (FWFLASH_FAILURE); } if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images, &nimages) != 0) { logmsg(MSG_ERROR, gettext("%s: %s missing UFM image data\n"), flashdev->drvname, flashdev->access_devname); return (FWFLASH_FAILURE); } if (nimages == 0) { logmsg(MSG_ERROR, gettext("%s: %s has no UFM images\n"), flashdev->drvname, flashdev->access_devname); return (FWFLASH_FAILURE); } if (nvlist_lookup_nvlist_array(images[0], DDI_UFM_NV_IMAGE_SLOTS, &slots, &nslots) != 0) { logmsg(MSG_ERROR, gettext("%s: image zero of %s has no " "slots\n"), flashdev->drvname, flashdev->access_devname); return (FWFLASH_FAILURE); } if (nvlist_lookup_uint64(slots[0], DDI_UFM_NV_SLOT_IMGSIZE, &imgsize) != 0) { logmsg(MSG_ERROR, gettext("%s: device %s doesn't have an image " "size\n"), flashdev->drvname, flashdev->access_devname); return (FWFLASH_FAILURE); } logmsg(MSG_INFO, gettext("%s: Need to read %" PRIu64 " bytes\n"), flashdev->drvname, imgsize); if ((buf = malloc(UFM_READ_BUFLEN)) == NULL) { logmsg(MSG_ERROR, gettext("%s: Failed to allocate data " "buffer\n"), flashdev->drvname); return (FWFLASH_FAILURE); } if ((fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) { logmsg(MSG_ERROR, gettext("%s: failed to open file %s: %s\n"), flashdev->drvname, filename, strerror(errno)); free(buf); return (FWFLASH_FAILURE); } offset = 0; while (imgsize > 0) { ufm_ioc_readimg_t rimg; uint64_t toread = MIN(imgsize, UFM_READ_BUFLEN); size_t woff; bzero(&rimg, sizeof (rimg)); rimg.ufri_version = DDI_UFM_CURRENT_VERSION; rimg.ufri_imageno = 0; rimg.ufri_slotno = 0; rimg.ufri_offset = offset; rimg.ufri_len = toread; rimg.ufri_buf = buf; (void) strlcpy(rimg.ufri_devpath, flashdev->addresses[UFM_ADDR_PATH], sizeof (rimg.ufri_devpath)); logmsg(MSG_INFO, gettext("%s: want to read %" PRIu64 " bytes " "at offset %" PRIu64 "\n"), flashdev->drvname, rimg.ufri_len, rimg.ufri_offset); if (ioctl(ufmfw_ufm_fd, UFM_IOC_READIMG, &rimg) != 0) { logmsg(MSG_ERROR, gettext("%s: failed to read image: " "%s\n"), flashdev->drvname, strerror(errno)); free(buf); (void) close(fd); return (FWFLASH_FAILURE); } logmsg(MSG_INFO, gettext("%s: read %" PRIu64 " bytes at offset " "%" PRIu64 "\n"), flashdev->drvname, rimg.ufri_nread, offset); offset += rimg.ufri_nread; imgsize -= rimg.ufri_nread; woff = 0; while (rimg.ufri_nread > 0) { size_t towrite = MIN(rimg.ufri_nread, UFM_READ_BUFLEN); ssize_t ret = write(fd, buf + woff, towrite); if (ret == -1) { logmsg(MSG_ERROR, gettext("%s: failed to write " "to %s: %s\n"), flashdev->drvname, filename, strerror(errno)); free(buf); (void) close(fd); return (FWFLASH_FAILURE); } rimg.ufri_nread -= ret; woff += ret; } } free(buf); if (close(fd) != 0) { logmsg(MSG_ERROR, gettext("%s: failed to finish writing to %s: " "%s\n"), flashdev->drvname, filename, strerror(errno)); return (FWFLASH_FAILURE); } logmsg(MSG_INFO, gettext("%s: successfully wrote image to %s\n"), flashdev->drvname, filename); return (FWFLASH_SUCCESS); } int fw_writefw(struct devicelist *flashdev) { return (FWFLASH_SUCCESS); } static void ufmfw_flashdev_free(struct devicelist *flashdev) { if (flashdev == NULL) return; if (flashdev->ident != NULL) { free(flashdev->ident->vid); free(flashdev->ident->pid); nvlist_free(flashdev->ident->encap_ident); } free(flashdev->ident); free(flashdev->drvname); free(flashdev->classname); free(flashdev->access_devname); di_devfs_path_free(flashdev->addresses[UFM_ADDR_PATH]); free(flashdev->addresses[UFM_ADDR_SUB]); free(flashdev); } /* * Check if a node is a PCI device. This is so we can deal with VPD information. * Hopefully we'll have a generalized devinfo or fmtopo VPD section which we can * then use for this instead. */ static boolean_t ufmfw_node_pci(di_node_t node) { while (node != DI_NODE_NIL) { char *strs; int ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node, "device_type", &strs); if (ret > 0) { if (strcmp(strs, "pci") == 0 || strcmp(strs, "pciex") == 0) { return (B_TRUE); } } node = di_parent_node(node); } return (B_FALSE); } /* * Cons up VPD information based on the PCI ID. Hopefully in time we'll use the * actual PCI VPD information and more generally allow a device to specify its * vpd automatically. */ static boolean_t ufmfw_fill_vpd(struct devicelist *flashdev, di_node_t node) { int *vid, *did, *svid, *sdid; pcidb_vendor_t *vend = NULL; pcidb_device_t *dev = NULL; pcidb_subvd_t *subdv = NULL; char *vstr, *dstr; if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vid) != 1) { logmsg(MSG_ERROR, gettext("%s: %s missing 'vendor-id' " "property\n"), flashdev->drvname, flashdev->access_devname); return (B_FALSE); } if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &did) != 1) { logmsg(MSG_ERROR, gettext("%s: %s missing 'device-id' " "property\n"), flashdev->drvname, flashdev->access_devname); return (B_FALSE); } if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-vendor-id", &svid) != 1 || di_prop_lookup_ints(DDI_DEV_T_ANY, node, "subsystem-device-id", &sdid) != 1) { svid = NULL; sdid = NULL; } vend = pcidb_lookup_vendor(ufmfw_pcidb, vid[0]); if (vend != NULL) { dev = pcidb_lookup_device_by_vendor(vend, did[0]); } if (dev != NULL && svid != NULL && sdid != NULL) { subdv = pcidb_lookup_subvd_by_device(dev, svid[0], sdid[0]); } if (vend != NULL) { vstr = strdup(pcidb_vendor_name(vend)); } else { (void) asprintf(&vstr, "pci:%x", vid[0]); } if (vstr == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to allocate vid " "string\n"), flashdev->drvname); return (B_FALSE); } flashdev->ident->vid = vstr; if (dev != NULL) { dstr = strdup(pcidb_device_name(dev)); } else { (void) asprintf(&dstr, "pci:%x", did[0]); } if (dstr == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to allocate pid " "string\n"), flashdev->drvname); return (B_FALSE); } flashdev->ident->pid = dstr; if (subdv != NULL) { /* * Because this is optional, don't fail if we fail to duplicate * this. */ flashdev->addresses[UFM_ADDR_SUB] = strdup(pcidb_subvd_name(subdv)); if (flashdev->addresses[UFM_ADDR_SUB] == NULL) { logmsg(MSG_WARN, gettext("%s: failed to allocate vpd " "subsystem name\n"), flashdev->drvname); } } return (B_TRUE); } static int ufmfw_di_walk_cb(di_node_t node, void *arg) { int ret; boolean_t found = B_FALSE; di_prop_t prop = DI_PROP_NIL; ufmfw_ident_arg_t *uia = arg; struct devicelist *flashdev = NULL; ufm_ioc_getcaps_t caps; ufm_ioc_bufsz_t bufsz; ufm_ioc_report_t rep; char *devfs, *packnvl; nvlist_t *nvl = NULL; while ((prop = di_prop_next(node, prop)) != DI_PROP_NIL) { const char *pname = di_prop_name(prop); if (strcmp(pname, "ddi-ufm-capable") == 0) { found = B_TRUE; break; } } if (!found) { return (DI_WALK_CONTINUE); } if (!ufmfw_node_pci(node)) { return (DI_WALK_CONTINUE); } if ((devfs = di_devfs_path(node)) == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to get device node " "path\n"), drivername); goto err; } bzero(&caps, sizeof (caps)); caps.ufmg_version = DDI_UFM_CURRENT_VERSION; (void) strlcpy(caps.ufmg_devpath, devfs, sizeof (caps.ufmg_devpath)); if (ioctl(ufmfw_ufm_fd, UFM_IOC_GETCAPS, &caps) != 0) { logmsg(MSG_ERROR, gettext("%s: failed to get UFM caps for " "UFM compatible device %s: %s\n"), drivername, devfs, strerror(errno)); di_devfs_path_free(devfs); return (DI_WALK_CONTINUE); } /* * If nothing is supported just leave it be. */ if (caps.ufmg_caps == 0) { di_devfs_path_free(devfs); return (DI_WALK_CONTINUE); } bzero(&bufsz, sizeof (bufsz)); bufsz.ufbz_version = DDI_UFM_CURRENT_VERSION; (void) strlcpy(bufsz.ufbz_devpath, devfs, sizeof (bufsz.ufbz_devpath)); if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORTSZ, &bufsz) != 0) { logmsg(MSG_ERROR, gettext("%s: failed to get UFM report size " "for device %s: %s\n"), drivername, devfs, strerror(errno)); di_devfs_path_free(devfs); return (DI_WALK_CONTINUE); } if ((packnvl = malloc(bufsz.ufbz_size)) == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to allocate %zu bytes " "for report buffer\n"), drivername, bufsz.ufbz_size); di_devfs_path_free(devfs); goto err; } bzero(&rep, sizeof (rep)); rep.ufmr_version = DDI_UFM_CURRENT_VERSION; rep.ufmr_bufsz = bufsz.ufbz_size; rep.ufmr_buf = packnvl; (void) strlcpy(rep.ufmr_devpath, devfs, sizeof (rep.ufmr_devpath)); if (ioctl(ufmfw_ufm_fd, UFM_IOC_REPORT, &rep) != 0) { logmsg(MSG_ERROR, gettext("%s: failed to get UFM report " "for device %s: %s\n"), drivername, devfs, strerror(errno)); free(packnvl); di_devfs_path_free(devfs); return (DI_WALK_CONTINUE); } if ((ret = nvlist_unpack(packnvl, rep.ufmr_bufsz, &nvl, 0)) != 0) { logmsg(MSG_ERROR, gettext("%s: failed to unpack UFM report " "for device %s: %s\n"), drivername, devfs, strerror(ret)); free(packnvl); di_devfs_path_free(devfs); return (DI_WALK_CONTINUE); } free(packnvl); if ((flashdev = calloc(1, sizeof (*flashdev))) == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to allocate new " "device entry for node %s\n"), drivername, devfs); di_devfs_path_free(devfs); goto err; } flashdev->addresses[UFM_ADDR_PATH] = devfs; if (asprintf(&flashdev->access_devname, "/devices%s", flashdev->addresses[UFM_ADDR_PATH]) == -1) { logmsg(MSG_ERROR, gettext("%s: failed to construct device " "path\n"), drivername); goto err; } if ((flashdev->drvname = strdup(drivername)) == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to construct driver " "name\n"), drivername); goto err; } if ((flashdev->classname = strdup(drivername)) == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to allocate vpd " "data\n"), drivername); goto err; } if ((flashdev->ident = calloc(1, sizeof (struct vpr))) == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to construct class " "name\n"), drivername); goto err; } if (!ufmfw_fill_vpd(flashdev, node)) { goto err; } flashdev->ident->encap_ident = nvl; flashdev->index = uia->uia_index; uia->uia_index++; flashdev->addresses[UFM_ADDR_CAP] = (void *)(uintptr_t)caps.ufmg_caps; flashdev->plugin = self; uia->uia_nfound++; TAILQ_INSERT_TAIL(fw_devices, flashdev, nextdev); return (DI_WALK_CONTINUE); err: nvlist_free(nvl); uia->uia_err = FWFLASH_FAILURE; ufmfw_flashdev_free(flashdev); return (DI_WALK_TERMINATE); } int fw_identify(int start) { ufmfw_ident_arg_t uia; if (!ufmfw_ready) { return (FWFLASH_FAILURE); } uia.uia_nfound = 0; uia.uia_index = start; uia.uia_err = FWFLASH_SUCCESS; (void) di_walk_node(rootnode, DI_WALK_CLDFIRST, &uia, ufmfw_di_walk_cb); if (uia.uia_nfound == 0) { return (FWFLASH_FAILURE); } return (uia.uia_err); } int fw_devinfo(struct devicelist *flashdev) { nvlist_t *nvl, **images; uint_t nimages, img, caps; (void) printf(gettext("Device[%d] %s\n"), flashdev->index, flashdev->access_devname); (void) printf(gettext("Class [%s]\n"), flashdev->classname); (void) printf(gettext("\tVendor: %s\n\tDevice: %s\n"), flashdev->ident->vid, flashdev->ident->pid); if (flashdev->addresses[UFM_ADDR_SUB] != NULL) { (void) printf(gettext("\tSubsystem: %s\n"), flashdev->addresses[UFM_ADDR_SUB]); } caps = (uintptr_t)flashdev->addresses[UFM_ADDR_CAP]; if (caps != 0) { boolean_t first = B_TRUE; (void) printf(gettext("\tCapabilities: ")); if (caps & DDI_UFM_CAP_REPORT) { (void) printf(gettext("Report")); first = B_FALSE; } if (caps & DDI_UFM_CAP_READIMG) { (void) printf(gettext("%sRead Image"), first ? "" : ", "); } (void) printf("\n"); } nvl = flashdev->ident->encap_ident; if (nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &images, &nimages) != 0) { goto done; } for (img = 0; img < nimages; img++) { nvlist_t **slots; uint_t nslots, s; char *desc; if (nvlist_lookup_nvlist_array(images[img], DDI_UFM_NV_IMAGE_SLOTS, &slots, &nslots) != 0) { goto done; } if (nvlist_lookup_string(images[img], DDI_UFM_NV_IMAGE_DESC, &desc) != 0) { desc = NULL; } if (desc != NULL) { (void) printf(gettext("\tImage %d: %s\n"), img, desc); } else { (void) printf(gettext("\tImage %d:\n"), img); } for (s = 0; s < nslots; s++) { uint32_t attr; char *version; if (nvlist_lookup_uint32(slots[s], DDI_UFM_NV_SLOT_ATTR, &attr) != 0) { attr = 0; } if (nvlist_lookup_string(slots[s], DDI_UFM_NV_SLOT_VERSION, &version) != 0) { version = ""; } printf(gettext("\t Slot %d (%c|%c|%c): %s\n"), s, attr & DDI_UFM_ATTR_READABLE ? 'r' : '-', attr & DDI_UFM_ATTR_WRITEABLE ? 'w' : '-', attr & DDI_UFM_ATTR_ACTIVE ? 'a' : '-', attr & DDI_UFM_ATTR_EMPTY ? "" : version); } } done: (void) printf("\n\n"); return (FWFLASH_SUCCESS); } void fw_cleanup(struct devicelist *flashdev) { ufmfw_flashdev_free(flashdev); } #pragma init(ufmfw_init) static void ufmfw_init(void) { ufmfw_ufm_fd = open("/dev/ufm", O_RDONLY); if (ufmfw_ufm_fd < 0) { logmsg(MSG_ERROR, gettext("%s: failed to open /dev/ufm: %s\n"), drivername, strerror(errno)); return; } ufmfw_pcidb = pcidb_open(PCIDB_VERSION); if (ufmfw_pcidb == NULL) { logmsg(MSG_ERROR, gettext("%s: failed to open libpcidb: %s\n"), drivername, strerror(errno)); return; } ufmfw_ready = B_TRUE; } #pragma fini(ufmfw_fini) static void ufmfw_fini(void) { pcidb_close(ufmfw_pcidb); if (ufmfw_ufm_fd >= 0) { (void) close(ufmfw_ufm_fd); } ufmfw_ready = B_FALSE; }