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 (c) 2019, Joyent, Inc.
14  */
15 
16 #include <assert.h>
17 #include <fcntl.h>
18 #include <fm/libtopo.h>
19 #include <fm/topo_mod.h>
20 #include <fm/topo_method.h>
21 #ifdef	__x86
22 #include <sys/mc.h>
23 #endif
24 #include <sys/fm/protocol.h>
25 #include <string.h>
26 #include <unistd.h>
27 
28 typedef struct smb_enum_data {
29 	topo_mod_t	*sme_mod;
30 	tnode_t		*sme_pnode;
31 	tnode_t		*sme_slotnode;
32 	topo_instance_t	sme_slot_inst;
33 	topo_instance_t	sme_slot_maxinst;
34 	smbios_info_t	*sme_smb_info;
35 	char		*sme_slot_form;
36 } smb_enum_data_t;
37 
38 static const topo_method_t slot_methods[] = {
39 	{ TOPO_METH_OCCUPIED, TOPO_METH_OCCUPIED_DESC,
40 	    TOPO_METH_OCCUPIED_VERSION, TOPO_STABILITY_INTERNAL,
41 	    topo_mod_hc_occupied },
42 	{ NULL }
43 };
44 
45 /*
46  * This function serves two purposes.  It filters out memory devices that
47  * don't have a formfactor that represents a reasonably modern DIMM-like
48  * device (and hence not a device we're interested in enumerating).  It also
49  * converts the numeric SMBIOS type representation to a more generic TOPO dimm
50  * type.
51  *
52  * Caller must free the returned string.
53  */
54 static char *
55 distill_dimm_form(topo_mod_t *mod, smbios_memdevice_t *smb_md)
56 {
57 	switch (smb_md->smbmd_form) {
58 	case (SMB_MDFF_DIMM):
59 		return (topo_mod_strdup(mod, TOPO_DIMM_SLOT_FORM_DIMM));
60 	case (SMB_MDFF_SODIMM):
61 		return (topo_mod_strdup(mod, TOPO_DIMM_SLOT_FORM_SODIMM));
62 	case (SMB_MDFF_FBDIMM):
63 		return (topo_mod_strdup(mod, TOPO_DIMM_SLOT_FORM_FBDIMM));
64 	default:
65 		topo_mod_dprintf(mod, "skipping device with form factor 0x%x",
66 		    smb_md->smbmd_form);
67 		return (NULL);
68 	}
69 }
70 
71 static char *
72 smbios2topotype(topo_mod_t *mod, uint8_t type)
73 {
74 	switch (type) {
75 	case (SMB_MDT_DDR):
76 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_DDR));
77 	case (SMB_MDT_DDR2):
78 	case (SMB_MDT_DDR2FBDIMM):
79 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_DDR2));
80 	case (SMB_MDT_DDR3):
81 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_DDR3));
82 	case (SMB_MDT_DDR4):
83 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_DDR4));
84 	case (SMB_MDT_LPDDR):
85 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_LPDDR));
86 	case (SMB_MDT_LPDDR2):
87 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_LPDDR2));
88 	case (SMB_MDT_LPDDR3):
89 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_LPDDR3));
90 	case (SMB_MDT_LPDDR4):
91 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_LPDDR4));
92 	default:
93 		return (topo_mod_strdup(mod, TOPO_DIMM_TYPE_UNKNOWN));
94 	}
95 }
96 
97 static boolean_t
98 is_valid_string(const char *str)
99 {
100 	if (strcmp(str, SMB_DEFAULT1) != 0 && strcmp(str, SMB_DEFAULT2) != 0 &&
101 	    strcmp(str, SMB_DEFAULT3) != 0 && strlen(str) > 0)
102 		return (B_TRUE);
103 
104 	return (B_FALSE);
105 }
106 
107 static tnode_t *
108 smbios_make_slot(smb_enum_data_t *smed, smbios_memdevice_t *smb_md)
109 {
110 	nvlist_t *auth, *fmri;
111 	tnode_t *slotnode;
112 	topo_mod_t *mod = smed->sme_mod;
113 	topo_pgroup_info_t pgi;
114 	int err;
115 
116 	if ((auth = topo_mod_auth(mod, smed->sme_pnode)) == NULL) {
117 		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
118 		    topo_mod_errmsg(mod));
119 		/* errno set */
120 		return (NULL);
121 	}
122 
123 	if ((fmri = topo_mod_hcfmri(mod, smed->sme_pnode, FM_HC_SCHEME_VERSION,
124 	    SLOT, smed->sme_slot_inst, NULL, auth, NULL, NULL, NULL)) ==
125 	    NULL) {
126 		nvlist_free(auth);
127 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
128 		    topo_mod_errmsg(mod));
129 		/* errno set */
130 		return (NULL);
131 	}
132 	if ((slotnode = topo_node_bind(mod, smed->sme_pnode, SLOT,
133 	    smed->sme_slot_inst, fmri)) == NULL) {
134 		nvlist_free(auth);
135 		nvlist_free(fmri);
136 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
137 		    topo_mod_errmsg(mod));
138 		/* errno set */
139 		return (NULL);
140 	}
141 	nvlist_free(fmri);
142 	fmri = NULL;
143 
144 	/* Create authority and system pgroups */
145 	topo_pgroup_hcset(slotnode, auth);
146 	nvlist_free(auth);
147 
148 	if (topo_node_label_set(slotnode, (char *)smb_md->smbmd_dloc, &err) !=
149 	    0) {
150 		topo_mod_dprintf(mod, "failed to set label on %s=%" PRIu64
151 		    ": %s", SLOT, smed->sme_slot_inst, topo_strerror(err));
152 		(void) topo_mod_seterrno(mod, err);
153 		return (NULL);
154 	}
155 	if (topo_node_fru(smed->sme_pnode, &fmri, NULL, &err) != 0 ||
156 	    topo_node_fru_set(slotnode, fmri, 0, &err) != 0) {
157 		topo_mod_dprintf(mod, "failed to set FRU on %s=%" PRIu64 ": %s",
158 		    SLOT, smed->sme_slot_inst, topo_strerror(err));
159 		nvlist_free(fmri);
160 		(void) topo_mod_seterrno(mod, err);
161 		return (NULL);
162 	}
163 	nvlist_free(fmri);
164 
165 	if (topo_method_register(mod, slotnode, slot_methods) != 0) {
166 		topo_mod_dprintf(mod, "topo_method_register() failed on "
167 		    "%s=%" PRIu64 ": %s", SLOT, smed->sme_slot_inst,
168 		    topo_mod_errmsg(mod));
169 		/* errno set */
170 		return (NULL);
171 	}
172 
173 	pgi.tpi_name = TOPO_PGROUP_SLOT;
174 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
175 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
176 	pgi.tpi_version = TOPO_VERSION;
177 	if (topo_pgroup_create(slotnode, &pgi, &err) != 0 ||
178 	    topo_prop_set_uint32(slotnode, TOPO_PGROUP_SLOT,
179 	    TOPO_PROP_SLOT_TYPE, TOPO_PROP_IMMUTABLE, TOPO_SLOT_TYPE_DIMM,
180 	    &err)) {
181 		topo_mod_dprintf(mod, "failed to create slot properties: %s",
182 		    topo_strerror(err));
183 		(void) topo_mod_seterrno(mod, err);
184 		return (NULL);
185 	}
186 
187 	pgi.tpi_name = TOPO_PGROUP_DIMM_SLOT;
188 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
189 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
190 	pgi.tpi_version = TOPO_VERSION;
191 	if (topo_pgroup_create(slotnode, &pgi, &err) != 0 ||
192 	    topo_prop_set_string(slotnode, TOPO_PGROUP_DIMM_SLOT,
193 	    TOPO_PROP_DIMM_SLOT_FORM, TOPO_PROP_IMMUTABLE, smed->sme_slot_form,
194 	    &err)) {
195 		topo_mod_dprintf(mod, "failed to create slot properties: %s",
196 		    topo_strerror(err));
197 		(void) topo_mod_seterrno(mod, err);
198 		return (NULL);
199 	}
200 	return (slotnode);
201 }
202 
203 static tnode_t *
204 smbios_make_dimm(smb_enum_data_t *smed, smbios_memdevice_t *smb_md)
205 {
206 	nvlist_t *auth, *fmri;
207 	smbios_info_t *smb_info = smed->sme_smb_info;
208 	tnode_t *slotnode = smed->sme_slotnode;
209 	tnode_t *dimmnode, *ret = NULL;
210 	topo_mod_t *mod = smed->sme_mod;
211 	topo_pgroup_info_t pgi;
212 	const char *part = NULL, *rev = NULL, *serial = NULL;
213 	char *type, *manuf = NULL, *prod = NULL, *asset = NULL, *loc = NULL;
214 	int err, rc = 0;
215 
216 	if ((auth = topo_mod_auth(mod, slotnode)) == NULL) {
217 		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
218 		    topo_mod_errmsg(mod));
219 		/* errno set */
220 		return (NULL);
221 	}
222 
223 	if (smed->sme_smb_info != NULL) {
224 		if (is_valid_string(smb_info->smbi_part) == B_TRUE)
225 			part = smb_info->smbi_part;
226 		if (is_valid_string(smb_info->smbi_version) == B_TRUE)
227 			rev = smb_info->smbi_version;
228 		if (is_valid_string(smb_info->smbi_serial) == B_TRUE)
229 			serial = smb_info->smbi_serial;
230 		if (is_valid_string(smb_info->smbi_manufacturer) == B_TRUE)
231 			manuf = topo_mod_clean_str(mod,
232 			    smb_info->smbi_manufacturer);
233 		if (is_valid_string(smb_info->smbi_product) == B_TRUE)
234 			prod = topo_mod_clean_str(mod, smb_info->smbi_product);
235 		if (is_valid_string(smb_info->smbi_asset) == B_TRUE)
236 			asset = topo_mod_clean_str(mod, smb_info->smbi_asset);
237 		if (is_valid_string(smb_info->smbi_location) == B_TRUE)
238 			loc = topo_mod_clean_str(mod, smb_info->smbi_location);
239 	}
240 
241 	if ((fmri = topo_mod_hcfmri(mod, slotnode, FM_HC_SCHEME_VERSION,
242 	    DIMM, 0, NULL, auth, part, rev, serial)) == NULL) {
243 		nvlist_free(auth);
244 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
245 		    topo_mod_errmsg(mod));
246 		/* errno set */
247 		goto err;
248 	}
249 
250 	if (topo_node_range_create(mod, slotnode, DIMM, 0, 0) < 0 ||
251 	    (dimmnode = topo_node_bind(mod, slotnode, DIMM, 0, fmri)) ==
252 	    NULL) {
253 		nvlist_free(auth);
254 		nvlist_free(fmri);
255 		topo_mod_dprintf(mod, "failed to bind dimm node: %s",
256 		    topo_mod_errmsg(mod));
257 		/* errno set */
258 		goto err;
259 	}
260 
261 	/* Create authority and system pgroups */
262 	topo_pgroup_hcset(dimmnode, auth);
263 	nvlist_free(auth);
264 
265 	if (topo_node_fru_set(dimmnode, fmri, 0, &err) != 0) {
266 		topo_mod_dprintf(mod, "failed to set FRU on %s: %s",
267 		    DIMM, topo_strerror(err));
268 		nvlist_free(fmri);
269 		(void) topo_mod_seterrno(mod, err);
270 		goto err;
271 	}
272 	nvlist_free(fmri);
273 
274 	if (topo_node_label_set(dimmnode, (char *)smb_md->smbmd_dloc, &err) !=
275 	    0) {
276 		topo_mod_dprintf(mod, "failed to set label on %s: %s",
277 		    DIMM, topo_strerror(err));
278 		(void) topo_mod_seterrno(mod, err);
279 		goto err;
280 	}
281 
282 	pgi.tpi_name = TOPO_PGROUP_DIMM_PROPS;
283 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
284 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
285 	pgi.tpi_version = TOPO_VERSION;
286 	if (topo_pgroup_create(dimmnode, &pgi, &err) != 0) {
287 		(void) topo_mod_seterrno(mod, err);
288 		goto err;
289 	}
290 
291 	rc += topo_prop_set_uint64(dimmnode, TOPO_PGROUP_DIMM_PROPS, "size",
292 	    TOPO_PROP_IMMUTABLE, smb_md->smbmd_size, &err);
293 	if (rc == 0 && (type = smbios2topotype(mod, smb_md->smbmd_type)) !=
294 	    NULL) {
295 		rc += topo_prop_set_string(dimmnode, TOPO_PGROUP_DIMM_PROPS,
296 		    "type", TOPO_PROP_IMMUTABLE, type, &err);
297 		topo_mod_strfree(mod, type);
298 	}
299 	if (rc == 0 && smb_md->smbmd_set != 0 && smb_md->smbmd_set != 0xFF)
300 		rc += topo_prop_set_uint32(dimmnode, TOPO_PGROUP_DIMM_PROPS,
301 		    "set", TOPO_PROP_IMMUTABLE, smb_md->smbmd_set, &err);
302 	if (rc == 0 && smb_md->smbmd_rank != 0)
303 		rc += topo_prop_set_uint32(dimmnode, TOPO_PGROUP_DIMM_PROPS,
304 		    "rank", TOPO_PROP_IMMUTABLE, smb_md->smbmd_rank, &err);
305 	if (rc == 0 && smb_md->smbmd_clkspeed != 0)
306 		rc += topo_prop_set_uint32(dimmnode, TOPO_PGROUP_DIMM_PROPS,
307 		    "configured-speed", TOPO_PROP_IMMUTABLE,
308 		    smb_md->smbmd_clkspeed, &err);
309 	if (rc == 0 && smb_md->smbmd_speed != 0)
310 		rc += topo_prop_set_uint32(dimmnode, TOPO_PGROUP_DIMM_PROPS,
311 		    "maximum-speed", TOPO_PROP_IMMUTABLE, smb_md->smbmd_speed,
312 		    &err);
313 	if (rc == 0 && smb_md->smbmd_maxvolt != 0)
314 		rc += topo_prop_set_double(dimmnode, TOPO_PGROUP_DIMM_PROPS,
315 		    "maximum-voltage", TOPO_PROP_IMMUTABLE,
316 		    (smb_md->smbmd_maxvolt / 1000), &err);
317 	if (rc == 0 && smb_md->smbmd_minvolt != 0)
318 		rc += topo_prop_set_double(dimmnode, TOPO_PGROUP_DIMM_PROPS,
319 		    "minimum-voltage", TOPO_PROP_IMMUTABLE,
320 		    (smb_md->smbmd_minvolt / 1000), &err);
321 	if (rc == 0 && smb_md->smbmd_confvolt != 0)
322 		rc += topo_prop_set_double(dimmnode, TOPO_PGROUP_DIMM_PROPS,
323 		    "configured-voltage", TOPO_PROP_IMMUTABLE,
324 		    (smb_md->smbmd_confvolt / 1000), &err);
325 	if (rc == 0 && manuf != NULL)
326 		rc += topo_prop_set_string(dimmnode, TOPO_PGROUP_DIMM_PROPS,
327 		    "manufacturer", TOPO_PROP_IMMUTABLE, manuf, &err);
328 	if (rc == 0 && prod != NULL)
329 		rc += topo_prop_set_string(dimmnode, TOPO_PGROUP_DIMM_PROPS,
330 		    "product", TOPO_PROP_IMMUTABLE, prod, &err);
331 	if (rc == 0 && asset != NULL)
332 		rc += topo_prop_set_string(dimmnode, TOPO_PGROUP_DIMM_PROPS,
333 		    "asset-tag", TOPO_PROP_IMMUTABLE, asset, &err);
334 	if (rc == 0 && loc != NULL)
335 		rc += topo_prop_set_string(dimmnode, TOPO_PGROUP_DIMM_PROPS,
336 		    "location", TOPO_PROP_IMMUTABLE, loc, &err);
337 
338 	if (rc != 0) {
339 		topo_mod_dprintf(mod, "error setting properties on %s node",
340 		    DIMM);
341 		(void) topo_mod_seterrno(mod, err);
342 		goto err;
343 	}
344 	ret = dimmnode;
345 err:
346 	topo_mod_strfree(mod, manuf);
347 	topo_mod_strfree(mod, prod);
348 	topo_mod_strfree(mod, asset);
349 	topo_mod_strfree(mod, loc);
350 	return (ret);
351 }
352 
353 static int
354 smbios_enum_memory(smbios_hdl_t *shp, const smbios_struct_t *sp, void *arg)
355 {
356 	smbios_info_t smb_info;
357 	smbios_memdevice_t smb_md;
358 	smb_enum_data_t *smed = arg;
359 	topo_mod_t *mod = smed->sme_mod;
360 	tnode_t *slotnode;
361 
362 	if (sp->smbstr_type != SMB_TYPE_MEMDEVICE)
363 		return (0);
364 
365 	if (smbios_info_memdevice(shp, sp->smbstr_id, &smb_md) != 0) {
366 		topo_mod_dprintf(mod, "libsmbios error");
367 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
368 	}
369 
370 	/*
371 	 * SMB_TYPE_MEMDEVICE records can also be used to represent memory
372 	 * that come in non-DIMM form factors. If we encounter something like
373 	 * that, then we skip over it.
374 	 */
375 	if ((smed->sme_slot_form = distill_dimm_form(mod, &smb_md)) == NULL)
376 		return (0);
377 
378 	if ((slotnode = smbios_make_slot(smed, &smb_md)) == NULL) {
379 		topo_mod_dprintf(mod, "failed to create %s node", SLOT);
380 		topo_mod_strfree(mod, smed->sme_slot_form);
381 		/* errno set */
382 		return (-1);
383 	}
384 	topo_mod_strfree(mod, smed->sme_slot_form);
385 	smed->sme_slotnode = slotnode;
386 
387 	/*
388 	 * A size of zero indicates that the DIMM slot is not populated, so
389 	 * we skip creating a child dimm node and return.
390 	 */
391 	if (smb_md.smbmd_size == 0) {
392 		smed->sme_slot_inst++;
393 		return (0);
394 	}
395 
396 	if (smbios_info_common(shp, sp->smbstr_id, &smb_info) == 0)
397 		smed->sme_smb_info = &smb_info;
398 
399 	if (smbios_make_dimm(smed, &smb_md) == NULL) {
400 		topo_mod_dprintf(mod, "failed to create %s node", DIMM);
401 		/* errno set */
402 		return (-1);
403 	}
404 	/*
405 	 * If we've exceeded our max inst then return non-zero to cause
406 	 * the walk to terminate.
407 	 */
408 	if (++smed->sme_slot_inst > smed->sme_slot_maxinst)
409 		return (1);
410 
411 	return (0);
412 }
413 
414 static int
415 smbios_enum_motherboard(smbios_hdl_t *shp, smb_enum_data_t *smed)
416 {
417 	smbios_struct_t sp;
418 	smbios_bboard_t smb_mb;
419 	smbios_bios_t smb_bios;
420 	smbios_info_t smb_info;
421 	const char *part = NULL, *rev = NULL, *serial = NULL;
422 	char *manuf = NULL, *prod = NULL, *asset = NULL;
423 	char *bios_vendor = NULL, *bios_rev = NULL, *bios_reldate = NULL;
424 	nvlist_t *auth, *fmri;
425 	topo_mod_t *mod = smed->sme_mod;
426 	tnode_t *mbnode;
427 	topo_pgroup_info_t pgi;
428 	int rc = 0, err;
429 
430 	if (smbios_lookup_type(shp, SMB_TYPE_BASEBOARD, &sp) == 0 &&
431 	    smbios_info_bboard(shp, sp.smbstr_id, &smb_mb) == 0 &&
432 	    smbios_info_common(shp, sp.smbstr_id, &smb_info) == 0) {
433 		if (is_valid_string(smb_info.smbi_part) == B_TRUE)
434 			part = smb_info.smbi_part;
435 		if (is_valid_string(smb_info.smbi_version) == B_TRUE)
436 			rev = smb_info.smbi_version;
437 		if (is_valid_string(smb_info.smbi_serial) == B_TRUE)
438 			serial = smb_info.smbi_serial;
439 		if (is_valid_string(smb_info.smbi_manufacturer) == B_TRUE)
440 			manuf = topo_mod_clean_str(mod,
441 			    smb_info.smbi_manufacturer);
442 		if (is_valid_string(smb_info.smbi_product) == B_TRUE)
443 			prod = topo_mod_clean_str(mod, smb_info.smbi_product);
444 		if (is_valid_string(smb_info.smbi_asset) == B_TRUE)
445 			asset = topo_mod_clean_str(mod, smb_info.smbi_asset);
446 	}
447 	if (smbios_lookup_type(shp, SMB_TYPE_BIOS, &sp) == 0 &&
448 	    smbios_info_bios(shp, &smb_bios) == 0) {
449 		if (is_valid_string(smb_bios.smbb_vendor) == B_TRUE)
450 			bios_vendor = topo_mod_clean_str(mod,
451 			    smb_bios.smbb_vendor);
452 		if (is_valid_string(smb_bios.smbb_version) == B_TRUE)
453 			bios_rev = topo_mod_clean_str(mod,
454 			    smb_bios.smbb_version);
455 		if (is_valid_string(smb_bios.smbb_reldate) == B_TRUE)
456 			bios_reldate = topo_mod_clean_str(mod,
457 			    smb_bios.smbb_reldate);
458 	}
459 	if ((auth = topo_mod_auth(mod, smed->sme_pnode)) == NULL) {
460 		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
461 		    topo_mod_errmsg(mod));
462 		/* errno set */
463 		goto err;
464 	}
465 
466 	if ((fmri = topo_mod_hcfmri(mod, NULL, FM_HC_SCHEME_VERSION,
467 	    MOTHERBOARD, 0, NULL, auth, part, rev, serial)) ==
468 	    NULL) {
469 		nvlist_free(auth);
470 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
471 		    topo_mod_errmsg(mod));
472 		/* errno set */
473 		goto err;
474 	}
475 
476 	if ((mbnode = topo_node_bind(mod, smed->sme_pnode, MOTHERBOARD, 0,
477 	    fmri)) == NULL) {
478 		nvlist_free(auth);
479 		nvlist_free(fmri);
480 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
481 		    topo_mod_errmsg(mod));
482 		/* errno set */
483 		goto err;
484 	}
485 
486 	/* Create authority and system pgroups */
487 	topo_pgroup_hcset(mbnode, auth);
488 	nvlist_free(auth);
489 
490 	if (topo_node_fru_set(mbnode, fmri, 0, &err) != 0) {
491 		topo_mod_dprintf(mod, "failed to set FRU on %s: %s",
492 		    MOTHERBOARD, topo_strerror(err));
493 		nvlist_free(fmri);
494 		(void) topo_mod_seterrno(mod, err);
495 		goto err;
496 	}
497 	nvlist_free(fmri);
498 	fmri = NULL;
499 
500 	if (topo_node_label_set(mbnode, "MB", &err) != 0) {
501 		topo_mod_dprintf(mod, "failed to set label on %s: %s",
502 		    MOTHERBOARD, topo_strerror(err));
503 		(void) topo_mod_seterrno(mod, err);
504 		goto err;
505 	}
506 
507 	pgi.tpi_name = TOPO_PGROUP_MOTHERBOARD;
508 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
509 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
510 	pgi.tpi_version = TOPO_VERSION;
511 	rc = topo_pgroup_create(mbnode, &pgi, &err);
512 
513 	if (rc == 0 && manuf != NULL)
514 		rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
515 		    TOPO_PROP_MB_MANUFACTURER, TOPO_PROP_IMMUTABLE, manuf,
516 		    &err);
517 	if (rc == 0 && prod != NULL)
518 		rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
519 		    TOPO_PROP_MB_PRODUCT, TOPO_PROP_IMMUTABLE, prod, &err);
520 	if (rc == 0 && asset != NULL)
521 		rc += topo_prop_set_string(mbnode, TOPO_PGROUP_MOTHERBOARD,
522 		    TOPO_PROP_MB_ASSET, TOPO_PROP_IMMUTABLE, asset, &err);
523 
524 	if (rc != 0) {
525 		topo_mod_dprintf(mod, "error setting properties on %s node",
526 		    MOTHERBOARD);
527 		(void) topo_mod_seterrno(mod, err);
528 		goto err;
529 	}
530 	/*
531 	 * If we were able to gleen the BIOS version from SMBIOS, then set
532 	 * up a UFM node to capture that information.
533 	 */
534 	if (bios_rev != NULL) {
535 		topo_ufm_slot_info_t slotinfo = { 0 };
536 		nvlist_t *extra;
537 
538 		slotinfo.usi_version = bios_rev;
539 		slotinfo.usi_active = B_TRUE;
540 		slotinfo.usi_mode = TOPO_UFM_SLOT_MODE_NONE;
541 
542 		if (bios_vendor != NULL || bios_reldate != NULL) {
543 			if (nvlist_alloc(&extra, NV_UNIQUE_NAME, 0) != 0) {
544 				goto err;
545 			}
546 			if (bios_vendor != NULL && nvlist_add_string(extra,
547 			    TOPO_PROP_MB_FIRMWARE_VENDOR, bios_vendor) != 0) {
548 				nvlist_free(extra);
549 				goto err;
550 			}
551 			if (bios_reldate != NULL && nvlist_add_string(extra,
552 			    TOPO_PROP_MB_FIRMWARE_RELDATE, bios_reldate) !=
553 			    0) {
554 				nvlist_free(extra);
555 				goto err;
556 			}
557 			slotinfo.usi_extra = extra;
558 		}
559 		if (topo_node_range_create(mod, mbnode, UFM, 0, 0) != 0) {
560 			topo_mod_dprintf(mod, "failed to create %s range",
561 			    UFM);
562 			nvlist_free(extra);
563 			goto err;
564 		}
565 		(void) topo_mod_create_ufm(mod, mbnode, 0, "BIOS", &slotinfo);
566 		nvlist_free(extra);
567 	}
568 
569 err:
570 	topo_mod_strfree(mod, manuf);
571 	topo_mod_strfree(mod, prod);
572 	topo_mod_strfree(mod, asset);
573 	topo_mod_strfree(mod, bios_vendor);
574 	topo_mod_strfree(mod, bios_rev);
575 	topo_mod_strfree(mod, bios_reldate);
576 
577 	return (0);
578 }
579 
580 /*
581  * A system with a functional memory controller driver will have one mc device
582  * node per chip instance, starting at instance 0.  The driver provides an
583  * ioctl interface for retrieving a snapshot of the system's memory topology.
584  * If we're able to issue this ioctl on one of the mc device nodes then we'll
585  * return B_TRUE, indicating that this system has a minimally functional memory
586  * controller driver.
587  */
588 static boolean_t
589 has_mc_driver()
590 {
591 #ifdef	__x86
592 	int mc_fd;
593 	mc_snapshot_info_t mcs;
594 
595 	if ((mc_fd = open("/dev/mc/mc0", O_RDONLY)) < 0)
596 		return (B_FALSE);
597 
598 	if (ioctl(mc_fd, MC_IOC_SNAPSHOT_INFO, &mcs) < 0) {
599 		(void) close(mc_fd);
600 		return (B_FALSE);
601 	}
602 	(void) close(mc_fd);
603 	return (B_TRUE);
604 #else
605 	return (B_TRUE);
606 #endif
607 }
608 
609 /*ARGSUSED*/
610 static int
611 smbios_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
612     topo_instance_t min, topo_instance_t max, void *arg, void *unused)
613 {
614 	smbios_hdl_t *smbh;
615 	smb_enum_data_t smed = { 0 };
616 
617 	if ((smbh = topo_mod_smbios(mod)) == NULL) {
618 		topo_mod_dprintf(mod, "failed to get libsmbios handle");
619 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
620 	}
621 	smed.sme_mod = mod;
622 	smed.sme_pnode = rnode;
623 	smed.sme_slot_inst = min;
624 	smed.sme_slot_maxinst = max;
625 
626 	/*
627 	 * Currently we only support enumerating dimm-slot and dimm nodes, but
628 	 * this module could be expanded in the future to enumerate other
629 	 * hardware components from SMBIOS.
630 	 */
631 	if (strcmp(name, SLOT) == 0) {
632 		/*
633 		 * If the system has a functional memory controller driver then
634 		 * we'll assume that it has responsibility for enumerating the
635 		 * memory topology.
636 		 */
637 		if (has_mc_driver() == B_TRUE)
638 			return (0);
639 		if (smbios_iter(smbh, smbios_enum_memory, &smed) < 0)
640 			/* errno set */
641 			return (-1);
642 	} else if (strcmp(name, MOTHERBOARD) == 0) {
643 		if (smbios_enum_motherboard(smbh, &smed) < 0)
644 			/* errno set */
645 			return (-1);
646 	} else {
647 		topo_mod_dprintf(mod, "smbios_enum() invoked for unsupported "
648 		    "node type: %s", name);
649 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
650 	}
651 	return (0);
652 }
653 
654 const topo_modops_t smbios_ops = { smbios_enum, NULL };
655 
656 const topo_modinfo_t smbios_info =
657 	{ "smbios", FM_FMRI_SCHEME_HC, TOPO_VERSION, &smbios_ops };
658 
659 /*ARGSUSED*/
660 int
661 _topo_init(topo_mod_t *mod, topo_version_t version)
662 {
663 	if (getenv("TOPOSMBIOSDEBUG") != NULL)
664 		topo_mod_setdebug(mod);
665 
666 	if (topo_mod_register(mod, &smbios_info, TOPO_VERSION) != 0) {
667 		topo_mod_dprintf(mod, "module registration failed: %s\n",
668 		    topo_mod_errmsg(mod));
669 		/* errno set */
670 		return (-1);
671 	}
672 
673 	topo_mod_dprintf(mod, "SMBIOS enumerator initialized\n");
674 	return (0);
675 }
676 
677 void
678 _topo_fini(topo_mod_t *mod)
679 {
680 	topo_mod_unregister(mod);
681 }
682