The information needed to determine if a machine can run jailhouse is located entirely in sysfs. To see if a machine can run jailhouse, the user must currently (1) generate a configuration file using jailhouse-config-create (2) run `make` (3) pass this file to jailhouse-hardware-check.
Prepare for enabling jailhouse-hardware-check to read this information directly from sysfs by moving the relevant sysfs parsing functions in jailhouse-config-create into a module in pyjailhouse. Signed-off-by: Chris Goldsworthy <[email protected]> --- pyjailhouse/sysfs_parser.py | 927 +++++++++++++++++++++++++++++++++++++++ tools/jailhouse-config-create | 959 ++--------------------------------------- tools/jailhouse-hardware-check | 1 + 3 files changed, 957 insertions(+), 930 deletions(-) create mode 100644 pyjailhouse/sysfs_parser.py diff --git a/pyjailhouse/sysfs_parser.py b/pyjailhouse/sysfs_parser.py new file mode 100644 index 0000000..c415473 --- /dev/null +++ b/pyjailhouse/sysfs_parser.py @@ -0,0 +1,927 @@ +# +# Jailhouse, a Linux-based partitioning hypervisor +# +# Copyright (c) Siemens AG, 2014-2017 +# Copyright (c) Valentine Sinitsyn, 2014-2015 +# +# Authors: +# Henning Schild <[email protected]> +# Jan Kiszka <[email protected]> +# Valentine Sinitsyn <[email protected]> +# +# This work is licensed under the terms of the GNU GPL, version 2. See +# the COPYING file in the top-level directory. +# +# This script should help to create a basic jailhouse configuration file. +# It needs to be executed on the target machine, where it will gather +# information about the system. For more advanced scenarios you will have +# to change the generated C-code. + + +import struct +import os +import fnmatch + +root_dir = "/" + +inputs = { + 'files': set(), + 'files_opt': set(), + 'files_intel': set(), + 'files_amd': set() +} + +# required files +inputs['files'].add('/proc/iomem') +inputs['files'].add('/proc/cpuinfo') +inputs['files'].add('/proc/cmdline') +inputs['files'].add('/proc/ioports') +inputs['files'].add('/sys/bus/pci/devices/*/config') +inputs['files'].add('/sys/bus/pci/devices/*/resource') +inputs['files'].add('/sys/devices/system/cpu/cpu*/uevent') +inputs['files'].add('/sys/firmware/acpi/tables/APIC') +inputs['files'].add('/sys/firmware/acpi/tables/MCFG') +# optional files +inputs['files_opt'].add('/sys/class/dmi/id/product_name') +inputs['files_opt'].add('/sys/class/dmi/id/sys_vendor') +inputs['files_opt'].add('/sys/class/tty/*/iomem_base') +inputs['files_opt'].add('/sys/class/tty/*/iomem_reg_shift') +inputs['files_opt'].add('/sys/class/tty/*/io_type') +inputs['files_opt'].add('/sys/class/tty/*/port') +inputs['files_opt'].add('/sys/devices/jailhouse/enabled') +# platform specific files +inputs['files_intel'].add('/sys/firmware/acpi/tables/DMAR') +inputs['files_amd'].add('/sys/firmware/acpi/tables/IVRS') + + +def check_input_listed(name, optional=False): + set = inputs['files_opt'] + if optional is False: + set = inputs['files'] + cpuvendor = get_cpu_vendor() + if cpuvendor == 'GenuineIntel': + set = set.union(inputs['files_intel']) + elif cpuvendor == 'AuthenticAMD': + set = set.union(inputs['files_amd']) + + for file in set: + if fnmatch.fnmatch(name, file): + return True + raise RuntimeError('"' + name + '" is not a listed input file') + + +def input_open(name, mode='r', optional=False): + check_input_listed(name, optional) + try: + f = open(os.path.join(root_dir, name), mode) + except Exception as e: + if optional: + return open("/dev/null", mode) + raise e + return f + + +def input_listdir(dir, wildcards): + for w in wildcards: + check_input_listed(os.path.join(dir, w)) + dirs = os.listdir(os.path.join(root_dir, dir)) + dirs.sort() + return dirs + + +def parse_iomem(pcidevices): + regions = IOMemRegionTree.parse_iomem_tree( + IOMemRegionTree.parse_iomem_file()) + + rom_region = MemRegion(0xc0000, 0xdffff, 'ROMs') + add_rom_region = False + + ret = [] + dmar_regions = [] + for r in regions: + append_r = True + # filter the list for MSI-X pages + for d in pcidevices: + if d.msix_address >= r.start and d.msix_address <= r.stop: + if d.msix_address > r.start: + head_r = MemRegion(r.start, d.msix_address - 1, + r.typestr, r.comments) + ret.append(head_r) + if d.msix_address + d.msix_region_size < r.stop: + tail_r = MemRegion(d.msix_address + d.msix_region_size, + r.stop, r.typestr, r.comments) + ret.append(tail_r) + append_r = False + break + # filter out the ROMs + if (r.start >= rom_region.start and r.stop <= rom_region.stop): + add_rom_region = True + append_r = False + # filter out and save DMAR regions + if r.typestr.find('dmar') >= 0: + dmar_regions.append(r) + append_r = False + if append_r: + ret.append(r) + + # add a region that covers all potential ROMs + if add_rom_region: + ret.append(rom_region) + + # newer Linux kernels will report the first page as reserved + # it is needed for CPU init so include it anyways + if (ret[0].typestr == 'System RAM' and ret[0].start == 0x1000): + ret[0].start = 0 + + return ret, dmar_regions + + +def parse_pcidevices(): + int_src_cnt = 0 + devices = [] + caps = [] + basedir = '/sys/bus/pci/devices' + list = input_listdir(basedir, ['*/config']) + for dir in list: + d = PCIDevice.parse_pcidevice_sysfsdir(basedir, dir) + if d is not None: + if len(d.caps) > 0: + duplicate = False + # look for duplicate capability patterns + for d2 in devices: + if d2.caps == d.caps: + # reused existing capability list, but record all users + d2.caps[0].comments.append(str(d)) + d.caps_start = d2.caps_start + duplicate = True + break + if not duplicate: + d.caps[0].comments.append(str(d)) + d.caps_start = len(caps) + caps.extend(d.caps) + int_src_cnt += max(d.num_msi_vectors, d.num_msix_vectors) + devices.append(d) + return (devices, caps, int_src_cnt) + + +def parse_madt(): + f = input_open('/sys/firmware/acpi/tables/APIC', 'rb') + signature = f.read(4) + if signature != b'APIC': + raise RuntimeError('MADT: incorrect input file format %s' % signature) + (length,) = struct.unpack('<I', f.read(4)) + f.seek(44) + length -= 44 + ioapics = [] + + while length > 0: + offset = 0 + (struct_type, struct_len) = struct.unpack('<BB', f.read(2)) + offset += 2 + length -= struct_len + + if struct_type == 1: + (id, address, gsi_base) = struct.unpack('<BxII', f.read(10)) + offset += 10 + ioapics.append(IOAPIC(id, address, gsi_base)) + + f.seek(struct_len - offset, os.SEEK_CUR) + + f.close() + return ioapics + + +def parse_dmar_devscope(f): + (scope_type, scope_len, id, bus, dev, fn) = \ + struct.unpack('<BBxxBBBB', f.read(8)) + if scope_len != 8: + raise RuntimeError('Unsupported DMAR Device Scope Structure') + return (scope_type, scope_len, id, bus, dev, fn) + + +# parsing of DMAR ACPI Table +# see Intel VT-d Spec chapter 8 +def parse_dmar(pcidevices, ioapics, dmar_regions): + f = input_open('/sys/firmware/acpi/tables/DMAR', 'rb') + signature = f.read(4) + if signature != b'DMAR': + raise RuntimeError('DMAR: incorrect input file format %s' % signature) + (length,) = struct.unpack('<I', f.read(4)) + f.seek(48) + length -= 48 + units = [] + regions = [] + + while length > 0: + offset = 0 + (struct_type, struct_len) = struct.unpack('<HH', f.read(4)) + offset += 4 + length -= struct_len + + # DMA Remapping Hardware Unit Definition + if struct_type == 0: + (flags, segment, base) = struct.unpack('<BxHQ', f.read(12)) + if segment != 0: + raise RuntimeError('We do not support multiple PCI segments') + if len(units) >= 8: + raise RuntimeError('Too many DMAR units. ' + 'Raise JAILHOUSE_MAX_IOMMU_UNITS.') + size = 0 + for r in dmar_regions: + if base == r.start: + size = r.size() + if size == 0: + raise RuntimeError('DMAR region size cannot be identified.\n' + 'Target Linux must run with Intel IOMMU ' + 'enabled.') + if size > 0x3000: + raise RuntimeError('Unexpectedly large DMAR region.') + units.append(IOMMUConfig({ + 'base_addr': base, + 'mmio_size': size + })) + if flags & 1: + for d in pcidevices: + if d.iommu is None: + d.iommu = len(units) - 1 + offset += 16 - offset + while offset < struct_len: + (scope_type, scope_len, id, bus, dev, fn) =\ + parse_dmar_devscope(f) + # PCI Endpoint Device + if scope_type == 1: + assert not (flags & 1) + for d in pcidevices: + if d.bus == bus and d.dev == dev and d.fn == fn: + d.iommu = len(units) - 1 + break + # PCI Sub-hierarchy + elif scope_type == 2: + assert not (flags & 1) + for d in pcidevices: + if d.bus == bus and d.dev == dev and d.fn == fn: + (secondbus, subordinate) = \ + PCIPCIBridge.get_2nd_busses(d) + for d2 in pcidevices: + if ( + d2.bus >= secondbus and + d2.bus <= subordinate + ): + d2.iommu = len(units) - 1 + break + # IOAPIC + elif scope_type == 3: + ioapic = next(chip for chip in ioapics if chip.id == id) + bdf = (bus << 8) | (dev << 3) | fn + for chip in ioapics: + if chip.bdf == bdf: + raise RuntimeError('IOAPICs with identical BDF') + ioapic.bdf = bdf + ioapic.iommu = len(units) - 1 + offset += scope_len + + # Reserved Memory Region Reporting Structure + if struct_type == 1: + f.seek(8 - offset, os.SEEK_CUR) + offset += 8 - offset + (base, limit) = struct.unpack('<QQ', f.read(16)) + offset += 16 + + comments = [] + while offset < struct_len: + (scope_type, scope_len, id, bus, dev, fn) =\ + parse_dmar_devscope(f) + if scope_type == 1: + comments.append('PCI device: %02x:%02x.%x' % + (bus, dev, fn)) + else: + comments.append('DMAR parser could not decode device path') + offset += scope_len + + reg = MemRegion(base, limit, 'ACPI DMAR RMRR', comments) + regions.append(reg) + + f.seek(struct_len - offset, os.SEEK_CUR) + + f.close() + + for d in pcidevices: + if d.iommu is None: + raise RuntimeError( + 'PCI device %02x:%02x.%x outside the scope of an ' + 'IOMMU' % (d.bus, d.dev, d.fn)) + + return units, regions + + +def parse_ivrs(pcidevices, ioapics): + def format_bdf(bdf): + bus, dev, fun = (bdf >> 8) & 0xff, (bdf >> 3) & 0x1f, bdf & 0x7 + return '%02x:%02x.%x' % (bus, dev, fun) + + f = input_open('/sys/firmware/acpi/tables/IVRS', 'rb') + signature = f.read(4) + if signature != b'IVRS': + raise RuntimeError('IVRS: incorrect input file format %s' % signature) + + (length, revision) = struct.unpack('<IB', f.read(5)) + if revision > 2: + raise RuntimeError('IVRS: unsupported Revision %02x' % revision) + + f.seek(48, os.SEEK_SET) + length -= 48 + + units = [] + regions = [] + # BDF of devices that are permitted outside IOMMU: root complex + iommu_skiplist = set([0x0]) + ivhd_blocks = 0 + while length > 0: + (block_type, block_length) = struct.unpack('<BxH', f.read(4)) + if block_type in [0x10, 0x11]: + ivhd_blocks += 1 + if ivhd_blocks > 1: + raise RuntimeError('Jailhouse doesn\'t support more than one ' + 'AMD IOMMU per PCI function.') + # IVHD block + ivhd_fields = struct.unpack('<HHQHxxL', f.read(20)) + (iommu_bdf, base_cap_ofs, + base_addr, pci_seg, iommu_feat) = ivhd_fields + + length -= block_length + block_length -= 24 + + if pci_seg != 0: + raise RuntimeError('We do not support multiple PCI segments') + + if len(units) > 8: + raise RuntimeError('Too many IOMMU units. ' + 'Raise JAILHOUSE_MAX_IOMMU_UNITS.') + + msi_cap_ofs = None + + for i, d in enumerate(pcidevices): + if d.bdf() == iommu_bdf: + # Extract MSI capability offset + for c in d.caps: + if c.id == 0x05: + msi_cap_ofs = c.start + # We must not map IOMMU to the cells + del pcidevices[i] + + if msi_cap_ofs is None: + raise RuntimeError('AMD IOMMU lacks MSI support, and ' + 'Jailhouse doesn\'t support MSI-X yet.') + + if (iommu_feat & (0xF << 13)) and (iommu_feat & (0x3F << 17)): + # Performance Counters are supported, allocate 512K + mmio_size = 524288 + else: + # Allocate 16K + mmio_size = 16384 + + units.append(IOMMUConfig({ + 'base_addr': base_addr, + 'mmio_size': mmio_size, + 'amd_bdf': iommu_bdf, + 'amd_base_cap': base_cap_ofs, + 'amd_msi_cap': msi_cap_ofs, + # IVHD block type 0x11 has exact EFR copy but type 0x10 may + # overwrite what hardware reports. Set reserved bit 0 in that + # case to indicate that the value is in use. + 'amd_features': (iommu_feat | 0x1) if block_type == 0x10 else 0 + })) + + bdf_start_range = None + while block_length > 0: + (entry_type, device_id) = struct.unpack('<BHx', f.read(4)) + block_length -= 4 + + if entry_type == 0x01: + # All + for d in pcidevices: + d.iommu = len(units) - 1 + elif entry_type == 0x02: + # Select + for d in pcidevices: + if d.bdf() == device_id: + d.iommu = len(units) - 1 + elif entry_type == 0x03: + # Start of range + bdf_start_range = device_id + elif entry_type == 0x04: + # End of range + if bdf_start_range is None: + continue + for d in pcidevices: + if d.bdf() >= bdf_start_range and d.bdf() <= device_id: + d.iommu = len(units) - 1 + bdf_start_range = None + elif entry_type == 0x42: + # Alias select + (device_id_b,) = struct.unpack('<xHx', f.read(4)) + block_length -= 4 + for d in pcidevices: + if d.bdf() == device_id_b: + d.iommu = len(units) - 1 + elif entry_type == 0x43: + # Alias start of range + (device_id_b,) = struct.unpack('<xHx', f.read(4)) + block_length -= 4 + bdf_start_range = device_id_b + elif entry_type == 0x48: + # Special device + (handle, device_id_b, variety) = struct.unpack( + '<BHB', f.read(4)) + block_length -= 4 + if variety == 0x01: # IOAPIC + for chip in ioapics: + if chip.id == handle: + chip.bdf = device_id + chip.iommu = len(units) - 1 + else: + # Reserved or ignored entries + if entry_type >= 0x40: + f.seek(4, os.SEEK_CUR) + block_length -= 4 + + elif type in [0x20, 0x21, 0x22]: + # IVMD block + ivmd_fields = struct.unpack('<BBHHHxxxxxxxxQQ', f.read(32)) + (block_type, block_flags, block_length, + device_id, aux_data, mem_addr, mem_len) = ivmd_fields + length -= block_length + + if int(block_flags): + bdf_str = format_bdf(device_id) + print( + 'WARNING: Jailhouse doesn\'t support configurable ' + '(eg. read-only) device memory. Device %s may not ' + 'work properly, especially in non-root cell.' % bdf_str) + + if block_type == 0x20: + # All devices + comment = None + elif block_type == 0x21: + # Selected device + comment = 'PCI Device: %s' % format_bdf(device_id) + elif block_type == 0x22: + # Device range + comment = 'PCI Device: %s - %s' % ( + format_bdf(device_id), format_bdf(aux_data)) + + if comment: + print('WARNING: Jailhouse doesn\'t support per-device memory ' + 'regions. The memory at 0x%x will be mapped accessible ' + 'to all devices.' % mem_addr) + + regions.append(MemRegion(mem_addr, mem_len, 'ACPI IVRS', comment)) + elif type == 0x40: + raise RuntimeError( + 'You board uses IVRS Rev. 2 feature Jailhouse doesn\'t ' + 'support yet. Please report this to ' + '[email protected].') + else: + print( + 'WARNING: Skipping unknown IVRS ' + 'block type 0x%02x' % block_type) + + for d in pcidevices: + if d.bdf() not in iommu_skiplist and d.iommu is None: + raise RuntimeError( + 'PCI device %02x:%02x.%x outside the scope of an ' + 'IOMMU' % (d.bus, d.dev, d.fn)) + + f.close() + return units, regions + + +def get_cpu_vendor(): + with open('/proc/cpuinfo') as f: + for line in f: + if not line.strip(): + continue + key, value = line.split(':') + if key.strip() == 'vendor_id': + return value.strip() + + +class PCIBARs: + IORESOURCE_IO = 0x00000100 + IORESOURCE_MEM = 0x00000200 + IORESOURCE_MEM_64 = 0x00100000 + + def __init__(self, dir): + self.mask = [] + f = input_open(os.path.join(dir, 'resource'), 'r') + n = 0 + while (n < 6): + (start, end, flags) = f.readline().split() + n += 1 + flags = int(flags, 16) + if flags & PCIBARs.IORESOURCE_IO: + mask = ~(int(end, 16) - int(start, 16)) + elif flags & PCIBARs.IORESOURCE_MEM: + if flags & PCIBARs.IORESOURCE_MEM_64: + mask = int(end, 16) - int(start, 16) + (start, end, flags) = f.readline().split() + mask |= (int(end, 16) - int(start, 16)) << 32 + mask = ~(mask) + self.mask.append(mask & 0xffffffff) + mask >>= 32 + n += 1 + else: + mask = ~(int(end, 16) - int(start, 16)) + else: + mask = 0 + self.mask.append(mask & 0xffffffff) + f.close() + + +class PCICapability: + def __init__(self, id, start, len, flags, content, msix_address): + self.id = id + self.start = start + self.len = len + self.flags = flags + self.content = content + self.msix_address = msix_address + self.comments = [] + + def __eq__(self, other): + return self.id == other.id and self.start == other.start and \ + self.len == other.len and self.flags == other.flags + + RD = '0' + RW = 'JAILHOUSE_PCICAPS_WRITE' + + JAILHOUSE_PCI_EXT_CAP = 0x8000 + + @staticmethod + def parse_pcicaps(dir): + caps = [] + has_extended_caps = False + f = input_open(os.path.join(dir, 'config'), 'rb') + f.seek(0x06) + (status,) = struct.unpack('<H', f.read(2)) + # capability list supported? + if (status & (1 << 4)) == 0: + f.close() + return caps + # walk capability list + f.seek(0x34) + (next,) = struct.unpack('B', f.read(1)) + while next != 0: + cap = next + msix_address = 0 + f.seek(cap) + (id, next) = struct.unpack('<BB', f.read(2)) + if id == 0x01: # Power Management + # this cap can be handed out completely + len = 8 + flags = PCICapability.RW + elif id == 0x05: # MSI + # access will be moderated by hypervisor + len = 10 + (msgctl,) = struct.unpack('<H', f.read(2)) + if (msgctl & (1 << 7)) != 0: # 64-bit support + len += 4 + if (msgctl & (1 << 8)) != 0: # per-vector masking support + len += 10 + flags = PCICapability.RW + elif id == 0x10: # Express + len = 20 + (cap_reg,) = struct.unpack('<H', f.read(2)) + if (cap_reg & 0xf) >= 2: # v2 capability + len = 60 + # access side effects still need to be analyzed + flags = PCICapability.RD + has_extended_caps = True + elif id == 0x11: # MSI-X + # access will be moderated by hypervisor + len = 12 + (table,) = struct.unpack('<xxI', f.read(6)) + f.seek(0x10 + (table & 7) * 4) + (bar,) = struct.unpack('<I', f.read(4)) + if (bar & 0x3) != 0: + raise RuntimeError('Invalid MSI-X BAR found') + if (bar & 0x4) != 0: + bar |= struct.unpack('<I', f.read(4))[0] << 32 + msix_address = (bar & 0xfffffffffffffff0) + table & 0xfffffff8 + flags = PCICapability.RW + else: + # unknown/unhandled cap, mark its existence + len = 2 + flags = PCICapability.RD + f.seek(cap + 2) + content = f.read(len - 2) + caps.append(PCICapability(id, cap, len, flags, content, + msix_address)) + + if has_extended_caps: + # walk extended capability list + next = 0x100 + while next != 0: + cap = next + f.seek(cap) + (id, version_next) = struct.unpack('<HH', f.read(4)) + next = version_next >> 4 + if id == 0xffff: + break + elif id == 0x0010: # SR-IOV + len = 64 + # access side effects still need to be analyzed + flags = PCICapability.RD + else: + if (id & PCICapability.JAILHOUSE_PCI_EXT_CAP) != 0: + print('WARNING: Ignoring unsupported PCI Express ' + 'Extended Capability ID %x' % id) + continue + # unknown/unhandled cap, mark its existence + len = 4 + flags = PCICapability.RD + f.seek(cap + 4) + content = f.read(len - 4) + id |= PCICapability.JAILHOUSE_PCI_EXT_CAP + caps.append(PCICapability(id, cap, len, flags, content, 0)) + + f.close() + return caps + + +class PCIDevice: + def __init__(self, type, domain, bus, dev, fn, bars, caps, path): + self.type = type + self.iommu = None + self.domain = domain + self.bus = bus + self.dev = dev + self.fn = fn + self.bars = bars + self.caps = caps + self.path = path + self.caps_start = 0 + self.num_caps = len(caps) + self.num_msi_vectors = 0 + self.msi_64bits = 0 + self.num_msix_vectors = 0 + self.msix_region_size = 0 + self.msix_address = 0 + for c in caps: + if c.id in (0x05, 0x11): + msg_ctrl = struct.unpack('<H', c.content[:2])[0] + if c.id == 0x05: # MSI + self.num_msi_vectors = 1 << ((msg_ctrl >> 1) & 0x7) + self.msi_64bits = (msg_ctrl >> 7) & 1 + else: # MSI-X + if c.msix_address != 0: + vectors = (msg_ctrl & 0x7ff) + 1 + self.num_msix_vectors = vectors + self.msix_region_size = (vectors * 16 + 0xfff) & 0xf000 + self.msix_address = c.msix_address + else: + print('WARNING: Ignoring invalid MSI-X configuration' + ' of device %02x:%02x.%x' % (bus, dev, fn)) + + def __str__(self): + return 'PCIDevice: %02x:%02x.%x' % (self.bus, self.dev, self.fn) + + def bdf(self): + return self.bus << 8 | self.dev << 3 | self.fn + + @staticmethod + def parse_pcidevice_sysfsdir(basedir, dir): + dpath = os.path.join(basedir, dir) + f = input_open(os.path.join(dpath, 'config'), 'rb') + (vendor_device,) = struct.unpack('<I', f.read(4)) + if vendor_device == 0xffffffff: + print('WARNING: Ignoring apparently disabled PCI device %s' % dir) + return None + f.seek(0x0A) + (classcode,) = struct.unpack('<H', f.read(2)) + f.close() + if classcode == 0x0604: + type = 'JAILHOUSE_PCI_TYPE_BRIDGE' + else: + type = 'JAILHOUSE_PCI_TYPE_DEVICE' + a = dir.split(':') + domain = int(a[0], 16) + bus = int(a[1], 16) + df = a[2].split('.') + bars = PCIBARs(dpath) + caps = PCICapability.parse_pcicaps(dpath) + return PCIDevice(type, domain, bus, int(df[0], 16), int(df[1], 16), + bars, caps, dpath) + + +class PCIPCIBridge(PCIDevice): + @staticmethod + def get_2nd_busses(dev): + assert dev.type == 'JAILHOUSE_PCI_TYPE_BRIDGE' + f = input_open(os.path.join(dev.path, 'config'), 'rb') + f.seek(0x19) + (secondbus, subordinate) = struct.unpack('<BB', f.read(2)) + f.close() + return (secondbus, subordinate) + + +class MemRegion: + def __init__(self, start, stop, typestr, comments=None): + self.start = start + self.stop = stop + self.typestr = typestr + self.comments = comments or [] + + def __str__(self): + return 'MemRegion: %08x-%08x : %s' % \ + (self.start, self.stop, self.typestr) + + def size(self): + # round up to full PAGE_SIZE + return int((self.stop - self.start + 0xfff) / 0x1000) * 0x1000 + + def flagstr(self, p=''): + if ( + self.typestr == 'System RAM' or + self.typestr == 'Kernel' or + self.typestr == 'RAM buffer' or + self.typestr == 'ACPI DMAR RMRR' or + self.typestr == 'ACPI IVRS' + ): + s = 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE |\n' + s += p + '\t\tJAILHOUSE_MEM_EXECUTE | JAILHOUSE_MEM_DMA' + return s + return 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE' + + +class IOAPIC: + def __init__(self, id, address, gsi_base, iommu=0, bdf=0): + self.id = id + self.address = address + self.gsi_base = gsi_base + self.iommu = iommu + self.bdf = bdf + + def __str__(self): + return 'IOAPIC %d, GSI base %d' % (self.id, self.gsi_base) + + def irqchip_id(self): + # encode the IOMMU number into the irqchip ID + return (self.iommu << 16) | self.bdf + + +class IOMemRegionTree: + def __init__(self, region, level): + self.region = region + self.level = level + self.parent = None + self.children = [] + + def __str__(self): + s = '' + if (self.region): + s = (' ' * (self.level - 1)) + str(self.region) + if self.parent and self.parent.region: + s += ' --> ' + self.parent.region.typestr + s += '\n' + for c in self.children: + s += str(c) + return s + + def regions_split_by_kernel(self): + kernel = [x for x in self.children if + x.region.typestr.startswith('Kernel ')] + + if (len(kernel) == 0): + return [self.region] + + r = self.region + s = r.typestr + + kernel_start = kernel[0].region.start + kernel_stop = kernel[len(kernel) - 1].region.stop + + # align this for 16M, but only if we have enough space + kernel_stop = (kernel_stop & ~0xFFFFFF) + 0xFFFFFF + if (kernel_stop > r.stop): + kernel_stop = r.stop + + before_kernel = None + after_kernel = None + + # before Kernel if any + if (r.start < kernel_start): + before_kernel = MemRegion(r.start, kernel_start - 1, s) + + kernel_region = MemRegion(kernel_start, kernel_stop, "Kernel") + + # after Kernel if any + if (r.stop > kernel_stop): + after_kernel = MemRegion(kernel_stop + 1, r.stop, s) + + return [before_kernel, kernel_region, after_kernel] + + @staticmethod + def parse_iomem_line(line): + a = line.split(':', 1) + level = int(a[0].count(' ') / 2) + 1 + region = a[0].split('-', 1) + a[1] = a[1].strip() + return level, MemRegion(int(region[0], 16), int(region[1], 16), a[1]) + + @staticmethod + def parse_iomem_file(): + root = IOMemRegionTree(None, 0) + f = input_open('/proc/iomem') + lastlevel = 0 + lastnode = root + for line in f: + (level, r) = IOMemRegionTree.parse_iomem_line(line) + t = IOMemRegionTree(r, level) + if (t.level > lastlevel): + t.parent = lastnode + if (t.level == lastlevel): + t.parent = lastnode.parent + if (t.level < lastlevel): + p = lastnode.parent + while(t.level < p.level): + p = p.parent + t.parent = p.parent + + t.parent.children.append(t) + lastnode = t + lastlevel = t.level + f.close() + + return root + + # find HPET regions in tree + @staticmethod + def find_hpet_regions(tree): + regions = [] + + for tree in tree.children: + r = tree.region + s = r.typestr + + if (s.find('HPET') >= 0): + regions.append(r) + + # if the tree continues recurse further down ... + if (len(tree.children) > 0): + regions.extend(IOMemRegionTree.find_hpet_regions(tree)) + + return regions + + # recurse down the tree + @staticmethod + def parse_iomem_tree(tree): + regions = [] + + for tree in tree.children: + r = tree.region + s = r.typestr + + # System RAM on the first level will be added completely, + # if they don't contain the kernel itself, if they do, + # we split them + if (tree.level == 1 and s == 'System RAM'): + regions.extend(tree.regions_split_by_kernel()) + continue + + # blacklisted on all levels + if ( + (s.find('PCI MMCONFIG') >= 0) or + (s.find('APIC') >= 0) # covers both APIC and IOAPIC + ): + continue + + # generally blacklisted, unless we find an HPET behind it + if (s.lower() == 'reserved'): + regions.extend(IOMemRegionTree.find_hpet_regions(tree)) + continue + + # if the tree continues recurse further down ... + if (len(tree.children) > 0): + regions.extend(IOMemRegionTree.parse_iomem_tree(tree)) + continue + + # add all remaining leaves + regions.append(r) + + return regions + + +class IOMMUConfig: + def __init__(self, props): + self.base_addr = props['base_addr'] + self.mmio_size = props['mmio_size'] + if 'amd_bdf' in props: + self.amd_bdf = props['amd_bdf'] + self.amd_base_cap = props['amd_base_cap'] + self.amd_msi_cap = props['amd_msi_cap'] + self.amd_features = props['amd_features'] + + @property + def is_amd_iommu(self): + return hasattr(self, 'amd_bdf') diff --git a/tools/jailhouse-config-create b/tools/jailhouse-config-create index df63b7a..297dee2 100755 --- a/tools/jailhouse-config-create +++ b/tools/jailhouse-config-create @@ -25,7 +25,6 @@ import math import re import argparse import struct -import fnmatch try: from mako.template import Template @@ -33,6 +32,11 @@ except ImportError: print("This script requires the mako library to run.") sys.exit(1) +sys.path[0] = os.path.dirname(os.path.abspath(__file__)) + "/.." +import pyjailhouse.sysfs_parser as sysfs_parser +# Imports from directory containing this must be done above +# import statement, see python documentation on sys.path[0] + datadir = None if datadir: @@ -40,8 +44,6 @@ if datadir: else: template_default_dir = os.path.abspath(os.path.dirname(sys.argv[0])) -cpuvendor = None - # pretend to be part of the jailhouse tool sys.argv[0] = sys.argv[0].replace('-', ' ') @@ -87,35 +89,6 @@ parser.add_argument('file', metavar='FILE', options = parser.parse_args() -inputs = { - 'files': set(), - 'files_opt': set(), - 'files_intel': set(), - 'files_amd': set() -} - -# required files -inputs['files'].add('/proc/iomem') -inputs['files'].add('/proc/cpuinfo') -inputs['files'].add('/proc/cmdline') -inputs['files'].add('/proc/ioports') -inputs['files'].add('/sys/bus/pci/devices/*/config') -inputs['files'].add('/sys/bus/pci/devices/*/resource') -inputs['files'].add('/sys/devices/system/cpu/cpu*/uevent') -inputs['files'].add('/sys/firmware/acpi/tables/APIC') -inputs['files'].add('/sys/firmware/acpi/tables/MCFG') -# optional files -inputs['files_opt'].add('/sys/class/dmi/id/product_name') -inputs['files_opt'].add('/sys/class/dmi/id/sys_vendor') -inputs['files_opt'].add('/sys/class/tty/*/iomem_base') -inputs['files_opt'].add('/sys/class/tty/*/iomem_reg_shift') -inputs['files_opt'].add('/sys/class/tty/*/io_type') -inputs['files_opt'].add('/sys/class/tty/*/port') -inputs['files_opt'].add('/sys/devices/jailhouse/enabled') -# platform specific files -inputs['files_intel'].add('/sys/firmware/acpi/tables/DMAR') -inputs['files_amd'].add('/sys/firmware/acpi/tables/IVRS') - def kmg_multiply(value, kmg): if (kmg == 'K' or kmg == 'k'): @@ -134,544 +107,13 @@ def kmg_multiply_str(str): raise RuntimeError('kmg_multiply_str can not parse input "' + str + '"') -def check_input_listed(name, optional=False): - set = inputs['files_opt'] - if optional is False: - set = inputs['files'] - global cpuvendor - if cpuvendor == 'GenuineIntel': - set = set.union(inputs['files_intel']) - elif cpuvendor == 'AuthenticAMD': - set = set.union(inputs['files_amd']) - - for file in set: - if fnmatch.fnmatch(name, file): - return True - raise RuntimeError('"' + name + '" is not a listed input file') - - -def input_open(name, mode='r', optional=False): - check_input_listed(name, optional) - try: - f = open(options.root + name, mode) - except Exception as e: - if optional: - return open("/dev/null", mode) - raise e - return f - - def input_readline(name, optional=False): - f = input_open(name, optional=optional) + f = sysfs_parser.input_open(name, optional=optional) line = f.readline() f.close() return line -def input_listdir(dir, wildcards): - for w in wildcards: - check_input_listed(os.path.join(dir, w)) - dirs = os.listdir(options.root + dir) - dirs.sort() - return dirs - - -class PCIBARs: - IORESOURCE_IO = 0x00000100 - IORESOURCE_MEM = 0x00000200 - IORESOURCE_MEM_64 = 0x00100000 - - def __init__(self, dir): - self.mask = [] - f = input_open(os.path.join(dir, 'resource'), 'r') - n = 0 - while (n < 6): - (start, end, flags) = f.readline().split() - n += 1 - flags = int(flags, 16) - if flags & PCIBARs.IORESOURCE_IO: - mask = ~(int(end, 16) - int(start, 16)) - elif flags & PCIBARs.IORESOURCE_MEM: - if flags & PCIBARs.IORESOURCE_MEM_64: - mask = int(end, 16) - int(start, 16) - (start, end, flags) = f.readline().split() - mask |= (int(end, 16) - int(start, 16)) << 32 - mask = ~(mask) - self.mask.append(mask & 0xffffffff) - mask >>= 32 - n += 1 - else: - mask = ~(int(end, 16) - int(start, 16)) - else: - mask = 0 - self.mask.append(mask & 0xffffffff) - f.close() - - -class PCICapability: - def __init__(self, id, start, len, flags, content, msix_address): - self.id = id - self.start = start - self.len = len - self.flags = flags - self.content = content - self.msix_address = msix_address - self.comments = [] - - def __eq__(self, other): - return self.id == other.id and self.start == other.start and \ - self.len == other.len and self.flags == other.flags - - RD = '0' - RW = 'JAILHOUSE_PCICAPS_WRITE' - - JAILHOUSE_PCI_EXT_CAP = 0x8000 - - @staticmethod - def parse_pcicaps(dir): - caps = [] - has_extended_caps = False - f = input_open(os.path.join(dir, 'config'), 'rb') - f.seek(0x06) - (status,) = struct.unpack('<H', f.read(2)) - # capability list supported? - if (status & (1 << 4)) == 0: - f.close() - return caps - # walk capability list - f.seek(0x34) - (next,) = struct.unpack('B', f.read(1)) - while next != 0: - cap = next - msix_address = 0 - f.seek(cap) - (id, next) = struct.unpack('<BB', f.read(2)) - if id == 0x01: # Power Management - # this cap can be handed out completely - len = 8 - flags = PCICapability.RW - elif id == 0x05: # MSI - # access will be moderated by hypervisor - len = 10 - (msgctl,) = struct.unpack('<H', f.read(2)) - if (msgctl & (1 << 7)) != 0: # 64-bit support - len += 4 - if (msgctl & (1 << 8)) != 0: # per-vector masking support - len += 10 - flags = PCICapability.RW - elif id == 0x10: # Express - len = 20 - (cap_reg,) = struct.unpack('<H', f.read(2)) - if (cap_reg & 0xf) >= 2: # v2 capability - len = 60 - # access side effects still need to be analyzed - flags = PCICapability.RD - has_extended_caps = True - elif id == 0x11: # MSI-X - # access will be moderated by hypervisor - len = 12 - (table,) = struct.unpack('<xxI', f.read(6)) - f.seek(0x10 + (table & 7) * 4) - (bar,) = struct.unpack('<I', f.read(4)) - if (bar & 0x3) != 0: - raise RuntimeError('Invalid MSI-X BAR found') - if (bar & 0x4) != 0: - bar |= struct.unpack('<I', f.read(4))[0] << 32 - msix_address = (bar & 0xfffffffffffffff0) + table & 0xfffffff8 - flags = PCICapability.RW - else: - # unknown/unhandled cap, mark its existence - len = 2 - flags = PCICapability.RD - f.seek(cap + 2) - content = f.read(len - 2) - caps.append(PCICapability(id, cap, len, flags, content, - msix_address)) - - if has_extended_caps: - # walk extended capability list - next = 0x100 - while next != 0: - cap = next - f.seek(cap) - (id, version_next) = struct.unpack('<HH', f.read(4)) - next = version_next >> 4 - if id == 0xffff: - break - elif id == 0x0010: # SR-IOV - len = 64 - # access side effects still need to be analyzed - flags = PCICapability.RD - else: - if (id & PCICapability.JAILHOUSE_PCI_EXT_CAP) != 0: - print('WARNING: Ignoring unsupported PCI Express ' - 'Extended Capability ID %x' % id) - continue - # unknown/unhandled cap, mark its existence - len = 4 - flags = PCICapability.RD - f.seek(cap + 4) - content = f.read(len - 4) - id |= PCICapability.JAILHOUSE_PCI_EXT_CAP - caps.append(PCICapability(id, cap, len, flags, content, 0)) - - f.close() - return caps - - -class PCIDevice: - def __init__(self, type, domain, bus, dev, fn, bars, caps, path): - self.type = type - self.iommu = None - self.domain = domain - self.bus = bus - self.dev = dev - self.fn = fn - self.bars = bars - self.caps = caps - self.path = path - self.caps_start = 0 - self.num_caps = len(caps) - self.num_msi_vectors = 0 - self.msi_64bits = 0 - self.num_msix_vectors = 0 - self.msix_region_size = 0 - self.msix_address = 0 - for c in caps: - if c.id in (0x05, 0x11): - msg_ctrl = struct.unpack('<H', c.content[:2])[0] - if c.id == 0x05: # MSI - self.num_msi_vectors = 1 << ((msg_ctrl >> 1) & 0x7) - self.msi_64bits = (msg_ctrl >> 7) & 1 - else: # MSI-X - if c.msix_address != 0: - vectors = (msg_ctrl & 0x7ff) + 1 - self.num_msix_vectors = vectors - self.msix_region_size = (vectors * 16 + 0xfff) & 0xf000 - self.msix_address = c.msix_address - else: - print('WARNING: Ignoring invalid MSI-X configuration' - ' of device %02x:%02x.%x' % (bus, dev, fn)) - - def __str__(self): - return 'PCIDevice: %02x:%02x.%x' % (self.bus, self.dev, self.fn) - - def bdf(self): - return self.bus << 8 | self.dev << 3 | self.fn - - @staticmethod - def parse_pcidevice_sysfsdir(basedir, dir): - dpath = os.path.join(basedir, dir) - f = input_open(os.path.join(dpath, 'config'), 'rb') - (vendor_device,) = struct.unpack('<I', f.read(4)) - if vendor_device == 0xffffffff: - print('WARNING: Ignoring apparently disabled PCI device %s' % dir) - return None - f.seek(0x0A) - (classcode,) = struct.unpack('<H', f.read(2)) - f.close() - if classcode == 0x0604: - type = 'JAILHOUSE_PCI_TYPE_BRIDGE' - else: - type = 'JAILHOUSE_PCI_TYPE_DEVICE' - a = dir.split(':') - domain = int(a[0], 16) - bus = int(a[1], 16) - df = a[2].split('.') - bars = PCIBARs(dpath) - caps = PCICapability.parse_pcicaps(dpath) - return PCIDevice(type, domain, bus, int(df[0], 16), int(df[1], 16), - bars, caps, dpath) - - -class PCIPCIBridge(PCIDevice): - @staticmethod - def get_2nd_busses(dev): - assert dev.type == 'JAILHOUSE_PCI_TYPE_BRIDGE' - f = input_open(os.path.join(dev.path, 'config'), 'rb') - f.seek(0x19) - (secondbus, subordinate) = struct.unpack('<BB', f.read(2)) - f.close() - return (secondbus, subordinate) - - -class MemRegion: - def __init__(self, start, stop, typestr, comments=None): - self.start = start - self.stop = stop - self.typestr = typestr - self.comments = comments or [] - - def __str__(self): - return 'MemRegion: %08x-%08x : %s' % \ - (self.start, self.stop, self.typestr) - - def size(self): - # round up to full PAGE_SIZE - return int((self.stop - self.start + 0xfff) / 0x1000) * 0x1000 - - def flagstr(self, p=''): - if ( - self.typestr == 'System RAM' or - self.typestr == 'Kernel' or - self.typestr == 'RAM buffer' or - self.typestr == 'ACPI DMAR RMRR' or - self.typestr == 'ACPI IVRS' - ): - s = 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE |\n' - s += p + '\t\tJAILHOUSE_MEM_EXECUTE | JAILHOUSE_MEM_DMA' - return s - return 'JAILHOUSE_MEM_READ | JAILHOUSE_MEM_WRITE' - - -class IOAPIC: - def __init__(self, id, address, gsi_base, iommu=0, bdf=0): - self.id = id - self.address = address - self.gsi_base = gsi_base - self.iommu = iommu - self.bdf = bdf - - def __str__(self): - return 'IOAPIC %d, GSI base %d' % (self.id, self.gsi_base) - - def irqchip_id(self): - # encode the IOMMU number into the irqchip ID - return (self.iommu << 16) | self.bdf - - -class IOMemRegionTree: - def __init__(self, region, level): - self.region = region - self.level = level - self.parent = None - self.children = [] - - def __str__(self): - s = '' - if (self.region): - s = (' ' * (self.level - 1)) + str(self.region) - if self.parent and self.parent.region: - s += ' --> ' + self.parent.region.typestr - s += '\n' - for c in self.children: - s += str(c) - return s - - def regions_split_by_kernel(self): - kernel = [x for x in self.children if - x.region.typestr.startswith('Kernel ')] - - if (len(kernel) == 0): - return [self.region] - - r = self.region - s = r.typestr - - kernel_start = kernel[0].region.start - kernel_stop = kernel[len(kernel) - 1].region.stop - - # align this for 16M, but only if we have enough space - kernel_stop = (kernel_stop & ~0xFFFFFF) + 0xFFFFFF - if (kernel_stop > r.stop): - kernel_stop = r.stop - - before_kernel = None - after_kernel = None - - # before Kernel if any - if (r.start < kernel_start): - before_kernel = MemRegion(r.start, kernel_start - 1, s) - - kernel_region = MemRegion(kernel_start, kernel_stop, "Kernel") - - # after Kernel if any - if (r.stop > kernel_stop): - after_kernel = MemRegion(kernel_stop + 1, r.stop, s) - - return [before_kernel, kernel_region, after_kernel] - - @staticmethod - def parse_iomem_line(line): - a = line.split(':', 1) - level = int(a[0].count(' ') / 2) + 1 - region = a[0].split('-', 1) - a[1] = a[1].strip() - return level, MemRegion(int(region[0], 16), int(region[1], 16), a[1]) - - @staticmethod - def parse_iomem_file(): - root = IOMemRegionTree(None, 0) - f = input_open('/proc/iomem') - lastlevel = 0 - lastnode = root - for line in f: - (level, r) = IOMemRegionTree.parse_iomem_line(line) - t = IOMemRegionTree(r, level) - if (t.level > lastlevel): - t.parent = lastnode - if (t.level == lastlevel): - t.parent = lastnode.parent - if (t.level < lastlevel): - p = lastnode.parent - while(t.level < p.level): - p = p.parent - t.parent = p.parent - - t.parent.children.append(t) - lastnode = t - lastlevel = t.level - f.close() - - return root - - # find HPET regions in tree - @staticmethod - def find_hpet_regions(tree): - regions = [] - - for tree in tree.children: - r = tree.region - s = r.typestr - - if (s.find('HPET') >= 0): - regions.append(r) - - # if the tree continues recurse further down ... - if (len(tree.children) > 0): - regions.extend(IOMemRegionTree.find_hpet_regions(tree)) - - return regions - - # recurse down the tree - @staticmethod - def parse_iomem_tree(tree): - regions = [] - - for tree in tree.children: - r = tree.region - s = r.typestr - - # System RAM on the first level will be added completely, - # if they don't contain the kernel itself, if they do, - # we split them - if (tree.level == 1 and s == 'System RAM'): - regions.extend(tree.regions_split_by_kernel()) - continue - - # blacklisted on all levels - if ( - (s.find('PCI MMCONFIG') >= 0) or - (s.find('APIC') >= 0) # covers both APIC and IOAPIC - ): - continue - - # generally blacklisted, unless we find an HPET behind it - if (s.lower() == 'reserved'): - regions.extend(IOMemRegionTree.find_hpet_regions(tree)) - continue - - # if the tree continues recurse further down ... - if (len(tree.children) > 0): - regions.extend(IOMemRegionTree.parse_iomem_tree(tree)) - continue - - # add all remaining leaves - regions.append(r) - - return regions - - -class IOMMUConfig: - def __init__(self, props): - self.base_addr = props['base_addr'] - self.mmio_size = props['mmio_size'] - if 'amd_bdf' in props: - self.amd_bdf = props['amd_bdf'] - self.amd_base_cap = props['amd_base_cap'] - self.amd_msi_cap = props['amd_msi_cap'] - self.amd_features = props['amd_features'] - - @property - def is_amd_iommu(self): - return hasattr(self, 'amd_bdf') - - -def parse_iomem(pcidevices): - regions = IOMemRegionTree.parse_iomem_tree( - IOMemRegionTree.parse_iomem_file()) - - rom_region = MemRegion(0xc0000, 0xdffff, 'ROMs') - add_rom_region = False - - ret = [] - dmar_regions = [] - for r in regions: - append_r = True - # filter the list for MSI-X pages - for d in pcidevices: - if d.msix_address >= r.start and d.msix_address <= r.stop: - if d.msix_address > r.start: - head_r = MemRegion(r.start, d.msix_address - 1, - r.typestr, r.comments) - ret.append(head_r) - if d.msix_address + d.msix_region_size < r.stop: - tail_r = MemRegion(d.msix_address + d.msix_region_size, - r.stop, r.typestr, r.comments) - ret.append(tail_r) - append_r = False - break - # filter out the ROMs - if (r.start >= rom_region.start and r.stop <= rom_region.stop): - add_rom_region = True - append_r = False - # filter out and save DMAR regions - if r.typestr.find('dmar') >= 0: - dmar_regions.append(r) - append_r = False - if append_r: - ret.append(r) - - # add a region that covers all potential ROMs - if add_rom_region: - ret.append(rom_region) - - # newer Linux kernels will report the first page as reserved - # it is needed for CPU init so include it anyways - if (ret[0].typestr == 'System RAM' and ret[0].start == 0x1000): - ret[0].start = 0 - - return ret, dmar_regions - - -def parse_pcidevices(): - int_src_cnt = 0 - devices = [] - caps = [] - basedir = '/sys/bus/pci/devices' - list = input_listdir(basedir, ['*/config']) - for dir in list: - d = PCIDevice.parse_pcidevice_sysfsdir(basedir, dir) - if d is not None: - if len(d.caps) > 0: - duplicate = False - # look for duplicate capability patterns - for d2 in devices: - if d2.caps == d.caps: - # reused existing capability list, but record all users - d2.caps[0].comments.append(str(d)) - d.caps_start = d2.caps_start - duplicate = True - break - if not duplicate: - d.caps[0].comments.append(str(d)) - d.caps_start = len(caps) - caps.extend(d.caps) - int_src_cnt += max(d.num_msi_vectors, d.num_msix_vectors) - devices.append(d) - return (devices, caps, int_src_cnt) - - def parse_kernel_cmdline(): line = input_readline('/proc/cmdline') ma = re.findall(r'memmap=([0-9a-fA-FxX]+)([KMG]?)\$' @@ -696,11 +138,12 @@ def alloc_mem(regions, size): r.stop + 1 >= mem[0] + mem[1] ): if r.start < mem[0]: - head_r = MemRegion(r.start, mem[0] - 1, r.typestr, r.comments) + head_r = sysfs_parser.MemRegion(r.start, mem[0] - 1, r.typestr, + r.comments) regions.insert(regions.index(r), head_r) if r.stop + 1 > mem[0] + mem[1]: - tail_r = MemRegion(mem[0] + mem[1], r.stop, r.typestr, - r.comments) + tail_r = sysfs_parser.MemRegion(mem[0] + mem[1], r.stop, + r.typestr, r.comments) regions.insert(regions.index(r), tail_r) regions.remove(r) return mem @@ -713,7 +156,7 @@ def alloc_mem(regions, size): def count_cpus(): - list = input_listdir('/sys/devices/system/cpu', ['cpu*/uevent']) + list = sysfs_parser.input_listdir('/sys/devices/system/cpu', ['cpu*/uevent']) count = 0 for f in list: if re.match(r'cpu[0-9]+', f): @@ -721,341 +164,9 @@ def count_cpus(): return count -def parse_madt(): - f = input_open('/sys/firmware/acpi/tables/APIC', 'rb') - signature = f.read(4) - if signature != b'APIC': - raise RuntimeError('MADT: incorrect input file format %s' % signature) - (length,) = struct.unpack('<I', f.read(4)) - f.seek(44) - length -= 44 - ioapics = [] - - while length > 0: - offset = 0 - (struct_type, struct_len) = struct.unpack('<BB', f.read(2)) - offset += 2 - length -= struct_len - - if struct_type == 1: - (id, address, gsi_base) = struct.unpack('<BxII', f.read(10)) - offset += 10 - ioapics.append(IOAPIC(id, address, gsi_base)) - - f.seek(struct_len - offset, os.SEEK_CUR) - - f.close() - return ioapics - - -def parse_dmar_devscope(f): - (scope_type, scope_len, id, bus, dev, fn) = \ - struct.unpack('<BBxxBBBB', f.read(8)) - if scope_len != 8: - raise RuntimeError('Unsupported DMAR Device Scope Structure') - return (scope_type, scope_len, id, bus, dev, fn) - - -# parsing of DMAR ACPI Table -# see Intel VT-d Spec chapter 8 -def parse_dmar(pcidevices, ioapics, dmar_regions): - f = input_open('/sys/firmware/acpi/tables/DMAR', 'rb') - signature = f.read(4) - if signature != b'DMAR': - raise RuntimeError('DMAR: incorrect input file format %s' % signature) - (length,) = struct.unpack('<I', f.read(4)) - f.seek(48) - length -= 48 - units = [] - regions = [] - - while length > 0: - offset = 0 - (struct_type, struct_len) = struct.unpack('<HH', f.read(4)) - offset += 4 - length -= struct_len - - # DMA Remapping Hardware Unit Definition - if struct_type == 0: - (flags, segment, base) = struct.unpack('<BxHQ', f.read(12)) - if segment != 0: - raise RuntimeError('We do not support multiple PCI segments') - if len(units) >= 8: - raise RuntimeError('Too many DMAR units. ' - 'Raise JAILHOUSE_MAX_IOMMU_UNITS.') - size = 0 - for r in dmar_regions: - if base == r.start: - size = r.size() - if size == 0: - raise RuntimeError('DMAR region size cannot be identified.\n' - 'Target Linux must run with Intel IOMMU ' - 'enabled.') - if size > 0x3000: - raise RuntimeError('Unexpectedly large DMAR region.') - units.append(IOMMUConfig({ - 'base_addr': base, - 'mmio_size': size - })) - if flags & 1: - for d in pcidevices: - if d.iommu is None: - d.iommu = len(units) - 1 - offset += 16 - offset - while offset < struct_len: - (scope_type, scope_len, id, bus, dev, fn) =\ - parse_dmar_devscope(f) - # PCI Endpoint Device - if scope_type == 1: - assert not (flags & 1) - for d in pcidevices: - if d.bus == bus and d.dev == dev and d.fn == fn: - d.iommu = len(units) - 1 - break - # PCI Sub-hierarchy - elif scope_type == 2: - assert not (flags & 1) - for d in pcidevices: - if d.bus == bus and d.dev == dev and d.fn == fn: - (secondbus, subordinate) = \ - PCIPCIBridge.get_2nd_busses(d) - for d2 in pcidevices: - if ( - d2.bus >= secondbus and - d2.bus <= subordinate - ): - d2.iommu = len(units) - 1 - break - # IOAPIC - elif scope_type == 3: - ioapic = next(chip for chip in ioapics if chip.id == id) - bdf = (bus << 8) | (dev << 3) | fn - for chip in ioapics: - if chip.bdf == bdf: - raise RuntimeError('IOAPICs with identical BDF') - ioapic.bdf = bdf - ioapic.iommu = len(units) - 1 - offset += scope_len - - # Reserved Memory Region Reporting Structure - if struct_type == 1: - f.seek(8 - offset, os.SEEK_CUR) - offset += 8 - offset - (base, limit) = struct.unpack('<QQ', f.read(16)) - offset += 16 - - comments = [] - while offset < struct_len: - (scope_type, scope_len, id, bus, dev, fn) =\ - parse_dmar_devscope(f) - if scope_type == 1: - comments.append('PCI device: %02x:%02x.%x' % - (bus, dev, fn)) - else: - comments.append('DMAR parser could not decode device path') - offset += scope_len - - reg = MemRegion(base, limit, 'ACPI DMAR RMRR', comments) - regions.append(reg) - - f.seek(struct_len - offset, os.SEEK_CUR) - - f.close() - - for d in pcidevices: - if d.iommu is None: - raise RuntimeError( - 'PCI device %02x:%02x.%x outside the scope of an ' - 'IOMMU' % (d.bus, d.dev, d.fn)) - - return units, regions - - -def parse_ivrs(pcidevices, ioapics): - def format_bdf(bdf): - bus, dev, fun = (bdf >> 8) & 0xff, (bdf >> 3) & 0x1f, bdf & 0x7 - return '%02x:%02x.%x' % (bus, dev, fun) - - f = input_open('/sys/firmware/acpi/tables/IVRS', 'rb') - signature = f.read(4) - if signature != b'IVRS': - raise RuntimeError('IVRS: incorrect input file format %s' % signature) - - (length, revision) = struct.unpack('<IB', f.read(5)) - if revision > 2: - raise RuntimeError('IVRS: unsupported Revision %02x' % revision) - - f.seek(48, os.SEEK_SET) - length -= 48 - - units = [] - regions = [] - # BDF of devices that are permitted outside IOMMU: root complex - iommu_skiplist = set([0x0]) - ivhd_blocks = 0 - while length > 0: - (block_type, block_length) = struct.unpack('<BxH', f.read(4)) - if block_type in [0x10, 0x11]: - ivhd_blocks += 1 - if ivhd_blocks > 1: - raise RuntimeError('Jailhouse doesn\'t support more than one ' - 'AMD IOMMU per PCI function.') - # IVHD block - ivhd_fields = struct.unpack('<HHQHxxL', f.read(20)) - (iommu_bdf, base_cap_ofs, - base_addr, pci_seg, iommu_feat) = ivhd_fields - - length -= block_length - block_length -= 24 - - if pci_seg != 0: - raise RuntimeError('We do not support multiple PCI segments') - - if len(units) > 8: - raise RuntimeError('Too many IOMMU units. ' - 'Raise JAILHOUSE_MAX_IOMMU_UNITS.') - - msi_cap_ofs = None - - for i, d in enumerate(pcidevices): - if d.bdf() == iommu_bdf: - # Extract MSI capability offset - for c in d.caps: - if c.id == 0x05: - msi_cap_ofs = c.start - # We must not map IOMMU to the cells - del pcidevices[i] - - if msi_cap_ofs is None: - raise RuntimeError('AMD IOMMU lacks MSI support, and ' - 'Jailhouse doesn\'t support MSI-X yet.') - - if (iommu_feat & (0xF << 13)) and (iommu_feat & (0x3F << 17)): - # Performance Counters are supported, allocate 512K - mmio_size = 524288 - else: - # Allocate 16K - mmio_size = 16384 - - units.append(IOMMUConfig({ - 'base_addr': base_addr, - 'mmio_size': mmio_size, - 'amd_bdf': iommu_bdf, - 'amd_base_cap': base_cap_ofs, - 'amd_msi_cap': msi_cap_ofs, - # IVHD block type 0x11 has exact EFR copy but type 0x10 may - # overwrite what hardware reports. Set reserved bit 0 in that - # case to indicate that the value is in use. - 'amd_features': (iommu_feat | 0x1) if block_type == 0x10 else 0 - })) - - bdf_start_range = None - while block_length > 0: - (entry_type, device_id) = struct.unpack('<BHx', f.read(4)) - block_length -= 4 - - if entry_type == 0x01: - # All - for d in pcidevices: - d.iommu = len(units) - 1 - elif entry_type == 0x02: - # Select - for d in pcidevices: - if d.bdf() == device_id: - d.iommu = len(units) - 1 - elif entry_type == 0x03: - # Start of range - bdf_start_range = device_id - elif entry_type == 0x04: - # End of range - if bdf_start_range is None: - continue - for d in pcidevices: - if d.bdf() >= bdf_start_range and d.bdf() <= device_id: - d.iommu = len(units) - 1 - bdf_start_range = None - elif entry_type == 0x42: - # Alias select - (device_id_b,) = struct.unpack('<xHx', f.read(4)) - block_length -= 4 - for d in pcidevices: - if d.bdf() == device_id_b: - d.iommu = len(units) - 1 - elif entry_type == 0x43: - # Alias start of range - (device_id_b,) = struct.unpack('<xHx', f.read(4)) - block_length -= 4 - bdf_start_range = device_id_b - elif entry_type == 0x48: - # Special device - (handle, device_id_b, variety) = struct.unpack( - '<BHB', f.read(4)) - block_length -= 4 - if variety == 0x01: # IOAPIC - for chip in ioapics: - if chip.id == handle: - chip.bdf = device_id - chip.iommu = len(units) - 1 - else: - # Reserved or ignored entries - if entry_type >= 0x40: - f.seek(4, os.SEEK_CUR) - block_length -= 4 - - elif type in [0x20, 0x21, 0x22]: - # IVMD block - ivmd_fields = struct.unpack('<BBHHHxxxxxxxxQQ', f.read(32)) - (block_type, block_flags, block_length, - device_id, aux_data, mem_addr, mem_len) = ivmd_fields - length -= block_length - - if int(block_flags): - bdf_str = format_bdf(device_id) - print( - 'WARNING: Jailhouse doesn\'t support configurable ' - '(eg. read-only) device memory. Device %s may not ' - 'work properly, especially in non-root cell.' % bdf_str) - - if block_type == 0x20: - # All devices - comment = None - elif block_type == 0x21: - # Selected device - comment = 'PCI Device: %s' % format_bdf(device_id) - elif block_type == 0x22: - # Device range - comment = 'PCI Device: %s - %s' % ( - format_bdf(device_id), format_bdf(aux_data)) - - if comment: - print('WARNING: Jailhouse doesn\'t support per-device memory ' - 'regions. The memory at 0x%x will be mapped accessible ' - 'to all devices.' % mem_addr) - - regions.append(MemRegion(mem_addr, mem_len, 'ACPI IVRS', comment)) - elif type == 0x40: - raise RuntimeError( - 'You board uses IVRS Rev. 2 feature Jailhouse doesn\'t ' - 'support yet. Please report this to ' - '[email protected].') - else: - print( - 'WARNING: Skipping unknown IVRS ' - 'block type 0x%02x' % block_type) - - for d in pcidevices: - if d.bdf() not in iommu_skiplist and d.iommu is None: - raise RuntimeError( - 'PCI device %02x:%02x.%x outside the scope of an ' - 'IOMMU' % (d.bus, d.dev, d.fn)) - - f.close() - return units, regions - - def parse_ioports(): pm_timer_base = None - f = input_open('/proc/ioports') + f = sysfs_parser.input_open('/proc/ioports') for line in f: if line.endswith('ACPI PM_TMR\n'): pm_timer_base = int(line.split('-')[0], 16) @@ -1071,7 +182,7 @@ class MMConfig: @staticmethod def parse(): - f = input_open('/sys/firmware/acpi/tables/MCFG', 'rb') + f = sysfs_parser.input_open('/sys/firmware/acpi/tables/MCFG', 'rb') signature = f.read(4) if signature != b'MCFG': raise RuntimeError('MCFG: incorrect input file format %s' % @@ -1088,20 +199,6 @@ class MMConfig: return MMConfig(base, end_bus) -def get_cpu_vendor(): - global cpuvendor - if cpuvendor is not None: - return cpuvendor - with input_open('/proc/cpuinfo', 'r') as f: - for line in f: - if not line.strip(): - continue - key, value = line.split(':') - if key.strip() == 'vendor_id': - cpuvendor = value.strip() - return cpuvendor - - class DebugConsole: def __init__(self, console): self.address = 0 @@ -1131,10 +228,10 @@ class DebugConsole: if options.generate_collector: f = open(options.file, 'w') - filelist = ' '.join(inputs['files']) - filelist_opt = ' '.join(inputs['files_opt']) - filelist_intel = ' '.join(inputs['files_intel']) - filelist_amd = ' '.join(inputs['files_amd']) + filelist = ' '.join(sysfs_parser.inputs['files']) + filelist_opt = ' '.join(sysfs_parser.inputs['files_opt']) + filelist_intel = ' '.join(sysfs_parser.inputs['files_intel']) + filelist_amd = ' '.join(sysfs_parser.inputs['files_amd']) tmpl = Template(filename=os.path.join(options.template_dir, 'jailhouse-config-collect.tmpl')) @@ -1157,7 +254,9 @@ if jh_enabled == '1': IOAPIC_MAX_PINS = 120 int_src_count = IOAPIC_MAX_PINS -(pcidevices, pcicaps, cnt) = parse_pcidevices() +sysfs_parser.root_dir = options.root + +(pcidevices, pcicaps, cnt) = sysfs_parser.parse_pcidevices() int_src_count += cnt vtd_interrupt_limit = 2**math.ceil(math.log(int_src_count, 2)) @@ -1171,20 +270,20 @@ product = [input_readline('/sys/class/dmi/id/sys_vendor', inmatemem = kmg_multiply_str(options.mem_inmates) hvmem = [0, kmg_multiply_str(options.mem_hv)] -(regions, dmar_regions) = parse_iomem(pcidevices) +(regions, dmar_regions) = sysfs_parser.parse_iomem(pcidevices) ourmem = parse_kernel_cmdline() total = hvmem[1] + inmatemem mmconfig = MMConfig.parse() -ioapics = parse_madt() +ioapics = sysfs_parser.parse_madt() -vendor = get_cpu_vendor() +vendor = sysfs_parser.get_cpu_vendor() if vendor == 'GenuineIntel': - (iommu_units, extra_memregs) = parse_dmar(pcidevices, ioapics, - dmar_regions) + (iommu_units, extra_memregs) = sysfs_parser.parse_dmar(pcidevices, ioapics, + dmar_regions) else: - (iommu_units, extra_memregs) = parse_ivrs(pcidevices, ioapics) + (iommu_units, extra_memregs) = sysfs_parser.parse_ivrs(pcidevices, ioapics) regions += extra_memregs # kernel does not have memmap region, pick one @@ -1197,9 +296,9 @@ elif (total > ourmem[1]): hvmem[0] = ourmem[0] -inmatereg = MemRegion(ourmem[0] + hvmem[1], - ourmem[0] + hvmem[1] + inmatemem - 1, - 'JAILHOUSE Inmate Memory') +inmatereg = sysfs_parser.MemRegion(ourmem[0] + hvmem[1], + ourmem[0] + hvmem[1] + inmatemem - 1, + 'JAILHOUSE Inmate Memory') regions.append(inmatereg) cpucount = count_cpus() diff --git a/tools/jailhouse-hardware-check b/tools/jailhouse-hardware-check index ae4879d..1c7d1b9 100755 --- a/tools/jailhouse-hardware-check +++ b/tools/jailhouse-hardware-check @@ -25,6 +25,7 @@ if sys.version_info[0] < 3: check_passed = True ran_all = True + def check_feature(msg, ok, optional=False): if not (ok or optional): global check_passed -- 2.7.4 -- You received this message because you are subscribed to the Google Groups "Jailhouse" group. To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]. For more options, visit https://groups.google.com/d/optout.
