#!/usr/bin/env 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 = '8x32sIIIIIII' 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.encode('utf-8') 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()