From 82ee7924aba5b30e7998a7e1414e258ab1cdbd31 Mon Sep 17 00:00:00 2001 From: Henning Schild Date: Mon, 7 Jul 2014 12:46:00 +0200 Subject: [PATCH] tools: Add configuration generator 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 Signed-off-by: Jan Kiszka --- .gitignore | 1 + tools/Makefile | 28 +- tools/jailhouse-config-collect.tmpl | 59 +++++ tools/jailhouse-config-create | 382 ++++++++++++++++++++++++++++ tools/jailhouse.c | 6 + tools/root-cell-config.c.tmpl | 103 ++++++++ 6 files changed, 578 insertions(+), 1 deletion(-) create mode 100644 tools/jailhouse-config-collect.tmpl create mode 100755 tools/jailhouse-config-create create mode 100644 tools/root-cell-config.c.tmpl diff --git a/.gitignore b/.gitignore index f5fcb2cdd..5be98d5a9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ jailhouse.ko hypervisor/include/jailhouse/config.h hypervisor/hypervisor.lds tools/jailhouse +tools/jailhouse-config-collect configs/*.cell diff --git a/tools/Makefile b/tools/Makefile index bfddc3b3c..45554e4c3 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -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 index 000000000..a298fb9a5 --- /dev/null +++ b/tools/jailhouse-config-collect.tmpl @@ -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 index 000000000..ce196079d --- /dev/null +++ b/tools/jailhouse-config-create @@ -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(' 0: + offset = 0 + (struct_type, struct_len) = struct.unpack(' 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)) diff --git a/tools/jailhouse.c b/tools/jailhouse.c index b8dd220b3..fbd3cd157 100644 --- a/tools/jailhouse.c +++ b/tools/jailhouse.c @@ -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 index 000000000..5a8b9a4fc --- /dev/null +++ b/tools/root-cell-config.c.tmpl @@ -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 +#include + +#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 + }, +};