diff options
author | Ondrej Oprala | 2013-04-22 14:18:24 +0200 |
---|---|---|
committer | Karel Zak | 2013-05-20 16:30:23 +0200 |
commit | fb2627cec4c85e9af30df417e6bda0e98b6cd0be (patch) | |
tree | 27909e9cdae1d4afc30808f784e084281fe34c77 /sys-utils | |
parent | cfdisk: add long options to the command (diff) | |
download | kernel-qcow2-util-linux-fb2627cec4c85e9af30df417e6bda0e98b6cd0be.tar.gz kernel-qcow2-util-linux-fb2627cec4c85e9af30df417e6bda0e98b6cd0be.tar.xz kernel-qcow2-util-linux-fb2627cec4c85e9af30df417e6bda0e98b6cd0be.zip |
lscpu: detect more hypervisor vendors
[kzak@redhat.com: - cleanup coding style,
- use path_exist()]
Signed-off-by: Ondrej Oprala <ooprala@redhat.com>
Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'sys-utils')
-rw-r--r-- | sys-utils/Makemodule.am | 5 | ||||
-rw-r--r-- | sys-utils/lscpu-dmi.c | 272 | ||||
-rw-r--r-- | sys-utils/lscpu.c | 72 | ||||
-rw-r--r-- | sys-utils/lscpu.h | 21 |
4 files changed, 354 insertions, 16 deletions
diff --git a/sys-utils/Makemodule.am b/sys-utils/Makemodule.am index 4139fcf65..977be5aff 100644 --- a/sys-utils/Makemodule.am +++ b/sys-utils/Makemodule.am @@ -239,7 +239,10 @@ endif if BUILD_LSCPU usrbin_exec_PROGRAMS += lscpu -lscpu_SOURCES = sys-utils/lscpu.c +lscpu_SOURCES = \ + sys-utils/lscpu.c \ + sys-utils/lscpu.h \ + sys-utils/lscpu-dmi.c lscpu_LDADD = $(LDADD) libcommon.la dist_man_MANS += sys-utils/lscpu.1 endif diff --git a/sys-utils/lscpu-dmi.c b/sys-utils/lscpu-dmi.c new file mode 100644 index 000000000..21e024bc4 --- /dev/null +++ b/sys-utils/lscpu-dmi.c @@ -0,0 +1,272 @@ +/* + * 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; + int fd; + + if ((fd = open(devmem, O_RDONLY)) == -1) + return NULL; + if ((p = malloc(len)) == NULL) + return NULL; + if (lseek(fd, base, SEEK_SET) == -1) { + free(p); + return NULL; + } + if (read_all(fd, p, len) == -1) { + free(p); + return NULL; + } + + close(fd); + return p; +} + +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 = NULL; + uint8_t *data; + int i = 0; + char *vendor = NULL; + char *product = NULL; + char *manufacturer = NULL; + + if ((buf = get_mem_chunk(base, len, devmem)) == NULL) + return HYPER_NONE; + data = buf; + + /* 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) + return HYPER_NONE; + + /* 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 (!strcmp(manufacturer, "innotek GmbH")) + return HYPER_INNOTEK; + else if (strstr(manufacturer, "HITACHI") && strstr(product, "LPAR")) + return HYPER_HITACHI; + else if (!strcmp(vendor, "Parallels")) + return HYPER_PARALLELS; + + free(buf); + return HYPER_NONE; +} + +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); +} + +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, '='); + *(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 ret = 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 ret; + + /* First try EFI (ia64, Intel-based Mac) */ + switch (address_from_efi(&fp)) { + case EFI_NOT_FOUND: + goto memory_scan; + case EFI_NO_SMBIOS: + goto exit_free; + } + + buf = get_mem_chunk(fp, 0x20, _PATH_DEV_MEM); + if (!buf) + goto exit_free; + + if (hypervisor_decode_smbios(buf, _PATH_DEV_MEM)) + goto done; + +memory_scan: + /* Fallback to memory scan (x86, x86_64) */ + buf = get_mem_chunk(0xF0000, 0x10000, _PATH_DEV_MEM); + if (!buf) + goto exit_free; + + for (fp = 0; fp <= 0xFFF0; fp += 16) { + if (memcmp(buf + fp, "_SM_", 4) == 0 && fp <= 0xFFE0) { + if ((ret = hypervisor_decode_smbios(buf + fp, + _PATH_DEV_MEM)) == -1) + fp += 16; + + } else if (memcmp(buf + fp, "_DMI_", 5) == 0) + ret = hypervisor_decode_legacy(buf + fp, _PATH_DEV_MEM); + } + +done: + free(buf); +exit_free: + return ret; +} diff --git a/sys-utils/lscpu.c b/sys-utils/lscpu.c index 2e08f66a9..58f3248e9 100644 --- a/sys-utils/lscpu.c +++ b/sys-utils/lscpu.c @@ -43,6 +43,7 @@ #include "path.h" #include "closestream.h" #include "optutils.h" +#include "lscpu.h" #define CACHE_MAX 100 @@ -54,6 +55,10 @@ #define _PATH_PROC_CPUINFO "/proc/cpuinfo" #define _PATH_PROC_PCIDEVS "/proc/bus/pci/devices" #define _PATH_PROC_SYSINFO "/proc/sysinfo" +#define _PATH_PROC_STATUS "/proc/self/status" +#define _PATH_PROC_VZ "/proc/vz" +#define _PATH_PROC_BC "/proc/bc" +#define _PATH_DEV_MEM "/dev/mem" /* virtualization types */ enum { @@ -67,22 +72,18 @@ const char *virt_types[] = { [VIRT_FULL] = N_("full") }; -/* hypervisor vendors */ -enum { - HYPER_NONE = 0, - HYPER_XEN, - HYPER_KVM, - HYPER_MSHV, - HYPER_VMWARE, - HYPER_IBM -}; const char *hv_vendors[] = { [HYPER_NONE] = NULL, [HYPER_XEN] = "Xen", [HYPER_KVM] = "KVM", [HYPER_MSHV] = "Microsoft", [HYPER_VMWARE] = "VMware", - [HYPER_IBM] = "IBM" + [HYPER_IBM] = "IBM", + [HYPER_VSERVER] = "Linux-VServer", + [HYPER_UML] = "User-mode Linux", + [HYPER_INNOTEK] = "Innotek GmbH", + [HYPER_HITACHI] = "Hitachi", + [HYPER_PARALLELS] = "Parallels" }; /* CPU modes */ @@ -529,17 +530,21 @@ read_hypervisor_cpuid(struct lscpu_desc *desc __attribute__((__unused__))) static void read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod) { - if (mod->system != SYSTEM_SNAPSHOT) + FILE *fd; + + if (mod->system != SYSTEM_SNAPSHOT) { read_hypervisor_cpuid(desc); + if (!desc->hyper) + desc->hyper = read_hypervisor_dmi(); + } if (desc->hyper) - /* hvm */ desc->virtype = VIRT_FULL; + /* Xen para-virt or dom0 */ else if (path_exist(_PATH_PROC_XEN)) { - /* Xen para-virt or dom0 */ - FILE *fd = path_fopen("r", 0, _PATH_PROC_XENCAP); int dom0 = 0; + fd = path_fopen("r", 0, _PATH_PROC_XENCAP); if (fd) { char buf[256]; @@ -552,10 +557,12 @@ read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod) desc->virtype = dom0 ? VIRT_NONE : VIRT_PARA; desc->hyper = HYPER_XEN; + /* Xen full-virt on non-x86_64 */ } else if (has_pci_device(0x5853, 0x0001)) { - /* Xen full-virt on non-x86_64 */ desc->hyper = HYPER_XEN; desc->virtype = VIRT_FULL; + + /* IBM PR/SM */ } else if (path_exist(_PATH_PROC_SYSINFO)) { FILE *fd = path_fopen("r", 0, _PATH_PROC_SYSINFO); char buf[BUFSIZ]; @@ -591,6 +598,41 @@ read_hypervisor(struct lscpu_desc *desc, struct lscpu_modifier *mod) } fclose(fd); } + + /* OpenVZ/Virtuozzo - /proc/vz dir should exist + * /proc/bc should not */ + else if (path_exist(_PATH_PROC_VZ) && !path_exist(_PATH_PROC_BC)) + desc->hyper = HYPER_PARALLELS; + + /* IBM */ + else if (desc->vendor && + (strcmp(desc->vendor, "PowerVM Lx86") == 0 || + strcmp(desc->vendor, "IBM/S390") == 0)) + desc->hyper = HYPER_IBM; + + /* User-mode-linux */ + else if (desc->modelname && strstr(desc->modelname, "UML")) + desc->hyper = HYPER_UML; + + /* Linux-VServer */ + else if (path_exist(_PATH_PROC_STATUS)) { + char buf[BUFSIZ]; + char *val = NULL; + + fd = path_fopen("r", 0, _PATH_PROC_STATUS); + while (fgets(buf, sizeof(buf), fd) != NULL) { + if (lookup(buf, "VxID", &val)) + break; + } + fclose(fd); + + if (val) { + while (isdigit(*val)) + ++val; + if (!*val) + desc->hyper = HYPER_VSERVER; + } + } } /* add @set to the @ary, unnecessary set is deallocated. */ diff --git a/sys-utils/lscpu.h b/sys-utils/lscpu.h new file mode 100644 index 000000000..312038fcc --- /dev/null +++ b/sys-utils/lscpu.h @@ -0,0 +1,21 @@ +#ifndef LSCPU_H +#define LSCPU_H + +/* hypervisor vendors */ +enum { + HYPER_NONE = 0, + HYPER_XEN, + HYPER_KVM, + HYPER_MSHV, + HYPER_VMWARE, + HYPER_IBM, /* sys-z powervm */ + HYPER_VSERVER, + HYPER_UML, + HYPER_INNOTEK, /* VBOX */ + HYPER_HITACHI, + HYPER_PARALLELS /* OpenVZ/VIrtuozzo */ +}; + +extern int read_hypervisor_dmi(void); + +#endif /* LSCPU_H */ |