]> rtime.felk.cvut.cz Git - jailhouse.git/commitdiff
tools: Add configuration generator
authorHenning Schild <henning.schild@siemens.com>
Mon, 7 Jul 2014 10:46:00 +0000 (12:46 +0200)
committerJan Kiszka <jan.kiszka@siemens.com>
Mon, 7 Jul 2014 11:28:34 +0000 (13:28 +0200)
Adding a helper script to generate a configuration for the root cell.
The script can also generate another script to collect all the necessary
files on a remote machine.
Both scripts can be accessed through the jailhouse command.

Signed-off-by: Henning Schild <henning.schild@siemens.com>
Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
.gitignore
tools/Makefile
tools/jailhouse-config-collect.tmpl [new file with mode: 0644]
tools/jailhouse-config-create [new file with mode: 0755]
tools/jailhouse.c
tools/root-cell-config.c.tmpl [new file with mode: 0644]

index f5fcb2cddd80cfa814f171378a2c01fd137a051f..5be98d5a937ec7e1f74988e8258ab89c30e420e9 100644 (file)
@@ -11,4 +11,5 @@ jailhouse.ko
 hypervisor/include/jailhouse/config.h
 hypervisor/hypervisor.lds
 tools/jailhouse
+tools/jailhouse-config-collect
 configs/*.cell
index bfddc3b3cf4908a905a7096a3579acbb4c103fd4..45554e4c353d19f9190ac15364711a7e87cad6dd 100644 (file)
@@ -15,8 +15,34 @@ CC = $(CROSS_COMPILE)gcc
 CFLAGS = -g -O3 -I.. -I../hypervisor/include \
        -Wall -Wmissing-declarations -Wmissing-prototypes
 
+TARGETS := jailhouse
+
+HAS_PYTHON_MAKO := \
+       $(shell python -c "from mako.template import Template" 2>/dev/null \
+       && echo yes)
+
+ifeq ($(strip $(HAS_PYTHON_MAKO)), yes)
+       TARGETS += jailhouse-config-collect
+else
+       TARGETS += no_python_mako
+endif
+
+all: $(TARGETS)
+
 jailhouse: jailhouse.c ../jailhouse.h ../hypervisor/include/jailhouse/cell-config.h
        $(CC) $(CFLAGS) -o $@ $<
 
+jailhouse-config-collect: jailhouse-config-create jailhouse-config-collect.tmpl
+       ./$< -g $@
+       chmod +x $@
+
+.PHONY: clean no_python_mako
+
 clean:
-       rm -f jailhouse
+       rm -f $(TARGETS)
+
+no_python_mako:
+       @echo -ne "WARNING: Could not create the helper script to generate" \
+                 "configurations on remote machines" \
+                 "(\"jailhouse-conf-collect\"). You need Python and the" \
+                 "Mako library for it.\n"
diff --git a/tools/jailhouse-config-collect.tmpl b/tools/jailhouse-config-collect.tmpl
new file mode 100644 (file)
index 0000000..a298fb9
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+#
+# 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 will collect information needed to generate a Jailhouse
+# configuration for hypervisor and root cell (Linux).
+#
+# Run it like that:
+#  $ jailhouse-config-collect.sh mytarget.tar
+#
+# Copying files and directories from /sys and /proc is surprisingly hard
+# it would be nice to use just one tool together with the list of files.
+# The main problem is that stat does not report the correct file sizes. In
+# procfs files seem to have a size of 0 while in sysfs they ofter appear
+# bigger than they really are.
+# Archivers like tar/cpio etc. can not be used for procfs and sysfs.
+# This scripts first gets a temporary copy of all the files we want. After
+# copying the files can be archived with tar.
+
+set -e
+
+if test -z "$1"; then
+       echo "Usage: $0 mytarget.tar" 1>&2
+       exit 1
+fi
+
+filelist="${filelist}"
+
+tmpdir=/tmp/jailhouse-config-collect.$$
+
+rm -rf $tmpdir
+mkdir $tmpdir
+
+# copy all the files we need to a temporary directory first
+for f in $filelist
+do
+       if [ -f $f ]
+       then
+               dstdir=$tmpdir/$(dirname $f)
+               if [ ! -d $dstdir ]
+               then
+                       mkdir -p $dstdir
+               fi
+               cp -p $f $tmpdir/$f
+       else
+               echo "Warning: $f does not exist" 1>&2
+       fi
+done
+
+# now archive it and remove temporary copy
+tar -C $tmpdir -cf $1 .
+rm -rf $tmpdir
+
+exit 0
diff --git a/tools/jailhouse-config-create b/tools/jailhouse-config-create
new file mode 100755 (executable)
index 0000000..ce19607
--- /dev/null
@@ -0,0 +1,382 @@
+#!/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.
+
+import sys
+import os
+import re
+import argparse
+import struct
+from mako.template import Template
+
+# 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)
+
+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(), 'dirs': set()}
+
+
+class PCIDevice:
+    def __init__(self, type, domain, bus, dev, fn):
+        self.type = type
+        self.domain = domain
+        self.bus = bus
+        self.dev = dev
+        self.fn = fn
+
+    def __str__(self):
+        return 'PCIDevice: %02x:%02x.%x' % (self.bus, self.dev, self.fn)
+
+    def devfn(self):
+        return self.dev << 3 | self.fn
+
+    @staticmethod
+    def parse_pcidevice_sysfsdir(basedir, dir):
+        dclass = input_readline(basedir + '/' + dir + '/class', False, False)
+        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('.')
+        return PCIDevice(type, domain, bus, int(df[0], 16), int(df[1], 16))
+
+
+class MemRegion:
+    def __init__(self, start, stop, typestr, comments=[]):
+        self.start = start
+        self.stop = stop
+        self.typestr = typestr
+        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 == 'ACPI Tables' or
+            self.typestr == 'ACPI Non-volatile Storage'
+        ):
+            return 'JAILHOUSE_MEM_READ'
+        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'
+
+    @staticmethod
+    # return the first region with the given typestr
+    def find_region(regions, typestr):
+        for r in regions:
+            if (r.typestr == typestr):
+                return r
+        return None
+
+    @staticmethod
+    def parse_iomem_line(line):
+        a = line.split(':', 1)
+        # HPET may be part of in reserved region
+        if a[0].startswith(' ') and a[1].find("HPET") < 0:
+            return None
+        region = a[0].split('-', 1)
+        a[1] = a[1].strip()
+        return MemRegion(int(region[0], 16), int(region[1], 16), a[1])
+
+
+def parse_iomem():
+    regions = []
+    f, e = input_open('/proc/iomem', True, False, 'r')
+    for line in f:
+        r = MemRegion.parse_iomem_line(line)
+        ## XXX what else to ignore??
+        if (
+            r is not None and
+            r.typestr != 'Local APIC' and
+            r.typestr != 'reserved'
+        ):
+            regions.append(r)
+    f.close()
+
+    # newer Linux kernels will report the first page as reserved
+    # it is needed for CPU init so include it anyways
+    if (
+        regions[0].typestr == 'System RAM' and
+        regions[0].start == 0x1000
+    ):
+        regions[0].start = 0
+
+    return regions
+
+
+def parse_pcidevices():
+    devices = []
+    basedir = '/sys/bus/pci/devices'
+    list = input_listdir(basedir, ['*/class'])
+    for dir in list:
+        d = PCIDevice.parse_pcidevice_sysfsdir(basedir, dir)
+        if d is not None:
+            devices.append(d)
+    return devices
+
+
+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, record, optional, *args):
+    if record:
+        inputs['files'].add(name)
+    try:
+        f = open(options.root + name, *args)
+    except Exception as e:
+        if optional:
+            return None, e
+        raise e
+    return f, None
+
+
+def input_readline(name, record=True, optional=False):
+    f, e = input_open(name, record, optional, 'r')
+    if f is None and optional:
+        return ''
+
+    line = f.readline()
+    f.close()
+    return line
+
+
+def input_listdir(dir, wildcards):
+    for w in wildcards:
+        inputs['dirs'].add(dir + '/' + w)
+    return os.listdir(options.root + dir)
+
+
+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*/topology/core_id'])
+    count = 0
+    for f in list:
+        if re.match(r'cpu[0-9]+', f):
+            count += 1
+    return count
+
+
+def parse_dmar_devscope(f):
+    offset = 0
+    (scope_type, scope_len, bus, dev, fn) = \
+        struct.unpack('<BBxxxBBB', f.read(8))
+    offset += 8
+    return (offset, scope_type, scope_len, bus, dev, fn)
+
+
+# parsing of DMAR ACPI Table
+# see Intel VT-d Spec chapter 8
+def parse_dmar():
+    f, e = input_open('/sys/firmware/acpi/tables/DMAR', True, True, 'rb')
+    if not f:
+        if options.generate_collector:
+            return 0, []
+        if e:
+            raise e
+        raise RuntimeError('could not find DMAR ACPI table')
+
+    signature = f.read(4)
+    if signature != b'DMAR':
+        raise RuntimeError('incorrect input file format %s' % signature)
+    (length,) = struct.unpack('<I', f.read(4))
+    f.seek(48)
+    length -= 48
+    regions = []
+    ioapic_id = 0
+
+    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:
+            f.seek(16 - offset, os.SEEK_CUR)
+            offset += 16 - offset
+            while offset < struct_len:
+                (off, scope_type, scope_len, bus, dev, fn) =\
+                    parse_dmar_devscope(f)
+                offset += off
+                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
+                f.seek(scope_len - 8, os.SEEK_CUR)
+                offset += scope_len - 8
+
+        # 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:
+                (off, scope_type, scope_len, bus, dev, fn) =\
+                    parse_dmar_devscope(f)
+                offset += off
+                npath = (scope_len - 6)/2
+                if scope_type == 1 and npath == 1:
+                    comments.append('PCI device: %02x:%02x.%x' %
+                                    (bus, dev, fn))
+                else:
+                    comments.append('DMAR parser could not decode device path')
+                f.seek(scope_len - off, os.SEEK_CUR)
+                offset += scope_len - off
+
+            reg = MemRegion(base, limit, 'ACPI DMAR RMRR', comments)
+            regions.append(reg)
+
+        f.seek(struct_len - offset, os.SEEK_CUR)
+
+    return ioapic_id, regions
+
+pcidevices = parse_pcidevices()
+
+product = [input_readline('/sys/class/dmi/id/sys_vendor',
+                          True, True).rstrip(),
+           input_readline('/sys/class/dmi/id/product_name',
+                          True, True).rstrip()
+           ]
+
+inmatemem = kmg_multiply_str(options.mem_inmates)
+hvmem = [0, kmg_multiply_str(options.mem_hv)]
+
+regions = parse_iomem()
+ourmem = parse_cmdline()
+total = hvmem[1] + inmatemem
+
+ioapic_id, rmrr_regs = parse_dmar()
+regions += rmrr_regs
+
+# kernel does not have memmap region, pick one
+if ourmem is None:
+    ourmem = alloc_mem(regions, total)
+elif (total > ourmem[1]):
+    raise RuntimeError('Your memmap reservation is too small you need >="' +
+                       hex(total) + '"')
+
+hvmem[0] = ourmem[0]
+
+creg = MemRegion.find_region(regions, 'ACPI Tables')
+if creg is None:
+    raise RuntimeError('could not find "ACPI Tables" memory')
+confmem = [creg.start, creg.size()]
+inmatereg = MemRegion(ourmem[0] + hvmem[1],
+                      ourmem[0] + hvmem[1] + inmatemem - 1,
+                      'JAILHOUSE Inmate Memory')
+regions.append(inmatereg)
+
+cpucount = count_cpus()
+
+f = open(options.file, 'w')
+
+if options.generate_collector:
+    filelist = ' '.join(inputs['files'].union(inputs['dirs']))
+
+    tmpl = Template(filename='jailhouse-config-collect.tmpl')
+    f.write(tmpl.render(filelist=filelist))
+else:
+    tmpl = Template(filename='root-cell-config.c.tmpl')
+    f.write(tmpl.render(regions=regions,
+                        ourmem=ourmem,
+                        argstr=' '.join(sys.argv),
+                        hvmem=hvmem,
+                        confmem=confmem,
+                        product=product,
+                        pcidevices=pcidevices,
+                        cpucount=cpucount,
+                        ioapic_id=ioapic_id))
index b8dd220b3963b9643013ecfe3cf70642e852782c..fbd3cd157fe56e1b024a1bf148c38f1939f161a8 100644 (file)
@@ -34,6 +34,9 @@ struct extension {
 static const struct extension extensions[] = {
        { "cell", "list", "" },
        { "cell", "stats", "{ ID | [--name] NAME }" },
+       { "config", "create", "[-h] [-g] [-r ROOT] "
+         "[--mem-inmates MEM_INMATES] [--mem-hv MEM_HV] FILE" },
+       { "config", "collect", "FILE.TAR" },
        { NULL }
 };
 
@@ -355,6 +358,9 @@ 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) {
+               call_extension_script(argv[1], argc, argv);
+               help(argv[0], 1);
        } else if (strcmp(argv[1], "--help") == 0) {
                help(argv[0], 0);
        } else
diff --git a/tools/root-cell-config.c.tmpl b/tools/root-cell-config.c.tmpl
new file mode 100644 (file)
index 0000000..5a8b9a4
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ *
+ * Configuration for ${product[0]} ${product[1]}
+ * created with '${argstr}'
+ *
+ * NOTE: This config expects the following to be appended to your kernel cmdline
+ *       "memmap=${hex(ourmem[1])}$${hex(ourmem[0])}"
+ */
+
+#include <linux/types.h>
+#include <jailhouse/cell-config.h>
+
+#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])
+
+struct {
+       struct jailhouse_system header;
+       __u64 cpus[1];
+       struct jailhouse_memory mem_regions[${len(regions)}];
+       struct jailhouse_irqchip irqchips[1];
+       __u8 pio_bitmap[0x2000];
+       struct jailhouse_pci_device pci_devices[${len(pcidevices)}];
+} __attribute__((packed)) config = {
+       .header = {
+               .hypervisor_memory = {
+                       .phys_start = ${hex(hvmem[0])},
+                       .size = ${hex(hvmem[1])},
+               },
+               .config_memory = {
+                       .phys_start = ${hex(confmem[0])},
+                       .size = ${hex(confmem[1])},
+               },
+               .root_cell = {
+                       .name = "${product[1]}",
+                       .cpu_set_size = sizeof(config.cpus),
+                       .num_memory_regions = ARRAY_SIZE(config.mem_regions),
+                       .num_irqchips = ARRAY_SIZE(config.irqchips),
+                       .pio_bitmap_size = ARRAY_SIZE(config.pio_bitmap),
+                       .num_pci_devices = ARRAY_SIZE(config.pci_devices),
+               },
+       },
+
+       .cpus = {
+               0b${'1'*cpucount},
+       },
+
+       .mem_regions = {
+               % for r in regions:
+               /* ${str(r)} */
+               % for c in r.comments:
+               /* ${c} */
+               % endfor
+               {
+                       .phys_start = ${hex(r.start)},
+                       .virt_start = ${hex(r.start)},
+                       .size = ${hex(r.size())},
+                       .flags = ${r.flagstr('\t\t')},
+               },
+               % endfor
+       },
+
+       .irqchips = {
+               /* IOAPIC */ {
+                       .address = 0xfec00000,
+                       .id = ${hex(ioapic_id)},
+                       .pin_bitmap = 0xffffff,
+               },
+       },
+
+       .pio_bitmap = {
+               [     0/8 ...   0x1f/8] = -1,
+               [  0x20/8 ...   0x27/8] = 0xfc, /* HACK: PIC */
+               [  0x28/8 ...   0x3f/8] = -1,
+               [  0x40/8 ...   0x47/8] = 0xf0, /* PIT */
+               [  0x48/8 ...   0x5f/8] = -1,
+               [  0x60/8 ...   0x67/8] = 0, /* HACK: 8042, NMI status/control */
+               [  0x68/8 ...   0x6f/8] = -1,
+               [  0x70/8 ...   0x77/8] = 0xfc, /* RTC */
+               [  0x78/8 ...  0x3af/8] = -1,
+               [ 0x3b0/8 ...  0x3df/8] = 0x00, /* VGA */
+               [ 0x3e0/8 ...  0x3ff/8] = -1,
+               [ 0x400/8 ...  0x47f/8] = 0, /* HACK: ACPI */
+               [ 0x480/8 ...  0xcff/8] = -1,
+               [ 0xd00/8 ... 0xffff/8] = 0, /* HACK: PCI bus */
+       },
+
+       .pci_devices = {
+               % for d in pcidevices:
+               /* ${str(d)} */
+               {
+                       .type = ${d.type},
+                       .domain = ${hex(d.domain)},
+                       .bus = ${hex(d.bus)},
+                       .devfn = ${hex(d.devfn())},
+               },
+               % endfor
+       },
+};