xref: /illumos-gate/usr/src/cmd/prtdiag/i386/smbios.c (revision 24f5a37652e188ebdcdd6da454511686935025df)
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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 /*
27  * Copyright 2015 Joyent, Inc.
28  */
29 
30 /*
31  * x86 System Management BIOS prtdiag
32  *
33  * Most modern x86 systems support a System Management BIOS, which is a memory
34  * buffer filled in by the BIOS at boot time that describes the hardware.  This
35  * data format is described by DMTF specification DSP0134 (see http://dmtf.org)
36  * This file implements a rudimentary prtdiag(1M) display using the SMBIOS.
37  * Access to the data is provided by libsmbios: see <sys/smbios.h> for info.
38  *
39  * NOTE: It is important to understand that x86 hardware varies extremely
40  * widely and that the DMTF SMBIOS specification leaves way too much latitude
41  * for implementors, and provides no standardized validation mechanism.  As
42  * such, it is not uncommon to find out-of-spec SMBIOSes or fields that
43  * contain strange and possibly even incorrect information.  As such, this
44  * file should not be extended to report every SMBIOS tidbit or structure in
45  * the spec unless we have good reason to believe it tends to be reliable.
46  *
47  * Similarly, the prtdiag(1M) utility itself should not be used to spit out
48  * every possible bit of x86 configuration data from every possible source;
49  * otherwise this code will become an unmaintainable and untestable disaster.
50  * Extensions to prtdiag should prefer to use more stable kernel mechanisms
51  * that actually discover the true hardware when such subsystems are available,
52  * and should generally limit themselves to commonly needed h/w data.  As such,
53  * extensions to x86 prtdiag should focus on integration with the device tree.
54  *
55  * The prtdiag(1M) utility is for service personnel and system administrators:
56  * it is not your personal ACPI disassembler or CPUID decoder ring.  The
57  * complete SMBIOS data is available from smbdump(1), and other specialized
58  * tools can be created to display the state of other x86 features, especially
59  * when that information is more for kernel developers than box administrators.
60  */
61 
62 #include <smbios.h>
63 #include <alloca.h>
64 #include <locale.h>
65 #include <strings.h>
66 #include <stdlib.h>
67 #include <stdio.h>
68 #include <ctype.h>
69 #include <pcidb.h>
70 #include <fm/libtopo.h>
71 #include <fm/topo_hc.h>
72 #include <sys/fm/protocol.h>
73 
74 static pcidb_hdl_t *prt_php;
75 
76 /*ARGSUSED*/
77 static int
78 do_procs(smbios_hdl_t *shp, const smbios_struct_t *sp, void *arg)
79 {
80 	smbios_processor_t p;
81 	smbios_info_t info;
82 	const char *v;
83 	char *s;
84 	size_t n;
85 
86 	if (sp->smbstr_type == SMB_TYPE_PROCESSOR &&
87 	    smbios_info_processor(shp, sp->smbstr_id, &p) != SMB_ERR &&
88 	    smbios_info_common(shp, sp->smbstr_id, &info) != SMB_ERR &&
89 	    SMB_PRSTATUS_PRESENT(p.smbp_status)) {
90 
91 		/*
92 		 * Obtaining a decent string for the type of processor is
93 		 * messy: the BIOS has hopefully filled in the SMBIOS record.
94 		 * If so, strip trailing spaces and \r (seen in some BIOSes).
95 		 * If not, fall back to the family name for p.smbp_family.
96 		 */
97 		if (info.smbi_version != NULL && *info.smbi_version != '\0') {
98 			n = strlen(info.smbi_version);
99 			v = s = alloca(n + 1);
100 			(void) strcpy(s, info.smbi_version);
101 
102 			if (s[n - 1] == '\r')
103 				s[--n] = '\0';
104 
105 			while (n != 0 && isspace(s[n - 1]))
106 				s[--n] = '\0';
107 
108 		} else if ((v = smbios_processor_family_desc(
109 		    p.smbp_family)) == NULL) {
110 			v = gettext("Unknown");
111 		}
112 
113 		(void) printf(gettext("%-32s %s\n"), v, info.smbi_location);
114 	}
115 
116 	return (0);
117 }
118 
119 /*
120  * NOTE: It would be very convenient to print the DIMM size in do_memdevs.
121  * Unfortunately, SMBIOS can only be relied upon to tell us whether a DIMM is
122  * present or not (smbmd_size == 0).  Some BIOSes do fill in an accurate size
123  * for DIMMs, whereas others fill in the maximum size, and still others insert
124  * a wrong value.  Sizes will need to wait for x86 memory controller interfaces
125  * or integration with IPMI, which can actually read the true DIMM SPD data.
126  */
127 /*ARGSUSED*/
128 static int
129 do_memdevs(smbios_hdl_t *shp, const smbios_struct_t *sp, void *arg)
130 {
131 	smbios_memdevice_t md;
132 
133 	if (sp->smbstr_type == SMB_TYPE_MEMDEVICE &&
134 	    smbios_info_memdevice(shp, sp->smbstr_id, &md) != SMB_ERR) {
135 
136 		const char *t = smbios_memdevice_type_desc(md.smbmd_type);
137 		char buf[8];
138 
139 		if (md.smbmd_set != (uint8_t)-1)
140 			(void) snprintf(buf, sizeof (buf), "%u", md.smbmd_set);
141 		else
142 			(void) strcpy(buf, "-");
143 
144 		(void) printf(gettext("%-11s %-6s %-3s %-19s %s\n"),
145 		    t ? t : gettext("Unknown"),
146 		    md.smbmd_size ? gettext("in use") : gettext("empty"),
147 		    buf, md.smbmd_dloc, md.smbmd_bloc);
148 	}
149 
150 	return (0);
151 }
152 
153 /*ARGSUSED*/
154 static int
155 do_obdevs(smbios_hdl_t *shp, const smbios_struct_t *sp, void *arg)
156 {
157 	smbios_obdev_t *argv;
158 	int i, argc;
159 
160 	if (sp->smbstr_type == SMB_TYPE_OBDEVS &&
161 	    (argc = smbios_info_obdevs(shp, sp->smbstr_id, 0, NULL)) > 0) {
162 		argv = alloca(sizeof (smbios_obdev_t) * argc);
163 		(void) smbios_info_obdevs(shp, sp->smbstr_id, argc, argv);
164 		for (i = 0; i < argc; i++)
165 			(void) printf(gettext("%s\n"), argv[i].smbd_name);
166 	}
167 
168 	return (0);
169 }
170 
171 /*ARGSUSED*/
172 static int
173 do_slot_mapping_cb(topo_hdl_t *thp, tnode_t *node, void *arg)
174 {
175 	int err, ret;
176 	nvlist_t *rsrc = NULL;
177 	const char *match = arg;
178 	char *s, *fmri = NULL;
179 	char *didstr = NULL, *driver = NULL, *vidstr = NULL;
180 	boolean_t printed = B_FALSE;
181 
182 	ret = TOPO_WALK_NEXT;
183 	if (topo_node_resource(node, &rsrc, &err) < 0)
184 		goto next;
185 	if (topo_fmri_nvl2str(thp, rsrc, &fmri, &err) < 0)
186 		goto next;
187 
188 	if ((s = strstr(fmri, match)) == NULL)
189 		goto next;
190 	if (s[strlen(match)] != '\0')
191 		goto next;
192 
193 	/* At this point we think we've found a match */
194 	ret = TOPO_WALK_TERMINATE;
195 	if (topo_prop_get_string(node, TOPO_PGROUP_IO, TOPO_IO_DRIVER, &driver,
196 	    &err) != 0)
197 		driver = NULL;
198 
199 	if (topo_prop_get_string(node, TOPO_PGROUP_PCI, TOPO_PCI_VENDID,
200 	    &vidstr, &err) != 0)
201 		goto next;
202 
203 	if (topo_prop_get_string(node, TOPO_PGROUP_PCI, TOPO_PCI_DEVID,
204 	    &didstr, &err) != 0)
205 		goto next;
206 
207 	if (prt_php != NULL) {
208 		long vid, did;
209 
210 		vid = strtol(vidstr, NULL, 16);
211 		did = strtol(didstr, NULL, 16);
212 		if (vid >= 0 && vid <= UINT16_MAX &&
213 		    did >= 0 && did <= UINT16_MAX) {
214 			pcidb_device_t *pdev;
215 
216 			pdev = pcidb_lookup_device(prt_php, vid, did);
217 			if (pdev != NULL) {
218 				pcidb_vendor_t *pvend;
219 				pvend = pcidb_device_vendor(pdev);
220 				(void) printf(gettext(", %s %s (%s)"),
221 				    pcidb_vendor_name(pvend),
222 				    pcidb_device_name(pdev),
223 				    driver != NULL ? driver : "<unknown>");
224 				printed = B_TRUE;
225 			}
226 		}
227 	}
228 
229 	if (printed == B_FALSE) {
230 		(void) printf(gettext(", pci%s,%s (%s)"), vidstr, didstr,
231 		    driver != NULL ? driver : "<unknown>");
232 	}
233 next:
234 	topo_hdl_strfree(thp, didstr);
235 	topo_hdl_strfree(thp, driver);
236 	topo_hdl_strfree(thp, vidstr);
237 	topo_hdl_strfree(thp, fmri);
238 	nvlist_free(rsrc);
239 	return (ret);
240 }
241 
242 static void
243 do_slot_mapping(smbios_slot_t *s, topo_hdl_t *thp)
244 {
245 	int err;
246 	uint_t dev, func;
247 	topo_walk_t *twp;
248 	char pciex[256];
249 
250 	/*
251 	 * Bits 7:3 are the device number and bits 2:0 are the function.
252 	 */
253 	dev = s->smbl_df >> 3;
254 	func = s->smbl_df & 0x7;
255 
256 	(void) snprintf(pciex, sizeof (pciex), "%s=%u/%s=%u/%s=%d",
257 	    PCIEX_BUS, s->smbl_bus, PCIEX_DEVICE, dev, PCIEX_FUNCTION, func);
258 
259 	twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, do_slot_mapping_cb, pciex,
260 	    &err);
261 	if (twp == NULL)
262 		return;
263 
264 	(void) topo_walk_step(twp, TOPO_WALK_CHILD);
265 	topo_walk_fini(twp);
266 }
267 
268 /*ARGSUSED*/
269 static int
270 do_slots(smbios_hdl_t *shp, const smbios_struct_t *sp, void *arg)
271 {
272 	smbios_slot_t s;
273 
274 	if (sp->smbstr_type == SMB_TYPE_SLOT &&
275 	    smbios_info_slot(shp, sp->smbstr_id, &s) != SMB_ERR) {
276 
277 		const char *t = smbios_slot_type_desc(s.smbl_type);
278 		const char *u = smbios_slot_usage_desc(s.smbl_usage);
279 
280 		(void) printf(gettext("%-3u %-9s %-16s %s"),
281 		    s.smbl_id, u ? u : gettext("Unknown"),
282 		    t ? t : gettext("Unknown"), s.smbl_name);
283 
284 		/*
285 		 * If the slot isn't of a type where this makes sense, then
286 		 * SMBIOS will populate any of these members with the value
287 		 * 0xff. Therefore if we find any of them set there, we just
288 		 * ignore it for now.
289 		 */
290 		if (s.smbl_sg != 0xff && s.smbl_bus != 0xff &&
291 		    s.smbl_df != 0xff && arg != NULL)
292 			do_slot_mapping(&s, arg);
293 
294 		(void) printf(gettext("\n"));
295 	}
296 
297 	return (0);
298 }
299 
300 /*ARGSUSED*/
301 int
302 do_prominfo(int opt_v, char *progname, int opt_l, int opt_p)
303 {
304 	smbios_hdl_t *shp;
305 	smbios_system_t sys;
306 	smbios_bios_t bios;
307 	smbios_ipmi_t ipmi;
308 	smbios_info_t info;
309 	topo_hdl_t *thp;
310 	char *uuid;
311 
312 	const char *s;
313 	id_t id;
314 	int err;
315 
316 	if ((shp = smbios_open(NULL, SMB_VERSION, 0, &err)) == NULL) {
317 		(void) fprintf(stderr,
318 		    gettext("%s: failed to open SMBIOS: %s\n"),
319 		    progname, smbios_errmsg(err));
320 		return (1);
321 	}
322 
323 	if ((id = smbios_info_system(shp, &sys)) != SMB_ERR &&
324 	    smbios_info_common(shp, id, &info) != SMB_ERR) {
325 		(void) printf(gettext("System Configuration: %s %s\n"),
326 		    info.smbi_manufacturer, info.smbi_product);
327 	} else {
328 		(void) fprintf(stderr,
329 		    gettext("%s: failed to get system info: %s\n"),
330 		    progname, smbios_errmsg(smbios_errno(shp)));
331 	}
332 
333 	if (smbios_info_bios(shp, &bios) != SMB_ERR) {
334 		(void) printf(gettext("BIOS Configuration: %s %s %s\n"),
335 		    bios.smbb_vendor, bios.smbb_version, bios.smbb_reldate);
336 	} else {
337 		(void) fprintf(stderr,
338 		    gettext("%s: failed to get bios info: %s\n"),
339 		    progname, smbios_errmsg(smbios_errno(shp)));
340 	}
341 
342 	if (smbios_info_ipmi(shp, &ipmi) != SMB_ERR) {
343 		if ((s = smbios_ipmi_type_desc(ipmi.smbip_type)) == NULL)
344 			s = gettext("Unknown");
345 
346 		(void) printf(gettext("BMC Configuration: IPMI %u.%u (%s)\n"),
347 		    ipmi.smbip_vers.smbv_major, ipmi.smbip_vers.smbv_minor, s);
348 	}
349 
350 	/*
351 	 * Silently swallow all libtopo and libpcidb related errors.
352 	 */
353 	uuid = NULL;
354 	if ((thp = topo_open(TOPO_VERSION, NULL, &err)) != NULL) {
355 		if ((uuid = topo_snap_hold(thp, NULL, &err)) == NULL) {
356 			topo_close(thp);
357 			thp = NULL;
358 		}
359 	}
360 
361 	prt_php = pcidb_open(PCIDB_VERSION);
362 
363 	(void) printf(gettext(
364 	    "\n==== Processor Sockets ====================================\n"));
365 
366 	(void) printf(gettext("\n%-32s %s"), "Version", "Location Tag");
367 
368 	(void) printf(gettext(
369 	    "\n-------------------------------- --------------------------\n"));
370 	(void) smbios_iter(shp, do_procs, NULL);
371 
372 	(void) printf(gettext(
373 	    "\n==== Memory Device Sockets ================================\n"));
374 
375 	(void) printf(gettext("\n%-11s %-6s %-3s %-19s %s"),
376 	    "Type", "Status", "Set", "Device Locator", "Bank Locator");
377 
378 	(void) printf(gettext(
379 	    "\n----------- ------ --- ------------------- ----------------\n"));
380 	(void) smbios_iter(shp, do_memdevs, NULL);
381 
382 	(void) printf(gettext(
383 	    "\n==== On-Board Devices =====================================\n"));
384 	(void) smbios_iter(shp, do_obdevs, NULL);
385 
386 	(void) printf(gettext(
387 	    "\n==== Upgradeable Slots ====================================\n"));
388 
389 	(void) printf(gettext("\n%-3s %-9s %-16s %s"),
390 	    "ID", "Status", "Type", "Description");
391 
392 	(void) printf(gettext(
393 	    "\n--- --------- ---------------- ----------------------------\n"));
394 	(void) smbios_iter(shp, do_slots, thp);
395 
396 	smbios_close(shp);
397 
398 	topo_hdl_strfree(thp, uuid);
399 	if (thp != NULL) {
400 		topo_snap_release(thp);
401 		topo_close(thp);
402 	}
403 	pcidb_close(prt_php);
404 
405 	return (0);
406 }
407