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