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 2023 Oxide Computer Company 14 */ 15 16 /* 17 * This implements logic to enumerate UFM nodes based on different data sources 18 * in the system. Being in a module allows it to be used by several other 19 * modules in the system and means that we can encapsulate all of the messy 20 * logic here. 21 * 22 * Our module is not designed to operate from a topo map right now. Instead, it 23 * is expected that callers are going to pass the enumeration argument in. 24 */ 25 26 #include <sys/fm/protocol.h> 27 #include <fm/topo_mod.h> 28 #include <fm/topo_hc.h> 29 #include <string.h> 30 #include <sys/ddi_ufm.h> 31 #include <sys/types.h> 32 #include <sys/stat.h> 33 #include <fcntl.h> 34 #include <unistd.h> 35 #include <errno.h> 36 37 #include "topo_ufm.h" 38 39 /* 40 * Attempt to create the specific UFM image that is listed in the nvl. 41 */ 42 static int 43 topo_ufm_devinfo_image(topo_mod_t *mod, tnode_t *pn, topo_instance_t inst, 44 nvlist_t *nvl) 45 { 46 int ret; 47 char *desc; 48 tnode_t *img_tn; 49 nvlist_t **slots; 50 uint_t nslots; 51 52 ret = nvlist_lookup_string(nvl, DDI_UFM_NV_IMAGE_DESC, &desc); 53 if (ret != 0) { 54 topo_mod_dprintf(mod, "failed to look up %s: %s", 55 DDI_UFM_NV_IMAGE_DESC, strerror(ret)); 56 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 57 } 58 59 ret = nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGE_SLOTS, &slots, 60 &nslots); 61 if (ret != 0) { 62 topo_mod_dprintf(mod, "failed to look up %s: %s", 63 DDI_UFM_NV_IMAGE_SLOTS, strerror(ret)); 64 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 65 } 66 67 if (nslots == 0) { 68 topo_mod_dprintf(mod, "refusing to create UFM image with zero " 69 "slots"); 70 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 71 } 72 73 img_tn = topo_mod_create_ufm(mod, pn, inst, desc, NULL); 74 if (img_tn == NULL) { 75 topo_mod_dprintf(mod, "failed to create ufm image %" PRIu64 76 "on %s[%" PRIu64 "]: %s", inst, topo_node_name(pn), 77 topo_node_instance(pn), topo_mod_errmsg(mod)); 78 return (-1); 79 } 80 81 if (topo_node_range_create(mod, img_tn, SLOT, 0, nslots - 1) != 0) { 82 topo_mod_dprintf(mod, "failed to create node range %s[0, %u]: " 83 "%s", SLOT, nslots - 1, topo_mod_errmsg(mod)); 84 topo_node_unbind(img_tn); 85 return (-1); 86 } 87 88 /* 89 * Go through and create the slots. Once we've reached this part, it's 90 * hard to clean up the UFM image node as it will have ranges and 91 * potentially children (because we've been looping). We'll have to hope 92 * that the enumeration error is sufficient for someone taking a 93 * snapshot. 94 * 95 * A slot must have an attributes property. If that is not there, we 96 * can't do much more than that. It must have a version, but only if the 97 * empty attribute is not set! There may be misc. extra data, which 98 * we'll include but don't care if we can get it or not. 99 */ 100 for (uint_t i = 0; i < nslots; i++) { 101 topo_ufm_slot_info_t slot = { 0 }; 102 uint32_t attr, rw; 103 char *vers; 104 105 slot.usi_slotid = i; 106 ret = nvlist_lookup_uint32(slots[i], DDI_UFM_NV_SLOT_ATTR, 107 &attr); 108 if (ret != 0) { 109 topo_mod_dprintf(mod, "failed to get required %s " 110 "property from slot %u: %s", DDI_UFM_NV_SLOT_ATTR, 111 i, strerror(errno)); 112 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 113 } 114 115 slot.usi_version = vers; 116 rw = attr & (DDI_UFM_ATTR_READABLE | DDI_UFM_ATTR_WRITEABLE); 117 switch (rw) { 118 case DDI_UFM_ATTR_READABLE | DDI_UFM_ATTR_WRITEABLE: 119 slot.usi_mode = TOPO_UFM_SLOT_MODE_RW; 120 break; 121 case DDI_UFM_ATTR_READABLE: 122 slot.usi_mode = TOPO_UFM_SLOT_MODE_RO; 123 break; 124 case DDI_UFM_ATTR_WRITEABLE: 125 slot.usi_mode = TOPO_UFM_SLOT_MODE_WO; 126 break; 127 default: 128 slot.usi_mode = TOPO_UFM_SLOT_MODE_NONE; 129 break; 130 } 131 132 slot.usi_active = (attr & DDI_UFM_ATTR_ACTIVE) != 0; 133 134 vers = NULL; 135 if ((attr & DDI_UFM_ATTR_EMPTY) == 0 && 136 (ret = nvlist_lookup_string(slots[i], 137 DDI_UFM_NV_SLOT_VERSION, &vers)) != 0) { 138 topo_mod_dprintf(mod, "failed to get required %s " 139 "property from non-empty slot %u: %s", 140 DDI_UFM_NV_SLOT_VERSION, i, strerror(errno)); 141 return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); 142 } 143 slot.usi_version = vers; 144 145 /* 146 * If there are additional attributes that exist, then leverage 147 * those. However, we'll ignore failures of this because it's 148 * optional. 149 */ 150 slot.usi_extra = NULL; 151 (void) nvlist_lookup_nvlist(slots[i], DDI_UFM_NV_SLOT_MISC, 152 &slot.usi_extra); 153 154 if (topo_mod_create_ufm_slot(mod, img_tn, &slot) == NULL) { 155 topo_mod_dprintf(mod, "failed to create ufm slot %u on " 156 "image %" PRIu64 ": %s", i, inst, 157 topo_mod_errmsg(mod)); 158 return (-1); 159 } 160 } 161 162 return (0); 163 } 164 165 /* 166 * Utlilizing the devinfo tree create information about the given ufm. We use 167 * [min, max] as a way to figure out which UFMs to create and treat this as a 168 * way to slice up parts of the range. We will only actually create nodes based 169 * on how many are present. 170 */ 171 static int 172 topo_ufm_devinfo(topo_mod_t *mod, tnode_t *pn, topo_instance_t min, 173 topo_instance_t max, topo_ufm_devinfo_t *tud) 174 { 175 int fd = -1; 176 int ret; 177 ufm_ioc_getcaps_t caps = { 0 }; 178 ufm_ioc_bufsz_t bufsz = { 0 }; 179 ufm_ioc_report_t report = { 0 }; 180 nvlist_t *nvl = NULL, **img_nvl; 181 uint_t nimg; 182 183 if (tud->tud_path == NULL) { 184 topo_mod_dprintf(mod, "missing required devfs path"); 185 return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); 186 } 187 188 /* 189 * We check the path size here now so we can guarantee that all of the 190 * rest of the string copying will fit inside our buffers and therefore 191 * we ignore the strlcpy() result. 192 */ 193 if (strlen(tud->tud_path) >= MAXPATHLEN) { 194 topo_mod_dprintf(mod, "given devfs path exceeds MAXPATHLEN " 195 "buffers, cannot continue"); 196 return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM)); 197 } 198 199 fd = open(DDI_UFM_DEV, O_RDONLY); 200 if (fd < 0) { 201 topo_mod_dprintf(mod, "failed to open %s: %s", DDI_UFM_DEV, 202 strerror(errno)); 203 return (topo_mod_seterrno(mod, EMOD_PARTIAL_ENUM)); 204 } 205 206 caps.ufmg_version = DDI_UFM_CURRENT_VERSION; 207 (void) strlcpy(caps.ufmg_devpath, tud->tud_path, 208 sizeof (caps.ufmg_devpath)); 209 210 /* 211 * We swallow ioctl errors on purpose. The device driver may not support 212 * UFMs at all. Similarly, if it doesn't actually support reporting UFM 213 * information, then we're done here. 214 */ 215 if (ioctl(fd, UFM_IOC_GETCAPS, &caps) != 0) { 216 topo_mod_dprintf(mod, "failed to get UFM capabilities for " 217 "%s: %s", tud->tud_path, strerror(errno)); 218 ret = 0; 219 goto out; 220 } 221 222 if ((caps.ufmg_caps & DDI_UFM_CAP_REPORT) == 0) { 223 topo_mod_dprintf(mod, "path %s does not support UFM reporting", 224 tud->tud_path); 225 ret = 0; 226 goto out; 227 } 228 229 bufsz.ufbz_version = DDI_UFM_CURRENT_VERSION; 230 (void) strlcpy(bufsz.ufbz_devpath, tud->tud_path, 231 sizeof (bufsz.ufbz_devpath)); 232 if (ioctl(fd, UFM_IOC_REPORTSZ, &bufsz) != 0) { 233 topo_mod_dprintf(mod, "failed to get UFM buffer size for " 234 "%s: %s", tud->tud_path, strerror(errno)); 235 ret = topo_mod_seterrno(mod, EMOD_UNKNOWN); 236 goto out; 237 } 238 239 report.ufmr_version = DDI_UFM_CURRENT_VERSION; 240 report.ufmr_bufsz = bufsz.ufbz_size; 241 report.ufmr_buf = topo_mod_alloc(mod, bufsz.ufbz_size); 242 if (report.ufmr_buf == NULL) { 243 ret = topo_mod_seterrno(mod, EMOD_NOMEM); 244 goto out; 245 } 246 (void) strlcpy(report.ufmr_devpath, tud->tud_path, 247 sizeof (report.ufmr_devpath)); 248 if (ioctl(fd, UFM_IOC_REPORT, &report) != 0) { 249 topo_mod_dprintf(mod, "failed to retrieve UFM report for " 250 "%s: %s", tud->tud_path, strerror(errno)); 251 ret = topo_mod_seterrno(mod, EMOD_UNKNOWN); 252 goto out; 253 } 254 255 ret = nvlist_unpack(report.ufmr_buf, report.ufmr_bufsz, &nvl, 0); 256 if (ret != 0) { 257 topo_mod_dprintf(mod, "failed to unpack report nvlist from " 258 "%s: %s", tud->tud_path, strerror(ret)); 259 ret = topo_mod_seterrno(mod, EMOD_UNKNOWN); 260 goto out; 261 } 262 263 /* 264 * First see if the report actually gave us images. If there are no 265 * images, then there is nothing to do. 266 */ 267 ret = nvlist_lookup_nvlist_array(nvl, DDI_UFM_NV_IMAGES, &img_nvl, 268 &nimg); 269 if (ret != 0) { 270 topo_mod_dprintf(mod, "failed to retrieve key %s from " 271 "report: %s", DDI_UFM_NV_IMAGES, strerror(ret)); 272 ret = topo_mod_seterrno(mod, EMOD_UNKNOWN); 273 goto out; 274 } 275 276 if (nimg == 0) { 277 ret = 0; 278 goto out; 279 } 280 281 max = MIN(max, nimg - 1); 282 if (topo_node_range_create(mod, pn, UFM, min, max) != 0) { 283 topo_mod_dprintf(mod, "failed to create node range %s[%" PRIu64 284 ", %" PRIu64 "]: %s", UFM, min, max, topo_mod_errmsg(mod)); 285 ret = -1; 286 goto out; 287 } 288 289 for (topo_instance_t i = min; i <= max; i++) { 290 ret = topo_ufm_devinfo_image(mod, pn, i, img_nvl[i]); 291 if (ret != 0) { 292 goto out; 293 } 294 } 295 296 out: 297 nvlist_free(nvl); 298 if (report.ufmr_buf != NULL) { 299 topo_mod_free(mod, report.ufmr_buf, bufsz.ufbz_size); 300 } 301 302 if (fd >= 0) { 303 (void) close(fd); 304 } 305 return (ret); 306 } 307 308 static int 309 topo_ufm_enum(topo_mod_t *mod, tnode_t *pnode, const char *name, 310 topo_instance_t min, topo_instance_t max, void *modarg, void *data) 311 { 312 topo_ufm_method_t *mp; 313 314 topo_mod_dprintf(mod, "asked to enum %s [%" PRIu64 ", %" PRIu64 "] on " 315 "%s%" PRIu64 "\n", name, min, max, topo_node_name(pnode), 316 topo_node_instance(pnode)); 317 318 if (strcmp(name, UFM) != 0) { 319 topo_mod_dprintf(mod, "cannot enumerate %s: unknown type", 320 name); 321 return (-1); 322 } 323 324 if (data == NULL) { 325 topo_mod_dprintf(mod, "cannot enumerate %s: missing required " 326 "data", name); 327 return (-1); 328 } 329 330 mp = data; 331 switch (*mp) { 332 case TOPO_UFM_M_DEVINFO: 333 return (topo_ufm_devinfo(mod, pnode, min, max, data)); 334 default: 335 topo_mod_dprintf(mod, "encountered unknown UFM enum method: " 336 "0x%x, bailing", *mp); 337 return (-1); 338 } 339 340 } 341 342 static const topo_modops_t topo_ufm_ops = { 343 topo_ufm_enum, NULL 344 }; 345 346 static topo_modinfo_t topo_ufm_mod = { 347 "UFM Enumerator", FM_FMRI_SCHEME_HC, TOPO_MOD_UFM_VERS, &topo_ufm_ops 348 }; 349 350 int 351 _topo_init(topo_mod_t *mod, topo_version_t version) 352 { 353 if (getenv("TOPOUFMDEBUG") != NULL) { 354 topo_mod_setdebug(mod); 355 } 356 357 return (topo_mod_register(mod, &topo_ufm_mod, TOPO_VERSION)); 358 } 359 360 void 361 _topo_fini(topo_mod_t *mod) 362 { 363 topo_mod_unregister(mod); 364 } 365