| /* |
| * The PCI Utilities -- Show Vital Product Data |
| * |
| * Copyright (c) 2008 Solarflare Communications |
| * |
| * Written by Ben Hutchings <[email protected]> |
| * Improved by Martin Mares <[email protected]> |
| * |
| * Can be freely distributed and used under the terms of the GNU GPL v2+. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| #include <stdio.h> |
| #include <string.h> |
| |
| #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"); |
| } |