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