#!/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)