From f7bd8491773f33409ea0bbcd2dc0bd4051e50ac6 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Sun, 24 May 2015 10:10:22 +0200 Subject: [PATCH] tools, inmates: Add "cell linux" subcommand to jailhouse tool This adds support for loading and booting paravirtualized x86 Linux kernels in non-root cells. The jailhouse tool is extended for this purpose with a new subcommand "cell linux" that accepts the cell configuration, the kernel image and an optional initrd as input. Also a kernel command line can be specified. The script then creates the cell, unless it already exists, load kernel, initrd, a special boot loader and the required parameters for that loader into the cell RAM. Finally, it starts the cell. The interface between python helper and the boot loader inmate is based on the kernels boot_params structure with a custom setup_data extension. The former is initialized by the python help, specifically to inform Linux about the location of its initrd and the command line. It also contains an e820 list to report the memory layout. The setup_data is filled by the boot loader with information about the PM timer address and the available CPUs as well as their physical APIC IDs. For that purpose, the Linux cell requires a communication region. Although the loader script is currently x86-only, extension to ARM is surely feasible as well. Signed-off-by: Jan Kiszka --- Makefile | 2 +- inmates/tools/x86/Makefile | 4 +- inmates/tools/x86/linux-loader.c | 55 ++++++ tools/Makefile | 2 + tools/jailhouse-cell-linux | 286 +++++++++++++++++++++++++++++++ tools/jailhouse.c | 3 + 6 files changed, 350 insertions(+), 2 deletions(-) create mode 100644 inmates/tools/x86/linux-loader.c create mode 100755 tools/jailhouse-cell-linux diff --git a/Makefile b/Makefile index 799e1e7..6d597af 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ modules_install: modules firmware_install: $(DESTDIR)$(firmwaredir) modules $(INSTALL_DATA) hypervisor/jailhouse*.bin $< -ifeq ($(ARCH),) +ifeq ($(ARCH),x86) TOOL_INMATES_INSTALL := tool_inmates_install tool_inmates_install: $(DESTDIR)$(libexecdir)/jailhouse $(INSTALL_DATA) inmates/tools/$(ARCH)/*.bin $< diff --git a/inmates/tools/x86/Makefile b/inmates/tools/x86/Makefile index a81719a..4a72277 100644 --- a/inmates/tools/x86/Makefile +++ b/inmates/tools/x86/Makefile @@ -12,6 +12,8 @@ include $(INMATES_LIB)/Makefile.lib -INMATES := +INMATES := linux-loader.bin + +linux-loader-y := linux-loader.o $(eval $(call DECLARE_TARGETS,$(INMATES))) diff --git a/inmates/tools/x86/linux-loader.c b/inmates/tools/x86/linux-loader.c new file mode 100644 index 0000000..d4a9de7 --- /dev/null +++ b/inmates/tools/x86/linux-loader.c @@ -0,0 +1,55 @@ +/* + * Jailhouse, a Linux-based partitioning hypervisor + * + * Copyright (c) Siemens AG, 2015 + * + * Authors: + * Jan Kiszka + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ + +#include + +#define ZERO_PAGE_ADDR 0xf5000UL + +struct boot_params { + u8 padding1[0x230]; + u32 kernel_alignment; + u8 padding2[0x250 - 0x230 - 4]; + u64 setup_data; + u8 padding3[8]; + u32 init_size; +}; + +struct setup_data { + u64 next; + u32 type; + u32 length; + u16 pm_timer_address; + u16 num_cpus; + u8 cpu_ids[SMP_MAX_CPUS]; +}; + +void inmate_main(void) +{ + struct boot_params *boot_params = (struct boot_params *)ZERO_PAGE_ADDR; + void (*entry)(int, struct boot_params *); + struct setup_data *setup_data; + void *kernel; + + kernel = (void *)(unsigned long)boot_params->kernel_alignment; + + map_range(kernel, boot_params->init_size, MAP_CACHED); + + setup_data = (struct setup_data *)boot_params->setup_data; + setup_data->pm_timer_address = comm_region->pm_timer_address; + setup_data->num_cpus = comm_region->num_cpus; + + smp_wait_for_all_cpus(); + memcpy(setup_data->cpu_ids, smp_cpu_ids, SMP_MAX_CPUS); + + entry = kernel + 0x200; + entry(0, boot_params); +} diff --git a/tools/Makefile b/tools/Makefile index 6898e72..55d0368 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -20,6 +20,7 @@ TARGETS := jailhouse INST_TARGETS := $(TARGETS) HELPERS := \ + jailhouse-cell-linux \ jailhouse-cell-list \ jailhouse-cell-stats \ jailhouse-config-create @@ -58,6 +59,7 @@ install-bin: $(INST_TARGETS) $(DESTDIR)$(sbindir) install-libexec: $(HELPERS) $(DESTDIR)$(libexecdir)/jailhouse $(INSTALL_PROGRAM) $^ + $(Q)$(call patch_dirvar,libexecdir,$(lastword $^)/jailhouse-cell-linux) $(Q)$(call patch_dirvar,datadir,$(lastword $^)/jailhouse-config-create) install-data: $(TEMPLATES) $(DESTDIR)$(datadir)/jailhouse diff --git a/tools/jailhouse-cell-linux b/tools/jailhouse-cell-linux new file mode 100755 index 0000000..245ba13 --- /dev/null +++ b/tools/jailhouse-cell-linux @@ -0,0 +1,286 @@ +#!/usr/bin/python + +# Jailhouse, a Linux-based partitioning hypervisor +# +# Copyright (c) Siemens AG, 2015 +# +# Authors: +# Jan Kiszka +# +# This work is licensed under the terms of the GNU GPL, version 2. See +# the COPYING file in the top-level directory. + +from __future__ import print_function +import argparse +import ctypes +import errno +import fcntl +import os +import struct +import sys + +PARAMS_BASE = 0xf5000 + +libexecdir = None + + +class MemoryRegion: + JAILHOUSE_MEM_READ = 0x0001 + JAILHOUSE_MEM_WRITE = 0x0002 + JAILHOUSE_MEM_EXECUTE = 0x0004 + JAILHOUSE_MEM_DMA = 0x0008 + JAILHOUSE_MEM_IO = 0x0010 + JAILHOUSE_MEM_COMM_REGION = 0x0020 + JAILHOUSE_MEM_ROOTSHARED = 0x0080 + + E820_RAM = 1 + E820_RESERVED = 2 + + _REGION_FORMAT = 'QQQQ' + SIZE = struct.calcsize(_REGION_FORMAT) + + def __init__(self, region_struct): + (self.phys_start, + self.virt_start, + self.size, + self.flags) = \ + struct.unpack_from(MemoryRegion._REGION_FORMAT, region_struct) + + def is_ram(self): + return ((self.flags & (MemoryRegion.JAILHOUSE_MEM_READ | + MemoryRegion.JAILHOUSE_MEM_WRITE | + MemoryRegion.JAILHOUSE_MEM_EXECUTE | + MemoryRegion.JAILHOUSE_MEM_DMA | + MemoryRegion.JAILHOUSE_MEM_IO | + MemoryRegion.JAILHOUSE_MEM_COMM_REGION | + MemoryRegion.JAILHOUSE_MEM_ROOTSHARED)) == + (MemoryRegion.JAILHOUSE_MEM_READ | + MemoryRegion.JAILHOUSE_MEM_WRITE | + MemoryRegion.JAILHOUSE_MEM_EXECUTE | + MemoryRegion.JAILHOUSE_MEM_DMA)) + + def is_comm_region(self): + return (self.flags & MemoryRegion.JAILHOUSE_MEM_COMM_REGION) != 0 + + def as_e820(self): + return struct.pack('QQI', self.virt_start, self.size, + MemoryRegion.E820_RAM if self.is_ram() else + MemoryRegion.E820_RESERVED) + + +class Config: + _HEADER_FORMAT = '32sIIIIIII' + + def __init__(self, config_file): + self.data = config_file.read() + + (name, + self.flags, + self.cpu_set_size, + self.num_memory_regions, + self.num_irqchips, + self.pio_bitmap_size, + self.num_pci_devices, + self.num_pci_caps) = \ + struct.unpack_from(Config._HEADER_FORMAT, self.data) + self.name = str(name.decode()) + + memregion_offs = struct.calcsize(Config._HEADER_FORMAT) + \ + self.cpu_set_size + self.memory_regions = [] + for n in range(self.num_memory_regions): + self.memory_regions.append( + MemoryRegion(self.data[memregion_offs:])) + memregion_offs += MemoryRegion.SIZE + + +class SetupHeader: + _HEADER_FORMAT = 'xB2xI8xH14xB7xII8xI4xI28xQ' + + def __init__(self, kernel): + kernel.seek(0x1f0) + parse_size = struct.calcsize(SetupHeader._HEADER_FORMAT) + (self.setup_sects, + self.syssize, + self.jump, + self.type_of_loader, + self.ramdisk_image, + self.ramdisk_size, + self.cmd_line_ptr, + self.kernel_alignment, + self.setup_data) = \ + struct.unpack(SetupHeader._HEADER_FORMAT, kernel.read(parse_size)) + + self.size = 0x202 + (self.jump >> 8) - 0x1f0 + kernel.seek(0x1f0) + self.data = bytearray(kernel.read(self.size)) + + def get_data(self): + struct.pack_into(SetupHeader._HEADER_FORMAT, self.data, 0, + self.setup_sects, self.syssize, self.jump, + self.type_of_loader, self.ramdisk_image, + self.ramdisk_size, self.cmd_line_ptr, + self.kernel_alignment, self.setup_data) + return self.data + + +class ZeroPage: + def __init__(self, kernel, initrd, config): + self.setup_header = SetupHeader(kernel) + + prot_image_offs = (self.setup_header.setup_sects + 1) * 512 + prot_image_size = self.setup_header.syssize * 16 + + self.kernel_load_addr = self.setup_header.kernel_alignment - \ + prot_image_offs + + self.setup_header.type_of_loader = 0xff + + if initrd: + kernel_size = os.fstat(kernel.fileno()).st_size + self.setup_header.ramdisk_size = os.fstat(initrd.fileno()).st_size + self.setup_header.ramdisk_image = \ + (self.kernel_load_addr - self.setup_header.ramdisk_size) & \ + ~0xfff + else: + self.setup_header.ramdisk_image = 0 + self.setup_header.ramdisk_size = 0 + + self.e820_entries = [] + for region in config.memory_regions: + if region.is_ram() or region.is_comm_region(): + if len(self.e820_entries) >= 128: + print("Too many memory regions", file=sys.stderr) + exit(1) + self.e820_entries.append(region) + + def get_data(self): + data = bytearray(0x1e8) + \ + struct.pack('B', len(self.e820_entries)) + \ + bytearray(0x1f0 - 0x1e9) + self.setup_header.get_data() + \ + bytearray(0x2d0 - 0x1f0 - self.setup_header.size) + for region in self.e820_entries: + data += region.as_e820() + return data + bytearray(0x1000 - len(data)) + + +class JailhouseCell: + JAILHOUSE_CELL_CREATE = 0x40100002 + JAILHOUSE_CELL_LOAD = 0x40300003 + JAILHOUSE_CELL_START = 0x40280004 + + JAILHOUSE_CELL_ID_UNUSED = -1 + + def __init__(self, config): + self.name = config.name + + self.dev = open('/dev/jailhouse') + + cbuf = ctypes.c_buffer(config.data) + create = struct.pack('QI4x', ctypes.addressof(cbuf), len(config.data)) + try: + fcntl.ioctl(self.dev, JailhouseCell.JAILHOUSE_CELL_CREATE, create) + except IOError as e: + if e.errno != errno.EEXIST: + raise e + + def load(self, image, address): + cbuf = ctypes.create_string_buffer(bytes(image)) + + load = struct.pack('i4x32sI4xQQQ8x', + JailhouseCell.JAILHOUSE_CELL_ID_UNUSED, self.name, + 1, ctypes.addressof(cbuf), len(image), address) + fcntl.ioctl(self.dev, self.JAILHOUSE_CELL_LOAD, load) + + def start(self): + start = struct.pack('i4x32s', JailhouseCell.JAILHOUSE_CELL_ID_UNUSED, + self.name) + fcntl.ioctl(self.dev, JailhouseCell.JAILHOUSE_CELL_START, start) + + +def gen_setup_data(): + MAX_CPUS = 255 + return struct.pack('8x4sI4x', b'JLHS', 4 + MAX_CPUS) + bytearray(MAX_CPUS) + + +# pretend to be part of the jailhouse tool +sys.argv[0] = sys.argv[0].replace('-', ' ') + +parser = argparse.ArgumentParser(description='Boot Linux in a non-root cell.') +parser.add_argument('config', metavar='CELLCONFIG', + type=argparse.FileType('rb'), + help='cell configuration file') +parser.add_argument('kernel', metavar='KERNEL', type=argparse.FileType('rb'), + help='image of the kernel to be booted') +parser.add_argument('--initrd', '-i', metavar='FILE', + type=argparse.FileType('rb'), + help='initrd/initramfs for the kernel') +parser.add_argument('--cmdline', '-c', metavar='"STRING"', + help='kernel command line') +parser.add_argument('--write-params', '-w', metavar='FILE', + type=argparse.FileType('wb'), + help='only parse cell configuration, write out ' + 'parameters into the specified file and print ' + 'required jailhouse cell commands to boot Linux ' + 'to the console') + +try: + args = parser.parse_args() +except IOError as e: + print(e.strerror, file=sys.stderr) + exit(1) + +config = Config(args.config) + +zero_page = ZeroPage(args.kernel, args.initrd, config) + +setup_data = gen_setup_data() + +zero_page.setup_header.setup_data = PARAMS_BASE + 0x1000 +zero_page.setup_header.cmd_line_ptr = \ + zero_page.setup_header.setup_data + len(setup_data) + +params = zero_page.get_data() + setup_data + \ + (args.cmdline.encode() if args.cmdline else b'') + b'\0' + +if args.write_params: + args.write_params.write(params) + args.write_params.close() + + print("\ +Boot parameters written. Start Linux with the following commands (adjusting \ +paths as needed):\n\ +\n\ +jailhouse cell create %s\n\ +jailhouse cell load %s linux-loader.bin -a 0xf0000 %s -a 0x%x " % + (args.config.name, config.name, args.kernel.name, + zero_page.kernel_load_addr), + end="") + if args.initrd: + print("%s -a 0x%x " % + (args.initrd.name, zero_page.setup_header.ramdisk_image), + end="") + print("%s -a 0x%x" % (args.write_params.name, PARAMS_BASE)) + print("jailhouse cell start %s" % config.name) +else: + arch_str = os.uname()[4] + if arch_str in ('i686', 'x86_64'): + srcarch = 'x86' + else: + print("Unsupported architecture", file=sys.stderr) + exit(1) + + if libexecdir: + linux_loader = libexecdir + '/jailhouse/linux-loader.bin' + else: + linux_loader = os.path.abspath(os.path.dirname(sys.argv[0])) + \ + '/../inmates/tools/' + srcarch + '/linux-loader.bin' + + cell = JailhouseCell(config) + cell.load(open(linux_loader, mode='rb').read(), 0xf0000) + args.kernel.seek(0) + cell.load(args.kernel.read(), zero_page.kernel_load_addr) + if args.initrd: + cell.load(args.initrd.read(), zero_page.setup_header.ramdisk_image) + cell.load(params, PARAMS_BASE) + cell.start() diff --git a/tools/jailhouse.c b/tools/jailhouse.c index 206c181..6a36892 100644 --- a/tools/jailhouse.c +++ b/tools/jailhouse.c @@ -34,6 +34,9 @@ struct extension { }; static const struct extension extensions[] = { + { "cell", "linux", "CELLCONFIG KERNEL [-i | --initrd FILE]\n" + " [-c | --cmdline \"STRING\"] " + "[-w | --write-params FILE]" }, { "cell", "list", "" }, { "cell", "stats", "{ ID | [--name] NAME }" }, { "config", "create", "[-h] [-g] [-r ROOT] " -- 2.39.2