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
do_procs(smbios_hdl_t * shp,const smbios_struct_t * sp,void * arg)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
do_memdevs(smbios_hdl_t * shp,const smbios_struct_t * sp,void * arg)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
do_obdevs(smbios_hdl_t * shp,const smbios_struct_t * sp,void * arg)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
do_slot_mapping_cb(topo_hdl_t * thp,tnode_t * node,void * arg)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
do_slot_mapping(smbios_slot_t * s,topo_hdl_t * thp)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
do_slots(smbios_hdl_t * shp,const smbios_struct_t * sp,void * arg)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
do_prominfo(int opt_v,char * progname,int opt_l,int opt_p)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