xref: /illumos-gate/usr/src/lib/fm/topo/libtopo/common/dev.c (revision fe231ea6f3cdffee825d2e92e1a4639b3bc796b7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2023 Oxide Computer Company
25  */
26 
27 #include <limits.h>
28 #include <strings.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <stdio.h>
32 #include <alloca.h>
33 #include <devid.h>
34 #include <sys/stat.h>
35 #include <libnvpair.h>
36 #include <fm/topo_mod.h>
37 #include <fm/fmd_fmri.h>
38 #include <sys/fm/protocol.h>
39 
40 #include <topo_method.h>
41 #include <topo_subr.h>
42 #include <dev.h>
43 
44 static int dev_enum(topo_mod_t *, tnode_t *, const char *, topo_instance_t,
45     topo_instance_t, void *, void *);
46 static void dev_release(topo_mod_t *, tnode_t *);
47 static int dev_fmri_nvl2str(topo_mod_t *, tnode_t *, topo_version_t,
48     nvlist_t *, nvlist_t **);
49 static int dev_fmri_str2nvl(topo_mod_t *, tnode_t *, topo_version_t,
50     nvlist_t *, nvlist_t **);
51 static int dev_fmri_create_meth(topo_mod_t *, tnode_t *, topo_version_t,
52     nvlist_t *, nvlist_t **);
53 static int dev_fmri_present(topo_mod_t *, tnode_t *, topo_version_t,
54     nvlist_t *, nvlist_t **);
55 static int dev_fmri_replaced(topo_mod_t *, tnode_t *, topo_version_t,
56     nvlist_t *, nvlist_t **);
57 static int dev_fmri_unusable(topo_mod_t *, tnode_t *, topo_version_t,
58     nvlist_t *, nvlist_t **);
59 static int dev_fmri_service_state(topo_mod_t *, tnode_t *, topo_version_t,
60     nvlist_t *, nvlist_t **);
61 
62 static const topo_method_t dev_methods[] = {
63 	{ TOPO_METH_NVL2STR, TOPO_METH_NVL2STR_DESC, TOPO_METH_NVL2STR_VERSION,
64 	    TOPO_STABILITY_INTERNAL, dev_fmri_nvl2str },
65 	{ TOPO_METH_STR2NVL, TOPO_METH_STR2NVL_DESC, TOPO_METH_STR2NVL_VERSION,
66 	    TOPO_STABILITY_INTERNAL, dev_fmri_str2nvl },
67 	{ TOPO_METH_FMRI, TOPO_METH_FMRI_DESC, TOPO_METH_FMRI_VERSION,
68 	    TOPO_STABILITY_INTERNAL, dev_fmri_create_meth },
69 	{ TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC, TOPO_METH_PRESENT_VERSION,
70 	    TOPO_STABILITY_INTERNAL, dev_fmri_present },
71 	{ TOPO_METH_REPLACED, TOPO_METH_REPLACED_DESC,
72 	    TOPO_METH_REPLACED_VERSION, TOPO_STABILITY_INTERNAL,
73 	    dev_fmri_replaced },
74 	{ TOPO_METH_UNUSABLE, TOPO_METH_UNUSABLE_DESC,
75 	    TOPO_METH_UNUSABLE_VERSION, TOPO_STABILITY_INTERNAL,
76 	    dev_fmri_unusable },
77 	{ TOPO_METH_SERVICE_STATE, TOPO_METH_SERVICE_STATE_DESC,
78 	    TOPO_METH_SERVICE_STATE_VERSION, TOPO_STABILITY_INTERNAL,
79 	    dev_fmri_service_state },
80 	{ NULL }
81 };
82 
83 static const topo_modops_t dev_ops =
84 	{ dev_enum, dev_release };
85 static const topo_modinfo_t dev_info =
86 	{ "dev", FM_FMRI_SCHEME_DEV, DEV_VERSION, &dev_ops };
87 
88 int
89 dev_init(topo_mod_t *mod, topo_version_t version)
90 {
91 	if (getenv("TOPOHCDEBUG"))
92 		topo_mod_setdebug(mod);
93 	topo_mod_dprintf(mod, "initializing dev builtin\n");
94 
95 	if (version != DEV_VERSION)
96 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
97 
98 	if (topo_mod_register(mod, &dev_info, TOPO_VERSION) != 0) {
99 		topo_mod_dprintf(mod, "failed to register dev_info: "
100 		    "%s\n", topo_mod_errmsg(mod));
101 		return (-1);
102 	}
103 
104 	return (0);
105 }
106 
107 void
108 dev_fini(topo_mod_t *mod)
109 {
110 	topo_mod_unregister(mod);
111 }
112 
113 static int
114 dev_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
115     topo_instance_t min, topo_instance_t max, void *notused1, void *notused2)
116 {
117 	/*
118 	 * Methods are registered, but there is no enumeration.  Should
119 	 * enumeration be added be sure to cater for global vs non-global
120 	 * zones.
121 	 */
122 	(void) topo_method_register(mod, pnode, dev_methods);
123 	return (0);
124 }
125 
126 static void
127 dev_release(topo_mod_t *mod, tnode_t *node)
128 {
129 	topo_method_unregister_all(mod, node);
130 }
131 
132 static size_t
133 fmri_nvl2str(nvlist_t *nvl, char *buf, size_t buflen)
134 {
135 	char *devid = NULL, *tpl0id = NULL;
136 	char *devpath = NULL;
137 	size_t size = 0;
138 	uint8_t version;
139 	int err;
140 
141 	if (nvlist_lookup_uint8(nvl, FM_VERSION, &version) != 0 ||
142 	    version > FM_DEV_SCHEME_VERSION)
143 		return (0);
144 
145 	/* Get devid, if present */
146 	err = nvlist_lookup_string(nvl, FM_FMRI_DEV_ID, &devid);
147 	if (err != 0 && err != ENOENT)
148 		return (0);
149 
150 	/* Get target-port-l0id, if present */
151 	err = nvlist_lookup_string(nvl, FM_FMRI_DEV_TGTPTLUN0, &tpl0id);
152 	if (err != 0 && err != ENOENT)
153 		return (0);
154 
155 	/* There must be a device path present */
156 	err = nvlist_lookup_string(nvl, FM_FMRI_DEV_PATH, &devpath);
157 	if (err != 0 || devpath == NULL)
158 		return (0);
159 
160 	/*
161 	 * dev:///
162 	 *
163 	 * The dev scheme does not render fmri authority information
164 	 * in the string form of an fmri.  It is meaningless to
165 	 * transmit a dev scheme fmri outside of the immediate fault
166 	 * manager.
167 	 */
168 	if (!topo_fmristr_build(&size, buf, buflen,
169 	    FM_FMRI_SCHEME_DEV, NULL, ":///")) {
170 		return (0);
171 	}
172 
173 	/* device-id part, topo_fmristr_build does nothing if devid is NULL */
174 	if (!topo_fmristr_build(&size, buf, buflen,
175 	    devid, ":" FM_FMRI_DEV_ID "=", NULL)) {
176 		return (0);
177 	}
178 
179 	/* target-port-l0id part */
180 	if (!topo_fmristr_build(&size, buf, buflen,
181 	    tpl0id, ":" FM_FMRI_DEV_TGTPTLUN0 "=", NULL)) {
182 		return (0);
183 	}
184 
185 	/*
186 	 * device-path part; the devpath should always start with a /
187 	 * so you'd think we don't need to add a further / prefix here;
188 	 * however past implementation has always added the / if
189 	 * there is a devid component so we continue to do that
190 	 * so strings match exactly as before.  So we can have:
191 	 *
192 	 *	dev:////pci@0,0/...
193 	 *	dev:///<devid-and-tpl0>//pci@0,0/...
194 	 *
195 	 *	where <devid-and-tpl0> =
196 	 *			[:devid=<devid>][:target-port-l0id=<tpl0>]
197 	 */
198 	if (!topo_fmristr_build(&size, buf, buflen,
199 	    devpath, devid || tpl0id ? "/" : NULL, NULL)) {
200 		return (0);
201 	}
202 
203 	return (size);
204 }
205 
206 static int
207 dev_fmri_nvl2str(topo_mod_t *mod, tnode_t *node, topo_version_t version,
208     nvlist_t *nvl, nvlist_t **out)
209 {
210 	size_t len;
211 	char *name = NULL;
212 	nvlist_t *fmristr;
213 
214 	if (version > TOPO_METH_NVL2STR_VERSION)
215 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
216 
217 	if ((len = fmri_nvl2str(nvl, NULL, 0)) == 0 ||
218 	    (name = topo_mod_alloc(mod, len + 1)) == NULL ||
219 	    fmri_nvl2str(nvl, name, len + 1) == 0) {
220 		if (name != NULL)
221 			topo_mod_free(mod, name, len + 1);
222 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
223 	}
224 
225 	if (topo_mod_nvalloc(mod, &fmristr, NV_UNIQUE_NAME) != 0)
226 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
227 	if (nvlist_add_string(fmristr, "fmri-string", name) != 0) {
228 		topo_mod_free(mod, name, len + 1);
229 		nvlist_free(fmristr);
230 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
231 	}
232 	topo_mod_free(mod, name, len + 1);
233 	*out = fmristr;
234 
235 	return (0);
236 }
237 
238 static int
239 dev_fmri_str2nvl(topo_mod_t *mod, tnode_t *node, topo_version_t version,
240     nvlist_t *in, nvlist_t **out)
241 {
242 	char *cur, *devid = NULL, *tpl0id = NULL;
243 	char *str, *strcp;
244 	nvlist_t *fmri;
245 	char *devpath;
246 	size_t len;
247 	int err;
248 
249 	if (version > TOPO_METH_STR2NVL_VERSION)
250 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
251 
252 	if (nvlist_lookup_string(in, "fmri-string", &str) != 0)
253 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
254 
255 	len = strlen(str);
256 
257 	/*
258 	 * We're expecting a string version of a dev scheme FMRI, and
259 	 * no fmri authority information.
260 	 *
261 	 * The shortest legal string would be "dev:////" (len 8) for a string
262 	 * with no FMRI auth info, no devid or target-port-l0id and
263 	 * an empty devpath string.
264 	 */
265 	if (len < 8 || strncmp(str, "dev:///", 7) != 0)
266 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
267 
268 	strcp = alloca(len + 1);
269 	(void) memcpy(strcp, str, len);
270 	strcp[len] = '\0';
271 	cur = strcp + 7;	/* already parsed "dev:///" */
272 
273 	/*
274 	 * If the first character after the "/" that terminates the (empty)
275 	 * fmri authority is a colon then we have devid and/or target-port-l0id
276 	 * info.  They could be in either order.
277 	 *
278 	 * If not a colon then it must be the / that begins the devpath.
279 	 */
280 	if (*cur == ':') {
281 		char *eos, *part[2];
282 		int i;
283 		/*
284 		 * Look ahead to the "/" that starts the devpath.  If not
285 		 * found or if straight after the : then we're busted.
286 		 */
287 		eos = devpath = strchr(cur, '/');
288 		if (devpath == NULL || devpath == cur + 1)
289 			return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
290 
291 		part[0] = ++cur;
292 
293 		/*
294 		 * Replace the initial "/" of the devpath with a NUL
295 		 * to terminate the string before it.  We'll undo this
296 		 * before rendering devpath.
297 		 */
298 		*eos = '\0';
299 
300 		/*
301 		 * We should now have a NUL-terminated string matching
302 		 * foo=<pat1>[:bar=<pat2>] (we stepped over the initial :)
303 		 * Look for a second colon; if found there must be space
304 		 * after it for the additional component, but no more colons.
305 		 */
306 		if ((part[1] = strchr(cur, ':')) != NULL) {
307 			if (part[1] + 1 == eos ||
308 			    strchr(part[1] + 1, ':') != NULL)
309 				return (topo_mod_seterrno(mod,
310 				    EMOD_FMRI_MALFORM));
311 			*part[1] = '\0'; /* terminate part[0] */
312 			part[1]++;
313 		}
314 
315 		for (i = 0; i < 2; i++) {
316 			char *eq;
317 
318 			if (!part[i])
319 				continue;
320 
321 			if ((eq = strchr(part[i], '=')) == NULL ||
322 			    *(eq + 1) == '\0')
323 				return (topo_mod_seterrno(mod,
324 				    EMOD_FMRI_MALFORM));
325 
326 			*eq = '\0';
327 			if (strcmp(part[i], FM_FMRI_DEV_ID) == 0)
328 				devid = eq + 1;
329 			else if (strcmp(part[i], FM_FMRI_DEV_TGTPTLUN0) == 0)
330 				tpl0id = eq + 1;
331 			else
332 				return (topo_mod_seterrno(mod,
333 				    EMOD_FMRI_MALFORM));
334 		}
335 
336 		if (devid == NULL && tpl0id == NULL)
337 			return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
338 
339 		cur = devpath;	/* initial slash is NULled */
340 	} else if (*cur != '/') {
341 		/* the device-path should start with a slash */
342 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
343 	} else {
344 		devpath = cur;
345 	}
346 
347 	if (topo_mod_nvalloc(mod, &fmri, NV_UNIQUE_NAME) != 0)
348 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
349 
350 	err = nvlist_add_uint8(fmri, FM_VERSION, FM_DEV_SCHEME_VERSION);
351 	err |= nvlist_add_string(fmri, FM_FMRI_SCHEME, FM_FMRI_SCHEME_DEV);
352 
353 	if (devid != NULL)
354 		err |= nvlist_add_string(fmri, FM_FMRI_DEV_ID, devid);
355 
356 	if (tpl0id != NULL)
357 		err |= nvlist_add_string(fmri, FM_FMRI_DEV_TGTPTLUN0, tpl0id);
358 
359 	if (devid != NULL || tpl0id != NULL)
360 		*devpath = '/';	/* we NULed this earlier; put it back */
361 
362 	/* step over repeated initial / in the devpath */
363 	while (*(devpath + 1) == '/')
364 		devpath++;
365 
366 	err |= nvlist_add_string(fmri, FM_FMRI_DEV_PATH, devpath);
367 
368 	if (err != 0) {
369 		nvlist_free(fmri);
370 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
371 	}
372 
373 	*out = fmri;
374 
375 	return (0);
376 }
377 
378 static int
379 dev_fmri_present(topo_mod_t *mod, tnode_t *node, topo_version_t version,
380     nvlist_t *in, nvlist_t **out)
381 {
382 	uint8_t fmversion;
383 	char *devpath = NULL;
384 	uint32_t present;
385 	char *devid = NULL, *path;
386 	ddi_devid_t id;
387 	ddi_devid_t matchid;
388 	di_node_t dnode;
389 	struct stat sb;
390 	int len;
391 
392 	if (version > TOPO_METH_PRESENT_VERSION)
393 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
394 
395 	if (nvlist_lookup_uint8(in, FM_VERSION, &fmversion) != 0 ||
396 	    fmversion > FM_DEV_SCHEME_VERSION ||
397 	    nvlist_lookup_string(in, FM_FMRI_DEV_PATH, &devpath) != 0)
398 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
399 
400 	(void) nvlist_lookup_string(in, FM_FMRI_DEV_ID, &devid);
401 
402 	if (devpath == NULL || strlen(devpath) == 0)
403 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
404 
405 	/*
406 	 * stat() the device node in devfs. This will tell us if the device is
407 	 * present or not. Don't stat the minor,  just the whole device.
408 	 * If the device is present and there is a devid, it must also match.
409 	 * so di_init that one node. No need for DINFOFORCE.
410 	 */
411 	len = strlen(devpath) + strlen("/devices") + 1;
412 	path = topo_mod_alloc(mod, len);
413 	(void) snprintf(path, len, "/devices%s", devpath);
414 	if (devid == NULL) {
415 		if (stat(path, &sb) != -1)
416 			present = 1;
417 		else if ((dnode = di_init("/", DINFOCACHE)) == DI_NODE_NIL)
418 			present = 0;
419 		else {
420 			if (di_lookup_node(dnode, devpath) == DI_NODE_NIL)
421 				present = 0;
422 			else
423 				present = 1;
424 			di_fini(dnode);
425 		}
426 	} else {
427 		if (stat(path, &sb) == -1)
428 			present = 0;
429 		else if ((dnode = di_init(devpath, DINFOCPYONE)) == DI_NODE_NIL)
430 			present = 0;
431 		else {
432 			if ((id = di_devid(dnode)) == NULL ||
433 			    devid_str_decode(devid, &matchid, NULL) != 0)
434 				present = 0;
435 			else {
436 				if (devid_compare(id, matchid) != 0)
437 					present = 0;
438 				else
439 					present = 1;
440 				devid_free(matchid);
441 			}
442 			di_fini(dnode);
443 		}
444 	}
445 	topo_mod_free(mod, path, len);
446 
447 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0)
448 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
449 	if (nvlist_add_uint32(*out, TOPO_METH_PRESENT_RET, present) != 0) {
450 		nvlist_free(*out);
451 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
452 	}
453 
454 	return (0);
455 }
456 
457 static int
458 dev_fmri_replaced(topo_mod_t *mod, tnode_t *node, topo_version_t version,
459     nvlist_t *in, nvlist_t **out)
460 {
461 	uint8_t fmversion;
462 	char *devpath = NULL;
463 	uint32_t rval;
464 	char *devid = NULL, *path;
465 	ddi_devid_t id;
466 	ddi_devid_t matchid;
467 	di_node_t dnode;
468 	struct stat sb;
469 	int len;
470 
471 	if (version > TOPO_METH_REPLACED_VERSION)
472 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
473 
474 	if (nvlist_lookup_uint8(in, FM_VERSION, &fmversion) != 0 ||
475 	    fmversion > FM_DEV_SCHEME_VERSION ||
476 	    nvlist_lookup_string(in, FM_FMRI_DEV_PATH, &devpath) != 0)
477 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
478 
479 	(void) nvlist_lookup_string(in, FM_FMRI_DEV_ID, &devid);
480 
481 	if (devpath == NULL || strlen(devpath) == 0)
482 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
483 
484 	/*
485 	 * stat() the device node in devfs. This will tell us if the device is
486 	 * present or not. Don't stat the minor,  just the whole device.
487 	 * If the device is present and there is a devid, it must also match.
488 	 * so di_init that one node. No need for DINFOFORCE.
489 	 */
490 	len = strlen(devpath) + strlen("/devices") + 1;
491 	path = topo_mod_alloc(mod, len);
492 	(void) snprintf(path, len, "/devices%s", devpath);
493 	if (devid == NULL) {
494 		if (stat(path, &sb) != -1)
495 			rval = FMD_OBJ_STATE_UNKNOWN;
496 		else if ((dnode = di_init("/", DINFOCACHE)) == DI_NODE_NIL)
497 			rval = FMD_OBJ_STATE_UNKNOWN;
498 		else {
499 			if (di_lookup_node(dnode, devpath) == DI_NODE_NIL)
500 				rval = FMD_OBJ_STATE_UNKNOWN;
501 			else
502 				rval = FMD_OBJ_STATE_UNKNOWN;
503 			di_fini(dnode);
504 		}
505 	} else {
506 		if (stat(path, &sb) == -1)
507 			rval = FMD_OBJ_STATE_UNKNOWN;
508 		else if ((dnode = di_init(devpath, DINFOCPYONE)) == DI_NODE_NIL)
509 			rval = FMD_OBJ_STATE_UNKNOWN;
510 		else {
511 			if ((id = di_devid(dnode)) == NULL ||
512 			    devid_str_decode(devid, &matchid, NULL) != 0)
513 				rval = FMD_OBJ_STATE_UNKNOWN;
514 			else {
515 				if (devid_compare(id, matchid) != 0)
516 					rval = FMD_OBJ_STATE_REPLACED;
517 				else
518 					rval = FMD_OBJ_STATE_STILL_PRESENT;
519 				devid_free(matchid);
520 			}
521 			di_fini(dnode);
522 		}
523 	}
524 	topo_mod_free(mod, path, len);
525 
526 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0)
527 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
528 	if (nvlist_add_uint32(*out, TOPO_METH_REPLACED_RET, rval) != 0) {
529 		nvlist_free(*out);
530 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
531 	}
532 
533 	return (0);
534 }
535 
536 static int
537 dev_fmri_unusable(topo_mod_t *mod, tnode_t *node, topo_version_t version,
538     nvlist_t *in, nvlist_t **out)
539 {
540 	di_node_t dnode;
541 	uint8_t fmversion;
542 	char *devpath = NULL;
543 	uint32_t unusable;
544 	uint_t state;
545 
546 	if (version > TOPO_METH_UNUSABLE_VERSION)
547 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
548 
549 	if (nvlist_lookup_uint8(in, FM_VERSION, &fmversion) != 0 ||
550 	    fmversion > FM_DEV_SCHEME_VERSION ||
551 	    nvlist_lookup_string(in, FM_FMRI_DEV_PATH, &devpath) != 0)
552 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
553 
554 	if (devpath == NULL)
555 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
556 
557 	if ((dnode = di_init(devpath, DINFOCPYONE)) == DI_NODE_NIL) {
558 		if (errno != ENXIO)
559 			return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
560 		unusable = 1;
561 	} else {
562 		uint_t retired = di_retired(dnode);
563 		state = di_state(dnode);
564 		if (retired || (state & (DI_DEVICE_OFFLINE | DI_DEVICE_DOWN |
565 		    DI_BUS_QUIESCED | DI_BUS_DOWN)))
566 			unusable = 1;
567 		else
568 			unusable = 0;
569 		di_fini(dnode);
570 	}
571 
572 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0)
573 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
574 	if (nvlist_add_uint32(*out, TOPO_METH_UNUSABLE_RET, unusable) != 0) {
575 		nvlist_free(*out);
576 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
577 	}
578 
579 	return (0);
580 }
581 
582 static int
583 dev_fmri_service_state(topo_mod_t *mod, tnode_t *node, topo_version_t version,
584     nvlist_t *in, nvlist_t **out)
585 {
586 	di_node_t dnode;
587 	uint8_t fmversion;
588 	char *devpath = NULL;
589 	uint32_t service_state;
590 	uint_t state;
591 
592 	if (version > TOPO_METH_SERVICE_STATE_VERSION)
593 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
594 
595 	if (nvlist_lookup_uint8(in, FM_VERSION, &fmversion) != 0 ||
596 	    fmversion > FM_DEV_SCHEME_VERSION ||
597 	    nvlist_lookup_string(in, FM_FMRI_DEV_PATH, &devpath) != 0)
598 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
599 
600 	if (devpath == NULL)
601 		return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM));
602 
603 	if ((dnode = di_init(devpath, DINFOCPYONE)) == DI_NODE_NIL) {
604 		if (errno != ENXIO)
605 			return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
606 		service_state = FMD_SERVICE_STATE_UNUSABLE;
607 	} else {
608 		uint_t retired = di_retired(dnode);
609 		state = di_state(dnode);
610 		if (retired || (state & (DI_DEVICE_OFFLINE | DI_DEVICE_DOWN |
611 		    DI_BUS_QUIESCED | DI_BUS_DOWN)))
612 			service_state = FMD_SERVICE_STATE_UNUSABLE;
613 		else if (state & DI_DEVICE_DEGRADED)
614 			service_state = FMD_SERVICE_STATE_DEGRADED;
615 		else
616 			service_state = FMD_SERVICE_STATE_OK;
617 		di_fini(dnode);
618 	}
619 
620 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) != 0)
621 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
622 	if (nvlist_add_uint32(*out, TOPO_METH_SERVICE_STATE_RET,
623 	    service_state) != 0) {
624 		nvlist_free(*out);
625 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
626 	}
627 
628 	return (0);
629 }
630 
631 static nvlist_t *
632 dev_fmri_create(topo_mod_t *mp, const char *id, const char *path)
633 {
634 	nvlist_t *out = NULL;
635 	int e;
636 
637 	if (topo_mod_nvalloc(mp, &out, NV_UNIQUE_NAME) != 0) {
638 		(void) topo_mod_seterrno(mp, EMOD_FMRI_NVL);
639 		return (NULL);
640 	}
641 	e = nvlist_add_string(out, FM_FMRI_SCHEME, FM_FMRI_SCHEME_DEV);
642 	e |= nvlist_add_uint8(out, FM_VERSION, FM_DEV_SCHEME_VERSION);
643 	e |= nvlist_add_string(out, FM_FMRI_DEV_PATH, path);
644 
645 	if (id != NULL)
646 		e |= nvlist_add_string(out, FM_FMRI_DEV_ID, id);
647 
648 	if (e == 0)
649 		return (out);
650 
651 	topo_mod_dprintf(mp, "construction of dev nvl failed");
652 	(void) topo_mod_seterrno(mp, EMOD_FMRI_NVL);
653 	nvlist_free(out);
654 	return (NULL);
655 }
656 
657 static int
658 dev_fmri_create_meth(topo_mod_t *mp, tnode_t *node, topo_version_t version,
659     nvlist_t *in, nvlist_t **out)
660 {
661 	nvlist_t *args = NULL;
662 	char *path, *id = NULL;
663 
664 	if (version > TOPO_METH_FMRI_VERSION)
665 		return (topo_mod_seterrno(mp, EMOD_VER_NEW));
666 
667 	if (nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_NVL, &args) != 0 ||
668 	    nvlist_lookup_string(args, FM_FMRI_DEV_PATH, &path) != 0) {
669 		topo_mod_dprintf(mp, "no path string in method argument\n");
670 		return (topo_mod_seterrno(mp, EMOD_METHOD_INVAL));
671 	}
672 
673 	(void) nvlist_lookup_string(args, FM_FMRI_DEV_ID, &id);
674 
675 	if ((*out = dev_fmri_create(mp, id, path)) == NULL)
676 		return (-1);
677 	return (0);
678 }
679