]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
tools: Add hardware feature check
authorJan Kiszka <jan.kiszka@siemens.com>
Wed, 18 May 2016 23:17:13 +0000 (01:17 +0200)
committerJan Kiszka <jan.kiszka@siemens.com>
Fri, 20 May 2016 08:31:33 +0000 (10:31 +0200)
The hypervisor itself is not very helpful when it comes to analyzing
feature deficits of the target platform. This adds another extension
script to the jailhouse command which checks the hardware using the
same key criteria that also the hypervisor applied.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
README.md
tools/jailhouse-completion.bash
tools/jailhouse-hardware-check [new file with mode: 0755]
tools/jailhouse.c

index be6298ad55be3c1ad13445544299fb2bd1736dbf..dea09ce39c6166459927360c260bec61a1cce267 100644 (file)
--- a/README.md
+++ b/README.md
@@ -127,6 +127,13 @@ ARM architecture:
     - ARM Versatile Express with Cortex-A15 or A7 cores
       (includes ARM Fast Model)
 
+On x86, hardware capabilities can be validated by running
+
+    jailhouse hardware check sysconfig.cell
+
+using the binary system configuration created for the target (see
+[below](#configuration)).
+
 
 Build & Installation
 --------------------
index 019087443ad3d20a6eb0bae844353aabb868fd4a..f1b1a568101898cf0d5f5bfa88e28b4bbe9d2792 100644 (file)
@@ -290,7 +290,7 @@ function _jailhouse() {
        local command command_cell command_config cur prev subcommand
 
        # first level
-       command="enable disable cell config --help"
+       command="enable disable cell config hardware --help"
 
        # second level
        command_cell="create load start shutdown destroy linux list stats"
@@ -331,6 +331,9 @@ function _jailhouse() {
                        COMPREPLY=( $( compgen -W "${command_config}" -- \
                                        "${cur}") )
                        ;;
+               hardware)
+                       COMPREPLY="check"
+                       ;;
                --help|disable)
                        # these first level commands have no further subcommand
                        # or option OR we don't even know it
@@ -366,6 +369,18 @@ function _jailhouse() {
                                return 1;;
                        esac
                        ;;
+               hardware)
+                       case "${subcommand}" in
+                       check)
+                               # this command takes only a argument at place 3
+                               [ "${COMP_CWORD}" -gt 3 ] && return 1
+
+                               _filedir
+                               ;;
+                       *)
+                               return 1;;
+                       esac
+                       ;;
                *)
                        # no further subsubcommand/option known for this
                        return 1;;
diff --git a/tools/jailhouse-hardware-check b/tools/jailhouse-hardware-check
new file mode 100755 (executable)
index 0000000..fa69580
--- /dev/null
@@ -0,0 +1,287 @@
+#!/usr/bin/env python
+
+# Jailhouse, a Linux-based partitioning hypervisor
+#
+# Copyright (c) Siemens AG, 2016
+#
+# Authors:
+#  Jan Kiszka <jan.kiszka@siemens.com>
+#
+# This work is licensed under the terms of the GNU GPL, version 2.  See
+# the COPYING file in the top-level directory.
+
+from __future__ import print_function
+import collections
+import mmap
+import os
+import struct
+import sys
+
+check_passed = True
+
+
+def check_feature(msg, ok, optional=False):
+    if not (ok or optional):
+        global check_passed
+        check_passed = False
+    print('%-32s%s' % (msg, 'ok' if ok else
+                            ('missing (optional)' if optional else 'MISSING')))
+    return ok
+
+
+def parse_cpuinfo():
+    vendor = None
+    features = None
+    cpus = 0
+    with open('/proc/cpuinfo', 'r') as info:
+        for line in info:
+            if not line.strip():
+                continue
+            key, value = line.split(':')
+            if key.strip() == 'vendor_id':
+                if not vendor:
+                    vendor = value.strip()
+                elif vendor != value.strip():
+                    print('ERROR: Inconsistent vendor string on CPU %d' % cpus,
+                          file=sys.stderr)
+                    sys.exit(2)
+                cpus += 1
+            if key.strip() == 'flags':
+                if not features:
+                    features = value.strip().split(' ')
+                elif features != value.strip().split(' '):
+                    print('ERROR: Inconsistent feature set on CPU %d' % cpus,
+                          file=sys.stderr)
+                    sys.exit(2)
+    return (vendor, features, cpus)
+
+
+class MSR:
+    IA32_FEATURE_CONTROL = 0x0000003a
+    IA32_VMX_BASIC = 0x00000480
+    IA32_VMX_PINBASED_CTLS = 0x00000481
+    IA32_VMX_PROCBASED_CTLS = 0x00000482
+    IA32_VMX_EXIT_CTLS = 0x00000483
+    IA32_VMX_ENTRY_CTLS = 0x00000484
+    IA32_VMX_MISC = 0x00000485
+    IA32_VMX_PROCBASED_CTLS2 = 0x0000048b
+    IA32_VMX_EPT_VPID_CAP = 0x0000048c
+    IA32_VMX_TRUE_PROCBASED_CTLS = 0x0000048e
+
+    def __init__(self, num_cpus):
+        self.num_cpus = num_cpus
+        self.msr = []
+        for n in range(self.num_cpus):
+            self.msr.append(open('/dev/cpu/%d/msr' % n, 'rb', 0))
+
+    def read(self, index):
+        try:
+            self.msr[0].seek(index)
+            value = struct.unpack('Q', self.msr[0].read(8))[0]
+        except:
+            return 0
+
+        for n in range(1, self.num_cpus):
+            self.msr[n].seek(index)
+            if value != struct.unpack('Q', self.msr[n].read(8))[0]:
+                print('ERROR: Inconsistent value of MSR 0x%x on CPU %d' %
+                      (index, n), file=sys.stderr)
+                sys.exit(2)
+            return value
+
+
+class MMIO:
+    def __init__(self, base, size):
+        f = os.open('/dev/mem', os.O_RDONLY | os.O_SYNC)
+        self.mmap = mmap.mmap(f, size, mmap.MAP_SHARED, mmap.PROT_READ,
+                              offset=base)
+
+    def read64(self, offset):
+        self.mmap.seek(offset)
+        return struct.unpack('Q', self.mmap.read(8))[0]
+
+
+class Sysconfig:
+    SIGNATURE_SIZE = 8
+    HVMEM_SIZE = 32
+    DBGCON_SIZE = 32
+    X86_MMCFGBASE_SIZE = 8
+    X86_MMCFGENDBUS_SIZE = 1
+    X86_PADDING = 5
+    X86_PMTMR_SIZE = 2
+    X86_MAX_IOMMU_UNITS = 8
+    X86_IOMMU_SIZE = 20
+
+    def __init__(self, path):
+        self.config = open(path, 'rb')
+        if self.config.read(Sysconfig.SIGNATURE_SIZE).decode() != 'JAILSYST':
+            print('Not a system configuration', file=sys.stderr)
+            sys.exit(1)
+
+    def parse_iommus(self):
+        self.config.seek(Sysconfig.SIGNATURE_SIZE + Sysconfig.HVMEM_SIZE +
+                         Sysconfig.DBGCON_SIZE + Sysconfig.X86_MMCFGBASE_SIZE +
+                         Sysconfig.X86_MMCFGENDBUS_SIZE +
+                         Sysconfig.X86_PADDING + Sysconfig.X86_PMTMR_SIZE)
+
+        keys = 'base size amd_bdf amd_base_cap amd_features'
+        IOMMU = collections.namedtuple('IOMMU', keys)
+
+        iommus = []
+        for n in range(Sysconfig.X86_MAX_IOMMU_UNITS):
+            data = self.config.read(Sysconfig.X86_IOMMU_SIZE)
+            iommu = IOMMU(*struct.unpack('QIHBxI', data))
+            iommus.append(iommu)
+        return iommus
+
+
+def usage(exit_code):
+    prog = os.path.basename(sys.argv[0]).replace('-', ' ')
+    print('usage: %s SYSCONFIG' % prog)
+    sys.exit(exit_code)
+
+
+if len(sys.argv) != 2:
+    usage(1)
+if sys.argv[1] in ("--help", "-h"):
+    usage(0)
+
+if os.uname()[4] not in ('x86_64', 'i686'):
+    print('Unsupported architecture', file=sys.stderr)
+    sys.exit(1)
+
+config = Sysconfig(sys.argv[1])
+iommu = config.parse_iommus()
+
+(cpu_vendor, cpu_features, cpu_count) = parse_cpuinfo()
+
+if not os.access('/dev/cpu/0/msr', os.R_OK):
+    if os.system('/sbin/modprobe msr'):
+        sys.exit(1)
+
+msr = MSR(cpu_count)
+
+print('Feature                         Availability')
+print('------------------------------  ------------------')
+check_feature('Number of CPUs > 1', cpu_count > 1)
+check_feature('Long mode', 'lm' in cpu_features)
+
+if cpu_vendor == 'GenuineIntel':
+    check_feature('x2APIC', 'x2apic' in cpu_features, True)
+    print()
+    check_feature('VT-x (VMX)', 'vmx' in cpu_features)
+
+    feature = msr.read(MSR.IA32_FEATURE_CONTROL)
+    check_feature('  VMX without TXT',
+                  (feature & (1 << 0)) == 0 or feature & (1 << 2))
+    check_feature('  IA32_TRUE_*_CLTS',
+                  msr.read(MSR.IA32_VMX_BASIC) & (1 << 55))
+
+    pinbased = msr.read(MSR.IA32_VMX_PINBASED_CTLS) >> 32
+    check_feature('  NMI exiting', pinbased & (1 << 3))
+    check_feature('  Preemption timer', pinbased & (1 << 6))
+
+    procbased = msr.read(MSR.IA32_VMX_PROCBASED_CTLS) >> 32
+    check_feature('  I/O bitmap', procbased & (1 << 25))
+    check_feature('  MSR bitmap', procbased & (1 << 28))
+    check_feature('  Secondary controls', procbased & (1 << 31))
+    check_feature('  Optional CR3 interception',
+                  (msr.read(MSR.IA32_VMX_TRUE_PROCBASED_CTLS) &
+                   (3 << 15)) == 0)
+
+    procbased2 = msr.read(MSR.IA32_VMX_PROCBASED_CTLS2) >> 32
+    check_feature('  Virtualize APIC access', procbased2 & (1 << 0))
+    check_feature('  RDTSCP', procbased2 & (1 << 3),
+                  'rdtscp' not in cpu_features)
+    check_feature('  Unrestricted guest', procbased2 & (1 << 7))
+
+    check_feature('  EPT', procbased2 & (1 << 1))
+    ept_cap = msr.read(MSR.IA32_VMX_EPT_VPID_CAP)
+    check_feature('    4-level page walk', ept_cap & (1 << 6))
+    check_feature('    EPTP write-back', ept_cap & (1 << 14))
+    check_feature('    2M pages', ept_cap & (1 << 16), True)
+    check_feature('    1G pages', ept_cap & (1 << 17), True)
+    check_feature('    INVEPT', ept_cap & (1 << 20))
+    check_feature('      Single or all-context', ept_cap & (3 << 25))
+
+    vmexit = msr.read(MSR.IA32_VMX_EXIT_CTLS) >> 32
+    check_feature('  VM-exit save IA32_PAT', vmexit & (1 << 18))
+    check_feature('  VM-exit load IA32_PAT', vmexit & (1 << 19))
+    check_feature('  VM-exit save IA32_EFER', vmexit & (1 << 20))
+    check_feature('  VM-exit load IA32_EFER', vmexit & (1 << 21))
+
+    vmentry = msr.read(MSR.IA32_VMX_ENTRY_CTLS) >> 32
+    check_feature('  VM-entry load IA32_PAT', vmentry & (1 << 14))
+    check_feature('  VM-entry load IA32_EFER', vmentry & (1 << 15))
+    check_feature('  Activity state HLT',
+                  msr.read(MSR.IA32_VMX_MISC) & (1 << 6))
+
+    for n in range(8):
+        if iommu[n].base == 0 and n > 0:
+            break
+        print()
+        check_feature('VT-d (IOMMU #%d)' % n, iommu[n].base)
+        if iommu[n].base == 0:
+            break
+        mmio = MMIO(iommu[n].base, iommu[n].size)
+        cap = mmio.read64(0x08)
+        check_feature('  Caching mode = 0', (cap & (1 << 7)) == 0)
+        check_feature('  39-bit AGAW', cap & (1 << 9), cap & (1 << 10))
+        check_feature('  48-bit AGAW', cap & (1 << 10), cap & (1 << 9))
+        check_feature('  2M pages', cap & (1 << 34), True)
+        check_feature('  1G pages', cap & (1 << 35), True)
+        ecap = mmio.read64(0x10)
+        check_feature('  Queued invalidation', ecap & (1 << 1))
+        check_feature('  Interrupt remapping', ecap & (1 << 3))
+        check_feature('  Extended interrupt mode', ecap & (1 << 4),
+                      'x2apic' not in cpu_features)
+
+elif cpu_vendor == 'AuthenticAMD':
+    print()
+    check_feature('AMD-V (SVM)', 'svm' in cpu_features)
+    check_feature('  NPT', 'npt' in cpu_features)
+    check_feature('  Decode assist', 'decodeassists' in cpu_features, True)
+    check_feature('  AVIC', 'avic' in cpu_features, True)
+    check_feature('  Flush by ASID', 'flushbyasid' in cpu_features, True)
+
+    for n in range(8):
+        if iommu[n].base == 0 and n > 0:
+            break
+        print()
+        check_feature('AMD-Vi (IOMMU #%d)' % n, iommu[n].base)
+        if iommu[n].base == 0:
+            break
+
+        bdf = iommu[n].amd_bdf
+        path = '/sys/bus/pci/devices/0000:%02x:%02x.%x/config' % \
+            (bdf >> 8, (bdf >> 3) & 0x1f, bdf & 0x7)
+        with open(path, 'rb') as config:
+            config.seek(iommu[n].amd_base_cap)
+            (caps, base) = struct.unpack('QQ', config.read(16))
+
+        check_feature('  Extended feature register', caps & (1 << 27))
+        check_feature('  Valid base register',
+                      (base & (1 << 0)) == 0 or
+                      base == (iommu[n].base | (1 << 0)))
+
+        mmio = MMIO(iommu[n].base, iommu[n].size)
+        efr = mmio.read64(0x30)
+        if check_feature('  SMI filter', ((efr >> 16) & 0x3) == 1):
+            smi_filter_ok = True
+            num_filter_regs = 1 << ((efr >> 18) & 7)
+            for n in range(num_filter_regs):
+                smi_freg = mmio.read64(0x60 + (n << 3))
+                # must not be locked AND set to match against specific device
+                if smi_freg & (1 << 17) and smi_freg & (1 << 16):
+                    smi_filter_ok = False
+            check_feature('    Valid filter registers', smi_filter_ok)
+
+        he_feature = iommu[n].amd_features if iommu[n].amd_features != 0 \
+            else efr
+        check_feature('  Hardware events', he_feature & (1 << 8), True)
+
+else:
+    print('Unsupported CPU', file=sys.stderr)
+
+print('\nCheck %s!' % ('passed' if check_passed else 'FAILED'))
+sys.exit(0 if check_passed else 2)
index 8b3bb869c5186a46b2a84df27c96f3a67172792c..ba2af66a666338b4b96da88cee58dc597ebe5062 100644 (file)
@@ -43,6 +43,7 @@ static const struct extension extensions[] = {
          "[--mem-inmates MEM_INMATES]\n"
          "                 [--mem-hv MEM_HV] FILE" },
        { "config", "collect", "FILE.TAR" },
+       { "hardware", "check", "SYSCONFIG" },
        { NULL }
 };
 
@@ -399,7 +400,8 @@ int main(int argc, char *argv[])
                close(fd);
        } else if (strcmp(argv[1], "cell") == 0) {
                err = cell_management(argc, argv);
-       } else if (strcmp(argv[1], "config") == 0) {
+       } else if (strcmp(argv[1], "config") == 0 ||
+                  strcmp(argv[1], "hardware") == 0) {
                call_extension_script(argv[1], argc, argv);
                help(argv[0], 1);
        } else if (strcmp(argv[1], "--version") == 0) {