From e1c6116a0f0ca818251a3867b081c1d57c1a0b75 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 5 Oct 2018 16:57:57 -0300 Subject: [PATCH] Implement Linux kernel boot protocol, accept bzimage/initrd as boot configuration --- Makefile | 2 +- loader.js | 2 +- src/browser/main.js | 6 ++ src/browser/starter.js | 11 +- src/cpu.js | 16 ++- src/kernel.js | 234 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 src/kernel.js diff --git a/Makefile b/Makefile index ef117ad6..c680dfd4 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ CORE_FILES=const.js config.js io.js main.js lib.js coverage.js ide.js pci.js flo memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js hpet.js acpi.js apic.js ioapic.js \ state.js ne2k.js virtio.js bus.js log.js \ cpu.js debug.js \ - elf.js + elf.js kernel.js LIB_FILES=9p.js filesystem.js jor1k.js marshall.js utf8.js BROWSER_FILES=screen.js \ keyboard.js mouse.js serial.js \ diff --git a/loader.js b/loader.js index 9b15d6a8..256f0b51 100644 --- a/loader.js +++ b/loader.js @@ -8,7 +8,7 @@ "const.js config.js log.js lib.js coverage.js cpu.js debug.js " + "io.js main.js ide.js pci.js floppy.js " + "memory.js dma.js pit.js vga.js ps2.js pic.js rtc.js uart.js acpi.js apic.js ioapic.js hpet.js " + - "ne2k.js state.js virtio.js bus.js elf.js"; + "ne2k.js state.js virtio.js bus.js elf.js kernel.js"; var BROWSER_FILES = "main.js screen.js keyboard.js mouse.js serial.js lib.js network.js starter.js worker_bus.js print_stats.js filestorage.js"; var LIB_FILES = ""; diff --git a/src/browser/main.js b/src/browser/main.js index bc299e60..678f3d1f 100644 --- a/src/browser/main.js +++ b/src/browser/main.js @@ -545,6 +545,9 @@ settings.cdrom = infos.cdrom; settings.hda = infos.hda; settings.multiboot = infos.multiboot; + settings.bzimage = infos.bzimage; + settings.initrd = infos.initrd; + settings.cmdline = infos.cmdline; settings.memory_size = infos.memory_size; settings.vga_memory_size = infos.vga_memory_size; @@ -767,6 +770,9 @@ "cdrom": settings.cdrom, "multiboot": settings.multiboot, + "bzimage": settings.bzimage, + "initrd": settings.initrd, + "cmdline": settings.cmdline, "initial_state": settings.initial_state, "filesystem": settings.filesystem || {}, diff --git a/src/browser/starter.js b/src/browser/starter.js index 4f1c3642..83dc6d80 100644 --- a/src/browser/starter.js +++ b/src/browser/starter.js @@ -245,6 +245,7 @@ V86Starter.prototype.continue_init = function(emulator, options) settings.boot_order = options["boot_order"] || 0x213; settings.fda = undefined; settings.fdb = undefined; + settings.cmdline = options["cmdline"]; if(options["network_relay_url"]) { @@ -299,6 +300,12 @@ V86Starter.prototype.continue_init = function(emulator, options) case "multiboot": settings.multiboot = this.disk_images["multiboot"] = buffer.buffer; break; + case "bzimage": + settings.bzimage = this.disk_images["bzimage"] = buffer.buffer; + break; + case "initrd": + settings.initrd = this.disk_images["initrd"] = buffer.buffer; + break; case "bios": settings.bios = buffer.buffer; @@ -345,7 +352,8 @@ V86Starter.prototype.continue_init = function(emulator, options) }; if(name === "bios" || name === "vga_bios" || - name === "initial_state" || name === "multiboot") + name === "initial_state" || name === "multiboot" || + name === "bzimage" || name === "initrd") { // Ignore async for these because they must be availabe before boot. // This should make result.buffer available after the object is loaded @@ -425,6 +433,7 @@ V86Starter.prototype.continue_init = function(emulator, options) "bios", "vga_bios", "cdrom", "hda", "hdb", "fda", "fdb", "initial_state", "multiboot", + "bzimage", "initrd", ]; for(var i = 0; i < image_names.length; i++) diff --git a/src/cpu.js b/src/cpu.js index 13f54fa9..980875bd 100644 --- a/src/cpu.js +++ b/src/cpu.js @@ -533,7 +533,7 @@ CPU.prototype.set_state = function(state) this.devices.pic = state[60]; this.a20_enabled[0] = state[61]; - this.fw_value[0] = state; + this.fw_value = state[62]; this.devices.ioapic = state[63]; @@ -736,6 +736,17 @@ CPU.prototype.init = function(settings, device_bus) this.load_bios(); + if(settings.bzimage) + { + dbg_assert(settings.cmdline); + const { option_rom } = load_kernel(this.mem8, settings.bzimage, settings.initrd, settings.cmdline); + + if(option_rom) + { + this.option_roms.push(option_rom); + } + } + var a20_byte = 0; io.register_read(0xB3, this, function() @@ -770,6 +781,9 @@ CPU.prototype.init = function(settings, device_bus) }); io.register_write(0x510, this, undefined, function(value) { + // https://wiki.osdev.org/QEMU_fw_cfg + // https://github.com/qemu/qemu/blob/master/docs/specs/fw_cfg.txt + dbg_log("bios config port, index=" + h(value)); function i32(x) diff --git a/src/kernel.js b/src/kernel.js new file mode 100644 index 00000000..10a134b0 --- /dev/null +++ b/src/kernel.js @@ -0,0 +1,234 @@ +"use strict"; + +// https://www.kernel.org/doc/Documentation/x86/boot.txt + +const LINUX_BOOT_HDR_SETUP_SECTS = 0x1F1; +const LINUX_BOOT_HDR_SYSSIZE = 0x1F4; +const LINUX_BOOT_HDR_VIDMODE = 0x1FA; +const LINUX_BOOT_HDR_BOOT_FLAG = 0x1FE; +const LINUX_BOOT_HDR_HEADER = 0x202; +const LINUX_BOOT_HDR_VERSION = 0x206; +const LINUX_BOOT_HDR_TYPE_OF_LOADER = 0x210; +const LINUX_BOOT_HDR_LOADFLAGS = 0x211; +const LINUX_BOOT_HDR_CODE32_START = 0x214; +const LINUX_BOOT_HDR_RAMDISK_IMAGE = 0x218; +const LINUX_BOOT_HDR_RAMDISK_SIZE = 0x21C; +const LINUX_BOOT_HDR_HEAP_END_PTR = 0x224; +const LINUX_BOOT_HDR_CMD_LINE_PTR = 0x228; +const LINUX_BOOT_HDR_INITRD_ADDR_MAX = 0x22C; +const LINUX_BOOT_HDR_KERNEL_ALIGNMENT = 0x230; +const LINUX_BOOT_HDR_RELOCATABLE_KERNEL = 0x234; +const LINUX_BOOT_HDR_MIN_ALIGNMENT = 0x235; +const LINUX_BOOT_HDR_XLOADFLAGS = 0x236; +const LINUX_BOOT_HDR_CMDLINE_SIZE = 0x238; +const LINUX_BOOT_HDR_PAYLOAD_OFFSET = 0x248; +const LINUX_BOOT_HDR_PAYLOAD_LENGTH = 0x24C; +const LINUX_BOOT_HDR_PREF_ADDRESS = 0x258; +const LINUX_BOOT_HDR_INIT_SIZE = 0x260; + +const LINUX_BOOT_HDR_CHECKSUM1 = 0xAA55; +const LINUX_BOOT_HDR_CHECKSUM2 = 0x53726448; + +const LINUX_BOOT_HDR_TYPE_OF_LOADER_NOT_ASSIGNED = 0xFF; + +const LINUX_BOOT_HDR_LOADFLAGS_LOADED_HIGH = 1 << 0; +const LINUX_BOOT_HDR_LOADFLAGS_QUIET_FLAG = 1 << 5; +const LINUX_BOOT_HDR_LOADFLAGS_KEEP_SEGMENTS = 1 << 6; +const LINUX_BOOT_HDR_LOADFLAGS_CAN_USE_HEAPS = 1 << 7; + + +function load_kernel(mem8, bzimage, initrd, cmdline) +{ + dbg_log("Trying to load kernel of size " + bzimage.length); + + const KERNEL_HIGH_ADDRESS = 0x100000; + + // Puts the initrd at the 32 mb boundary. This means the mininum memory size + // is 32 mb plus the size of the initrd + const INITRD_ADDRESS = 32 << 20; + + const quiet = false; + + const bzimage8 = new Uint8Array(bzimage); + const bzimage16 = new Uint16Array(bzimage); + const bzimage32 = new Uint32Array(bzimage); + + const setup_sects = bzimage8[LINUX_BOOT_HDR_SETUP_SECTS] || 4; + const syssize = bzimage32[LINUX_BOOT_HDR_SYSSIZE >> 2] << 4; + + const vidmode = bzimage16[LINUX_BOOT_HDR_VIDMODE >> 1]; + + const checksum1 = bzimage16[LINUX_BOOT_HDR_BOOT_FLAG >> 1]; + if(checksum1 !== LINUX_BOOT_HDR_CHECKSUM1) + { + dbg_log("Bad checksum1: " + h(checksum1)); + return; + } + + // Not aligned, so split into two 16-bit reads + const checksum2 = + bzimage16[LINUX_BOOT_HDR_HEADER >> 1] | + bzimage16[LINUX_BOOT_HDR_HEADER + 2 >> 1] << 16; + if(checksum2 !== LINUX_BOOT_HDR_CHECKSUM2) + { + dbg_log("Bad checksum2: " + h(checksum2)); + return; + } + + const protocol = bzimage16[LINUX_BOOT_HDR_VERSION >> 1]; + dbg_assert(protocol >= 0x202); // older not supported by us + + const flags = bzimage8[LINUX_BOOT_HDR_LOADFLAGS]; + dbg_assert(flags & LINUX_BOOT_HDR_LOADFLAGS_LOADED_HIGH); // low kernels not supported by us + + // we don't relocate the kernel, so we don't care much about most of these + + const flags2 = bzimage16[LINUX_BOOT_HDR_XLOADFLAGS >> 1]; + const initrd_addr_max = bzimage32[LINUX_BOOT_HDR_INITRD_ADDR_MAX >> 2]; + const kernel_alignment = bzimage32[LINUX_BOOT_HDR_KERNEL_ALIGNMENT >> 2]; + const relocatable_kernel = bzimage8[LINUX_BOOT_HDR_RELOCATABLE_KERNEL]; + const min_alignment = bzimage8[LINUX_BOOT_HDR_MIN_ALIGNMENT]; + const cmdline_size = bzimage32[LINUX_BOOT_HDR_CMDLINE_SIZE >> 2]; + const payload_offset = bzimage32[LINUX_BOOT_HDR_PAYLOAD_OFFSET >> 2]; + const payload_length = bzimage32[LINUX_BOOT_HDR_PAYLOAD_LENGTH >> 2]; + const pref_address = bzimage32[LINUX_BOOT_HDR_PREF_ADDRESS >> 2]; + const pref_address_high = bzimage32[LINUX_BOOT_HDR_PREF_ADDRESS + 4 >> 2]; + const init_size = bzimage32[LINUX_BOOT_HDR_INIT_SIZE >> 2]; + + dbg_log("kernel boot protocol version: " + h(protocol)); + dbg_log("flags=" + h(flags) + " xflags=" + h(flags2)); + dbg_log("code32_start=" + h(bzimage32[LINUX_BOOT_HDR_CODE32_START >> 2])); + dbg_log("initrd_addr_max=" + h(initrd_addr_max)); + dbg_log("kernel_alignment=" + h(kernel_alignment)); + dbg_log("relocatable=" + relocatable_kernel); + dbg_log("min_alignment=" + h(min_alignment)); + dbg_log("cmdline max=" + h(cmdline_size)); + dbg_log("payload offset=" + h(payload_offset) + " size=" + h(payload_length)); + dbg_log("pref_address=" + h(pref_address_high) + ":" + h(pref_address)); + dbg_log("init_size=" + h(init_size)); + + const real_mode_segment = 0x8000; + const base_ptr = real_mode_segment << 4; + + const heap_end = 0xE000; + const heap_end_ptr = heap_end - 0x200; + + // fill in the kernel boot header with infos the kernel needs to know + + bzimage8[LINUX_BOOT_HDR_TYPE_OF_LOADER] = LINUX_BOOT_HDR_TYPE_OF_LOADER_NOT_ASSIGNED; + + const new_flags = + (quiet ? flags | LINUX_BOOT_HDR_LOADFLAGS_QUIET_FLAG : flags & ~LINUX_BOOT_HDR_LOADFLAGS_QUIET_FLAG) + & ~LINUX_BOOT_HDR_LOADFLAGS_KEEP_SEGMENTS + | LINUX_BOOT_HDR_LOADFLAGS_CAN_USE_HEAPS; + bzimage8[LINUX_BOOT_HDR_LOADFLAGS] = new_flags; + + bzimage16[LINUX_BOOT_HDR_HEAP_END_PTR >> 1] = heap_end_ptr; + + // should parse the vga=... paramter from cmdline here, but we don't really care + bzimage16[LINUX_BOOT_HDR_VIDMODE >> 1] = 0xFFFF; // normal + + dbg_log("heap_end_ptr=" + h(heap_end_ptr)); + + cmdline += "\x00"; + dbg_assert(cmdline.length < cmdline_size); + + const cmd_line_ptr = base_ptr + heap_end; + dbg_log("cmd_line_ptr=" + h(cmd_line_ptr)); + + bzimage32[LINUX_BOOT_HDR_CMD_LINE_PTR >> 2] = cmd_line_ptr; + for(let i = 0; i < cmdline.length; i++) + { + mem8[cmd_line_ptr + i] = cmdline.charCodeAt(i); + } + + const prot_mode_kernel_start = (setup_sects + 1) * 512; + dbg_log("prot_mode_kernel_start=" + h(prot_mode_kernel_start)); + + const real_mode_kernel = new Uint8Array(bzimage, 0, prot_mode_kernel_start); + const protected_mode_kernel = new Uint8Array(bzimage, prot_mode_kernel_start); + + let ramdisk_address = 0; + let ramdisk_size = 0; + + if(initrd) + { + ramdisk_address = INITRD_ADDRESS; + ramdisk_size = initrd.byteLength; + + dbg_assert(KERNEL_HIGH_ADDRESS + protected_mode_kernel.length < ramdisk_address); + + mem8.set(new Uint8Array(initrd), ramdisk_address); + } + + bzimage32[LINUX_BOOT_HDR_RAMDISK_IMAGE >> 2] = ramdisk_address; + bzimage32[LINUX_BOOT_HDR_RAMDISK_SIZE >> 2] = ramdisk_size; + + dbg_assert(base_ptr + real_mode_kernel.length < 0xA0000); + + mem8.set(real_mode_kernel, base_ptr); + mem8.set(protected_mode_kernel, KERNEL_HIGH_ADDRESS); + + return { + option_rom: + { + name: "genroms/kernel.bin", + data: make_linux_boot_rom(real_mode_segment, heap_end), + } + }; +} + +function make_linux_boot_rom(real_mode_segment, heap_end) +{ + // This rom will be executed by seabios after its initialisation + // It sets up segment registers, the stack and calls the kernel real mode entry point + + const SIZE = 0x200; + + const data8 = new Uint8Array(0x100); + const data16 = new Uint16Array(data8.buffer); + + data16[0] = 0xAA55; + data8[2] = SIZE / 0x200; + + let i = 3; + + data8[i++] = 0xFA; // cli + data8[i++] = 0xB8; // mov ax, real_mode_segment + data8[i++] = real_mode_segment >> 0; + data8[i++] = real_mode_segment >> 8; + data8[i++] = 0x8E; // mov es, ax + data8[i++] = 0xC0; + data8[i++] = 0x8E; // mov ds, ax + data8[i++] = 0xD8; + data8[i++] = 0x8E; // mov fs, ax + data8[i++] = 0xE0; + data8[i++] = 0x8E; // mov gs, ax + data8[i++] = 0xE8; + data8[i++] = 0x8E; // mov ss, ax + data8[i++] = 0xD0; + data8[i++] = 0xBC; // mov sp, heap_end + data8[i++] = heap_end >> 0; + data8[i++] = heap_end >> 8; + data8[i++] = 0xEA; // jmp (real_mode_segment+0x20):0x0 + data8[i++] = 0x00; + data8[i++] = 0x00; + data8[i++] = real_mode_segment + 0x20 >> 0; + data8[i++] = real_mode_segment + 0x20 >> 8; + + dbg_assert(i < SIZE); + + const checksum_index = i; + data8[checksum_index] = 0; + + let checksum = 0; + + for(let i = 0; i < data8.length; i++) + { + checksum += data8[i]; + } + + data8[checksum_index] = -checksum; + + return data8; +}