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
topo_ufm_devinfo_image(topo_mod_t * mod,tnode_t * pn,topo_instance_t inst,nvlist_t * nvl)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
topo_ufm_devinfo(topo_mod_t * mod,tnode_t * pn,topo_instance_t min,topo_instance_t max,topo_ufm_devinfo_t * tud)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
topo_ufm_enum(topo_mod_t * mod,tnode_t * pnode,const char * name,topo_instance_t min,topo_instance_t max,void * modarg,void * data)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
_topo_init(topo_mod_t * mod,topo_version_t version)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
_topo_fini(topo_mod_t * mod)361 _topo_fini(topo_mod_t *mod)
362 {
363 topo_mod_unregister(mod);
364 }
365