| /* |
| * lscpu-dmi - Module to parse SMBIOS information |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License along |
| * with this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Code originally taken from the dmidecode utility and slightly rewritten |
| * to suite the needs of lscpu |
| */ |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include "c.h" |
| #include "pathnames.h" |
| #include "all-io.h" |
| #include "lscpu.h" |
| |
| #define WORD(x) (uint16_t)(*(const uint16_t *)(x)) |
| #define DWORD(x) (uint32_t)(*(const uint32_t *)(x)) |
| |
| struct dmi_header |
| { |
| uint8_t type; |
| uint8_t length; |
| uint16_t handle; |
| uint8_t *data; |
| }; |
| |
| static int checksum(const uint8_t *buf, size_t len) |
| { |
| uint8_t sum = 0; |
| size_t a; |
| |
| for (a = 0; a < len; a++) |
| sum += buf[a]; |
| return (sum == 0); |
| } |
| |
| static void *get_mem_chunk(size_t base, size_t len, const char *devmem) |
| { |
| void *p = NULL; |
| int fd; |
| |
| if ((fd = open(devmem, O_RDONLY)) < 0) |
| return NULL; |
| |
| if (!(p = malloc(len))) |
| goto nothing; |
| if (lseek(fd, base, SEEK_SET) == -1) |
| goto nothing; |
| if (read_all(fd, p, len) == -1) |
| goto nothing; |
| |
| close(fd); |
| return p; |
| |
| nothing: |
| free(p); |
| close(fd); |
| return NULL; |
| } |
| |
| static void to_dmi_header(struct dmi_header *h, uint8_t *data) |
| { |
| h->type = data[0]; |
| h->length = data[1]; |
| h->handle = WORD(data + 2); |
| h->data = data; |
| } |
| |
| static char *dmi_string(const struct dmi_header *dm, uint8_t s) |
| { |
| char *bp = (char *)dm->data; |
| |
| if (s == 0) |
| return NULL; |
| |
| bp += dm->length; |
| while (s > 1 && *bp) |
| { |
| bp += strlen(bp); |
| bp++; |
| s--; |
| } |
| |
| if (!*bp) |
| return NULL; |
| |
| return bp; |
| } |
| |
| static int hypervisor_from_dmi_table(uint32_t base, uint16_t len, |
| uint16_t num, const char *devmem) |
| { |
| uint8_t *buf; |
| uint8_t *data; |
| int i = 0; |
| char *vendor = NULL; |
| char *product = NULL; |
| char *manufacturer = NULL; |
| int rc = HYPER_NONE; |
| |
| data = buf = get_mem_chunk(base, len, devmem); |
| if (!buf) |
| goto done; |
| |
| /* 4 is the length of an SMBIOS structure header */ |
| while (i < num && data + 4 <= buf + len) { |
| uint8_t *next; |
| struct dmi_header h; |
| |
| to_dmi_header(&h, data); |
| |
| /* |
| * If a short entry is found (less than 4 bytes), not only it |
| * is invalid, but we cannot reliably locate the next entry. |
| * Better stop at this point. |
| */ |
| if (h.length < 4) |
| goto done; |
| |
| /* look for the next handle */ |
| next = data + h.length; |
| while (next - buf + 1 < len && (next[0] != 0 || next[1] != 0)) |
| next++; |
| next += 2; |
| switch (h.type) { |
| case 0: |
| vendor = dmi_string(&h, data[0x04]); |
| break; |
| case 1: |
| manufacturer = dmi_string(&h, data[0x04]); |
| product = dmi_string(&h, data[0x05]); |
| break; |
| default: |
| break; |
| } |
| |
| data = next; |
| i++; |
| } |
| if (manufacturer && !strcmp(manufacturer, "innotek GmbH")) |
| rc = HYPER_INNOTEK; |
| else if (manufacturer && strstr(manufacturer, "HITACHI") && |
| product && strstr(product, "LPAR")) |
| rc = HYPER_HITACHI; |
| else if (vendor && !strcmp(vendor, "Parallels")) |
| rc = HYPER_PARALLELS; |
| done: |
| free(buf); |
| return rc; |
| } |
| |
| #if defined(__x86_64__) || defined(__i386__) |
| static int hypervisor_decode_legacy(uint8_t *buf, const char *devmem) |
| { |
| if (!checksum(buf, 0x0F)) |
| return HYPER_NONE; |
| |
| return hypervisor_from_dmi_table(DWORD(buf + 0x08), WORD(buf + 0x06), |
| WORD(buf + 0x0C), |
| devmem); |
| } |
| #endif |
| |
| static int hypervisor_decode_smbios(uint8_t *buf, const char *devmem) |
| { |
| if (!checksum(buf, buf[0x05]) |
| || memcmp(buf + 0x10, "_DMI_", 5) != 0 |
| || !checksum(buf + 0x10, 0x0F)) |
| return -1; |
| |
| return hypervisor_from_dmi_table(DWORD(buf + 0x18), WORD(buf + 0x16), |
| WORD(buf + 0x1C), |
| devmem); |
| } |
| |
| /* |
| * Probe for EFI interface |
| */ |
| #define EFI_NOT_FOUND (-1) |
| #define EFI_NO_SMBIOS (-2) |
| static int address_from_efi(size_t *address) |
| { |
| FILE *tab; |
| char linebuf[64]; |
| int ret; |
| |
| *address = 0; /* Prevent compiler warning */ |
| |
| /* |
| * Linux up to 2.6.6: /proc/efi/systab |
| * Linux 2.6.7 and up: /sys/firmware/efi/systab |
| */ |
| if (!(tab = fopen("/sys/firmware/efi/systab", "r")) && |
| !(tab = fopen("/proc/efi/systab", "r"))) |
| return EFI_NOT_FOUND; /* No EFI interface */ |
| |
| ret = EFI_NO_SMBIOS; |
| while ((fgets(linebuf, sizeof(linebuf) - 1, tab)) != NULL) { |
| char *addrp = strchr(linebuf, '='); |
| if (!addrp) |
| continue; |
| *(addrp++) = '\0'; |
| if (strcmp(linebuf, "SMBIOS") == 0) { |
| *address = strtoul(addrp, NULL, 0); |
| ret = 0; |
| break; |
| } |
| } |
| |
| fclose(tab); |
| return ret; |
| } |
| |
| int read_hypervisor_dmi(void) |
| { |
| int rc = HYPER_NONE; |
| uint8_t *buf = NULL; |
| size_t fp = 0; |
| |
| if (sizeof(uint8_t) != 1 |
| || sizeof(uint16_t) != 2 |
| || sizeof(uint32_t) != 4 |
| || '\0' != 0) |
| return rc; |
| |
| /* First try EFI (ia64, Intel-based Mac) */ |
| switch (address_from_efi(&fp)) { |
| case EFI_NOT_FOUND: |
| goto memory_scan; |
| case EFI_NO_SMBIOS: |
| goto done; |
| } |
| |
| buf = get_mem_chunk(fp, 0x20, _PATH_DEV_MEM); |
| if (!buf) |
| goto done; |
| |
| rc = hypervisor_decode_smbios(buf, _PATH_DEV_MEM); |
| if (rc) |
| goto done; |
| free(buf); |
| buf = NULL; |
| memory_scan: |
| #if defined(__x86_64__) || defined(__i386__) |
| /* Fallback to memory scan (x86, x86_64) */ |
| buf = get_mem_chunk(0xF0000, 0x10000, _PATH_DEV_MEM); |
| if (!buf) |
| goto done; |
| |
| for (fp = 0; fp <= 0xFFF0; fp += 16) { |
| if (memcmp(buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) { |
| rc = hypervisor_decode_smbios(buf + fp, _PATH_DEV_MEM); |
| if (rc == -1) |
| fp += 16; |
| |
| } else if (memcmp(buf + fp, "_DMI_", 5) == 0) |
| rc = hypervisor_decode_legacy(buf + fp, _PATH_DEV_MEM); |
| |
| if (rc >= 0) |
| break; |
| } |
| #endif |
| done: |
| free(buf); |
| return rc; |
| } |