#!/usr/bin/env python # # Jailhouse, a Linux-based partitioning hypervisor # # Copyright (c) Siemens AG, 2014 # # 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. from __future__ import print_function import sys import os import re import argparse import struct from mako.template import Template abspath = os.path.abspath(os.path.dirname(sys.argv[0])) # pretend to be part of the jailhouse tool sys.argv[0] = sys.argv[0].replace('-', ' ') parser = argparse.ArgumentParser() parser.add_argument('-g', '--generate-collector', help='generate a script to collect input files on ' 'a remote machine', action='store_true') parser.add_argument('-r', '--root', help='gather information in ROOT/, the default is "/" ' 'which means creating a config for localhost', default='/', action='store', type=str) parser.add_argument('-t', '--template-dir', help='the directory where the templates are located,' 'the default is "' + abspath + '"', default=abspath, action='store', type=str) memargs = [['--mem-inmates', '2M', 'inmate'], ['--mem-hv', '64M', 'hypervisor']] for entry in memargs: parser.add_argument(entry[0], help='the amount of ' + entry[2] + ' memory, default is "' + entry[1] + '", format "xxx[K|M|G]"', default=entry[1], action='store', type=str) parser.add_argument('file', metavar='FILE', help='name of file to write out', type=str) options = parser.parse_args() inputs = {'files': set(), 'files_opt': set(), 'dirs': set()} def kmg_multiply(value, kmg): if (kmg == 'K' or kmg == 'k'): return 1024 * value if (kmg == 'M' or kmg == 'm'): return 1024**2 * value if (kmg == 'G' or kmg == 'g'): return 1024**3 * value return value def kmg_multiply_str(str): m = re.match(r'([0-9a-fA-FxX]+)([KMG]?)', str) if m is not None: return kmg_multiply(int(m.group(1)), m.group(2)) raise RuntimeError('kmg_multiply_str can not parse input "' + str + '"') return 0 def input_open(name, mode='r', optional=False): inputs['files_opt' if optional else 'files'].add(name) try: f = open(options.root + name, mode) except Exception as e: if optional or options.generate_collector: return open("/dev/null", mode) raise e return f def input_readline(name, optional=False): f = input_open(name, optional=optional) line = f.readline() f.close() return line def input_listdir(dir, wildcards): for w in wildcards: inputs['dirs'].add(os.path.join(dir, w)) if options.generate_collector: return [] dirs = os.listdir(options.root + dir) dirs.sort() return dirs class PCICapability: def __init__(self, id, start, len, flags, content): self.id = id self.start = start self.len = len self.flags = flags self.content = content 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' @staticmethod def parse_pcicaps(dir): caps = [] f = input_open(os.path.join(dir, 'config'), 'rb') f.seek(0x06) (status,) = struct.unpack('> 1) & 0x7) self.msi_64bits = (msg_ctrl >> 7) & 1 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) dclass = input_readline(os.path.join(dpath, 'class')) if re.match(r'0x0604..', dclass): 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('.') caps = PCICapability.parse_pcicaps(dpath) return PCIDevice(type, domain, bus, int(df[0], 16), int(df[1], 16), caps) class MemRegion: def __init__(self, start, stop, typestr, comments=None): self.start = start self.stop = stop self.typestr = typestr if comments is None: self.comments = [] else: self.comments = comments 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 == 'RAM buffer' or self.typestr == 'ACPI DMAR RMRR' ): 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 IOMemRegionTree: def __init__(self, region, level, linenum): self.region = region self.linenum = linenum self.level = level self.parent = None self.children = set() def __str__(self): s = '' if (self.region): s = (' ' * (self.level - 1)) + str(self.region) + ' line %d' \ % (self.linenum) if self.parent and self.parent.region: s += '--> ' + self.parent.region.typestr + ' line %d' \ % (self.parent.linenum) s += '\n' for c in self.children: s += str(c) return s @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, -1) f = input_open('/proc/iomem') lastlevel = 0 lastnode = root linenum = 0 for line in f: (level, r) = IOMemRegionTree.parse_iomem_line(line) t = IOMemRegionTree(r, level, linenum) 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.add(t) lastnode = t lastlevel = t.level linenum += 1 f.close() return linenum, root # recurse down the tree @staticmethod def parse_iomem_tree(tree): regions = [] linenumbers = [] for tree in tree.children: r = tree.region s = r.typestr # System RAM on first level will be added without digging deeper if (tree.level == 1 and s == 'System RAM'): regions.append(r) linenumbers.append(tree.linenum) continue # blacklisted on all levels if ( (s.find('PCI MMCONFIG') >= 0) or (s.find('APIC') >= 0) or # covers both APIC and IOAPIC (s.find('dmar') >= 0) ): continue # generally blacklisted, unless we find an HPET right behind it # on the next level if (s == 'reserved'): for subtree in tree.children: r2 = subtree.region if (r2.typestr.find('HPET') >= 0): regions.append(r2) linenumbers.append(subtree.linenum) continue # if the tree continues recurse further down ... if (len(tree.children) > 0): ln2, r2 = IOMemRegionTree.parse_iomem_tree(tree) linenumbers.extend(ln2) regions.extend(r2) continue # add all remaining leaves regions.append(r) linenumbers.append(tree.linenum) return linenumbers, regions def parse_iomem(): (maxsz, tree) = IOMemRegionTree.parse_iomem_file() # create a spare array so we can easiely keep the order from the file regions = [None for x in range(maxsz)] lines, regs = IOMemRegionTree.parse_iomem_tree(tree) i = 0 for l in lines: regions[l] = regs[i] i += 1 # now prepare a non-sparse array for a return value ret = [] for r in regions: if r: ret.append(r) # 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 def parse_pcidevices(): devices = [] caps = [] basedir = '/sys/bus/pci/devices' list = input_listdir(basedir, ['*/class', '*/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) devices.append(d) return (devices, caps) def parse_cmdline(): line = input_readline('/proc/cmdline') m = re.match(r'.*memmap=([0-9a-fA-FxX]+)([KMG]?)\$' '([0-9a-fA-FxX]+)([KMG]?).*', line) if m is not None: size = kmg_multiply(int(m.group(1), 0), m.group(2)) start = kmg_multiply(int(m.group(3), 0), m.group(4)) return [start, size] return None def alloc_mem(regions, size): mem = [0, size] for r in reversed(regions): if (r.typestr == 'System RAM' and r.size() >= mem[1]): mem[0] = r.start r.start += mem[1] return mem raise RuntimeError('failed to allocate memory') def count_cpus(): list = input_listdir('/sys/devices/system/cpu', ['cpu*/uevent']) count = 0 for f in list: if re.match(r'cpu[0-9]+', f): count += 1 return count def parse_dmar_devscope(f): (scope_type, scope_len, bus, dev, fn) = \ struct.unpack(' 0: offset = 0 (struct_type, struct_len) = struct.unpack('= 8: raise RuntimeError('Too many DMAR units. ' 'Raise JAILHOUSE_MAX_DMAR_UNITS.') units.append(base) offset += 16 - offset while offset < struct_len: (scope_type, scope_len, bus, dev, fn) =\ parse_dmar_devscope(f) if scope_type == 3: if ioapic_id != 0: raise RuntimeError('We do not support more ' 'than 1 IOAPIC') ioapic_id = (bus << 8) | (dev << 3) | fn 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(' 60: raise RuntimeError('Multiple MMCONFIG regions found! ' 'This is not supported') f.seek(44) (base, segment, start_bus, end_bus) = \ struct.unpack(' ourmem[1]): raise RuntimeError('Your memmap reservation is too small you need >="' + hex(total) + '"') hvmem[0] = ourmem[0] inmatereg = MemRegion(ourmem[0] + hvmem[1], ourmem[0] + hvmem[1] + inmatemem - 1, 'JAILHOUSE Inmate Memory') regions.append(inmatereg) cpucount = count_cpus() pm_timer_base = parse_ioports() jh_enabled = input_readline('/sys/devices/jailhouse/enabled', True).rstrip() if options.generate_collector is False and jh_enabled == '1': print('ERROR: Jailhouse was enabled when collecting input files! ' 'Disable jailhouse and try again.', file=sys.stderr) sys.exit(1) f = open(options.file, 'w') if options.generate_collector: filelist = ' '.join(inputs['files'].union(inputs['dirs'])) filelist_opt = ' '.join(inputs['files_opt']) tmpl = Template(filename=os.path.join(options.template_dir, 'jailhouse-config-collect.tmpl')) f.write(tmpl.render(filelist=filelist, filelist_opt=filelist_opt)) else: tmpl = Template(filename=os.path.join(options.template_dir, 'root-cell-config.c.tmpl')) f.write(tmpl.render(regions=regions, ourmem=ourmem, argstr=' '.join(sys.argv), hvmem=hvmem, product=product, pcidevices=pcidevices, pcicaps=pcicaps, cpucount=cpucount, ioapic_id=ioapic_id, pm_timer_base=pm_timer_base, mmconfig=mmconfig, dmar_units=dmar_units)) f.close()