X-Git-Url: http://rtime.felk.cvut.cz/gitweb/jailhouse.git/blobdiff_plain/4e0ea74420900c7a66d265117b09fddbc3aaa754..f053c948f5b6fe7b9ec5cf6533e177fd42113ba3:/tools/jailhouse-hardware-check diff --git a/tools/jailhouse-hardware-check b/tools/jailhouse-hardware-check new file mode 100755 index 0000000..fa69580 --- /dev/null +++ b/tools/jailhouse-hardware-check @@ -0,0 +1,287 @@ +#!/usr/bin/env python + +# Jailhouse, a Linux-based partitioning hypervisor +# +# Copyright (c) Siemens AG, 2016 +# +# Authors: +# Jan Kiszka +# +# 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)