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