/* * The PCI Utilities -- Show Vital Product Data * * Copyright (c) 2008 Solarflare Communications * * Written by Ben Hutchings * Improved by Martin Mares * * Can be freely distributed and used under the terms of the GNU GPL v2+. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include "lspci.h" /* * The list of all known VPD items and their formats. * Technically, this belongs to the pci.ids file, but the VPD does not seem * to be developed any longer, so we have chosen the easier way. */ enum vpd_format { F_BINARY, F_TEXT, F_RESVD, F_RDWR, }; static const struct vpd_item { byte id1, id2; byte format; const char *name; } vpd_items[] = { { 'C','P', F_BINARY, "Extended capability" }, { 'E','C', F_TEXT, "Engineering changes" }, { 'M','N', F_TEXT, "Manufacture ID" }, { 'P','N', F_TEXT, "Part number" }, { 'R','V', F_RESVD, "Reserved" }, { 'R','W', F_RDWR, "Read-write area" }, { 'S','N', F_TEXT, "Serial number" }, { 'Y','A', F_TEXT, "Asset tag" }, { 'V', 0 , F_TEXT, "Vendor specific" }, { 'Y', 0 , F_TEXT, "System specific" }, /* Non-standard extensions */ { 'C','C', F_TEXT, "CCIN" }, { 'F','C', F_TEXT, "Feature code" }, { 'F','N', F_TEXT, "FRU" }, { 'N','A', F_TEXT, "Network address" }, { 'R','M', F_TEXT, "Firmware version" }, { 'Z', 0 , F_TEXT, "Product specific" }, { 0, 0 , F_BINARY, "Unknown" } }; static void print_vpd_string(const byte *buf, word len) { while (len--) { byte ch = *buf++; if (ch == '\\') printf("\\\\"); else if (!ch && !len) ; /* Cards with null-terminated strings have been observed */ else if (ch < 32 || ch == 127) printf("\\x%02x", ch); else putchar(ch); } } static void print_vpd_binary(const byte *buf, word len) { int i; for (i = 0; i < len; i++) { if (i) putchar(' '); printf("%02x", buf[i]); } } static int read_vpd(struct device *d, int pos, byte *buf, int len, byte *csum) { if (!pci_read_vpd(d->dev, pos, buf, len)) return 0; while (len--) *csum += *buf++; return 1; } void cap_vpd(struct device *d) { word res_addr = 0, res_len, part_pos, part_len; byte buf[256]; byte tag; byte csum = 0; printf("Vital Product Data\n"); if (verbose < 2) return; while (res_addr <= PCI_VPD_ADDR_MASK) { if (!read_vpd(d, res_addr, &tag, 1, &csum)) break; if (tag & 0x80) { if (res_addr > PCI_VPD_ADDR_MASK + 1 - 3) break; if (!read_vpd(d, res_addr + 1, buf, 2, &csum)) break; res_len = buf[0] + (buf[1] << 8); res_addr += 3; } else { res_len = tag & 7; tag >>= 3; res_addr += 1; } if (res_len > PCI_VPD_ADDR_MASK + 1 - res_addr) break; part_pos = 0; switch (tag) { case 0x0f: printf("\t\tEnd\n"); return; case 0x82: printf("\t\tProduct Name: "); while (part_pos < res_len) { part_len = res_len - part_pos; if (part_len > sizeof(buf)) part_len = sizeof(buf); if (!read_vpd(d, res_addr + part_pos, buf, part_len, &csum)) break; print_vpd_string(buf, part_len); part_pos += part_len; } printf("\n"); break; case 0x90: case 0x91: printf("\t\t%s fields:\n", (tag == 0x90) ? "Read-only" : "Read/write"); while (part_pos + 3 <= res_len) { word read_len; const struct vpd_item *item; byte id[2], id1, id2; if (!read_vpd(d, res_addr + part_pos, buf, 3, &csum)) break; part_pos += 3; memcpy(id, buf, 2); id1 = id[0]; id2 = id[1]; part_len = buf[2]; if (part_len > res_len - part_pos) break; /* Is this item known? */ for (item=vpd_items; item->id1 && item->id1 != id1 || item->id2 && item->id2 != id2; item++) ; /* Only read the first byte of the RV field because the * remaining bytes are not included in the checksum. */ read_len = (item->format == F_RESVD) ? 1 : part_len; if (!read_vpd(d, res_addr + part_pos, buf, read_len, &csum)) break; printf("\t\t\t["); print_vpd_string(id, 2); printf("] %s: ", item->name); switch (item->format) { case F_TEXT: print_vpd_string(buf, part_len); printf("\n"); break; case F_BINARY: print_vpd_binary(buf, part_len); printf("\n"); break; case F_RESVD: printf("checksum %s, %d byte(s) reserved\n", csum ? "bad" : "good", part_len - 1); break; case F_RDWR: printf("%d byte(s) free\n", part_len); break; } part_pos += part_len; } break; default: printf("\t\tUnknown %s resource type %02x, will not decode more.\n", (tag & 0x80) ? "large" : "small", tag & ~0x80); return; } res_addr += res_len; } if (res_addr == 0) printf("\t\tNot readable\n"); else printf("\t\tNo end tag found\n"); }