| // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later |
| /* |
| * Parse Vital Product Data (VPD) |
| * |
| * Copyright 2013-2019 IBM Corp. |
| */ |
| |
| #include <skiboot.h> |
| #include <vpd.h> |
| #include <string.h> |
| #include <device.h> |
| |
| #define CHECK_SPACE(_p, _n, _e) (((_e) - (_p)) >= (_n)) |
| |
| /* Low level keyword search in a record. Can be used when we |
| * need to find the next keyword of a given type, for example |
| * when having multiple MF/SM keyword pairs |
| */ |
| const void *vpd_find_keyword(const void *rec, size_t rec_sz, |
| const char *kw, uint8_t *kw_size) |
| { |
| const uint8_t *p = rec, *end = rec + rec_sz; |
| |
| while (CHECK_SPACE(p, 3, end)) { |
| uint8_t k1 = *(p++); |
| uint8_t k2 = *(p++); |
| uint8_t sz = *(p++); |
| |
| if (k1 == kw[0] && k2 == kw[1]) { |
| if (kw_size) |
| *kw_size = sz; |
| return p; |
| } |
| p += sz; |
| } |
| return NULL; |
| } |
| |
| /* vpd_valid - does some basic sanity checks to ensure a VPD blob is |
| * actually a VPD blob |
| */ |
| bool vpd_valid(const void *vvpd, size_t vpd_size) |
| { |
| const uint8_t *vpd = vvpd; |
| int size, i = 0; |
| |
| /* find the record start byte */ |
| while (i < vpd_size) |
| if (vpd[i++] == 0x84) |
| break; |
| |
| if (i >= vpd_size) |
| return false; |
| |
| /* next two bytes are the record length, little endian */ |
| size = 2; |
| size += vpd[i]; |
| size += vpd[i + 1] << 8; |
| |
| i += size; /* skip to the end marker */ |
| |
| if (i >= vpd_size || vpd[i] != 0x78) |
| return false; |
| |
| return true; |
| } |
| |
| /* Locate a record in a VPD blob |
| * |
| * Note: This works with VPD LIDs. It will scan until it finds |
| * the first 0x84, so it will skip all those 0's that the VPD |
| * LIDs seem to contain |
| */ |
| const void *vpd_find_record(const void *vpd, size_t vpd_size, |
| const char *record, size_t *sz) |
| { |
| const uint8_t *p = vpd, *end = vpd + vpd_size; |
| bool first_start = true; |
| size_t rec_sz; |
| uint8_t namesz = 0; |
| const char *rec_name; |
| |
| if (!vpd) |
| return NULL; |
| |
| while (CHECK_SPACE(p, 4, end)) { |
| /* Get header byte */ |
| if (*(p++) != 0x84) { |
| /* Skip initial crap in VPD LIDs */ |
| if (first_start) |
| continue; |
| break; |
| } |
| first_start = false; |
| rec_sz = *(p++); |
| rec_sz |= *(p++) << 8; |
| if (!CHECK_SPACE(p, rec_sz, end)) { |
| prerror("VPD: Malformed or truncated VPD," |
| " record size doesn't fit\n"); |
| return NULL; |
| } |
| |
| /* Find record name */ |
| rec_name = vpd_find_keyword(p, rec_sz, "RT", &namesz); |
| if (rec_name && strncmp(record, rec_name, namesz) == 0) { |
| if (sz) |
| *sz = rec_sz; |
| return p; |
| } |
| |
| p += rec_sz; |
| if (*(p++) != 0x78) { |
| prerror("VPD: Malformed or truncated VPD," |
| " missing final 0x78 in record %.4s\n", |
| rec_name ? rec_name : "????"); |
| return NULL; |
| } |
| } |
| return NULL; |
| } |
| |
| /* Locate a keyword in a record in a VPD blob |
| * |
| * Note: This works with VPD LIDs. It will scan until it finds |
| * the first 0x84, so it will skip all those 0's that the VPD |
| * LIDs seem to contain |
| */ |
| const void *vpd_find(const void *vpd, size_t vpd_size, |
| const char *record, const char *keyword, |
| uint8_t *sz) |
| { |
| size_t rec_sz; |
| const uint8_t *p; |
| |
| p = vpd_find_record(vpd, vpd_size, record, &rec_sz); |
| if (p) |
| p = vpd_find_keyword(p, rec_sz, keyword, sz); |
| return p; |
| } |