Implement Linux kernel boot protocol, accept bzimage/initrd as boot configuration
This commit is contained in:
parent
04d282c298
commit
e1c6116a0f
2
Makefile
2
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 \
|
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 \
|
state.js ne2k.js virtio.js bus.js log.js \
|
||||||
cpu.js debug.js \
|
cpu.js debug.js \
|
||||||
elf.js
|
elf.js kernel.js
|
||||||
LIB_FILES=9p.js filesystem.js jor1k.js marshall.js utf8.js
|
LIB_FILES=9p.js filesystem.js jor1k.js marshall.js utf8.js
|
||||||
BROWSER_FILES=screen.js \
|
BROWSER_FILES=screen.js \
|
||||||
keyboard.js mouse.js serial.js \
|
keyboard.js mouse.js serial.js \
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"const.js config.js log.js lib.js coverage.js cpu.js debug.js " +
|
"const.js config.js log.js lib.js coverage.js cpu.js debug.js " +
|
||||||
"io.js main.js ide.js pci.js floppy.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 " +
|
"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 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 = "";
|
var LIB_FILES = "";
|
||||||
|
|
|
@ -545,6 +545,9 @@
|
||||||
settings.cdrom = infos.cdrom;
|
settings.cdrom = infos.cdrom;
|
||||||
settings.hda = infos.hda;
|
settings.hda = infos.hda;
|
||||||
settings.multiboot = infos.multiboot;
|
settings.multiboot = infos.multiboot;
|
||||||
|
settings.bzimage = infos.bzimage;
|
||||||
|
settings.initrd = infos.initrd;
|
||||||
|
settings.cmdline = infos.cmdline;
|
||||||
|
|
||||||
settings.memory_size = infos.memory_size;
|
settings.memory_size = infos.memory_size;
|
||||||
settings.vga_memory_size = infos.vga_memory_size;
|
settings.vga_memory_size = infos.vga_memory_size;
|
||||||
|
@ -767,6 +770,9 @@
|
||||||
"cdrom": settings.cdrom,
|
"cdrom": settings.cdrom,
|
||||||
|
|
||||||
"multiboot": settings.multiboot,
|
"multiboot": settings.multiboot,
|
||||||
|
"bzimage": settings.bzimage,
|
||||||
|
"initrd": settings.initrd,
|
||||||
|
"cmdline": settings.cmdline,
|
||||||
|
|
||||||
"initial_state": settings.initial_state,
|
"initial_state": settings.initial_state,
|
||||||
"filesystem": settings.filesystem || {},
|
"filesystem": settings.filesystem || {},
|
||||||
|
|
|
@ -245,6 +245,7 @@ V86Starter.prototype.continue_init = function(emulator, options)
|
||||||
settings.boot_order = options["boot_order"] || 0x213;
|
settings.boot_order = options["boot_order"] || 0x213;
|
||||||
settings.fda = undefined;
|
settings.fda = undefined;
|
||||||
settings.fdb = undefined;
|
settings.fdb = undefined;
|
||||||
|
settings.cmdline = options["cmdline"];
|
||||||
|
|
||||||
if(options["network_relay_url"])
|
if(options["network_relay_url"])
|
||||||
{
|
{
|
||||||
|
@ -299,6 +300,12 @@ V86Starter.prototype.continue_init = function(emulator, options)
|
||||||
case "multiboot":
|
case "multiboot":
|
||||||
settings.multiboot = this.disk_images["multiboot"] = buffer.buffer;
|
settings.multiboot = this.disk_images["multiboot"] = buffer.buffer;
|
||||||
break;
|
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":
|
case "bios":
|
||||||
settings.bios = buffer.buffer;
|
settings.bios = buffer.buffer;
|
||||||
|
@ -345,7 +352,8 @@ V86Starter.prototype.continue_init = function(emulator, options)
|
||||||
};
|
};
|
||||||
|
|
||||||
if(name === "bios" || name === "vga_bios" ||
|
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.
|
// Ignore async for these because they must be availabe before boot.
|
||||||
// This should make result.buffer available after the object is loaded
|
// 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",
|
"bios", "vga_bios",
|
||||||
"cdrom", "hda", "hdb", "fda", "fdb",
|
"cdrom", "hda", "hdb", "fda", "fdb",
|
||||||
"initial_state", "multiboot",
|
"initial_state", "multiboot",
|
||||||
|
"bzimage", "initrd",
|
||||||
];
|
];
|
||||||
|
|
||||||
for(var i = 0; i < image_names.length; i++)
|
for(var i = 0; i < image_names.length; i++)
|
||||||
|
|
16
src/cpu.js
16
src/cpu.js
|
@ -533,7 +533,7 @@ CPU.prototype.set_state = function(state)
|
||||||
this.devices.pic = state[60];
|
this.devices.pic = state[60];
|
||||||
|
|
||||||
this.a20_enabled[0] = state[61];
|
this.a20_enabled[0] = state[61];
|
||||||
this.fw_value[0] = state;
|
this.fw_value = state[62];
|
||||||
|
|
||||||
this.devices.ioapic = state[63];
|
this.devices.ioapic = state[63];
|
||||||
|
|
||||||
|
@ -736,6 +736,17 @@ CPU.prototype.init = function(settings, device_bus)
|
||||||
|
|
||||||
this.load_bios();
|
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;
|
var a20_byte = 0;
|
||||||
|
|
||||||
io.register_read(0xB3, this, function()
|
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)
|
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));
|
dbg_log("bios config port, index=" + h(value));
|
||||||
|
|
||||||
function i32(x)
|
function i32(x)
|
||||||
|
|
234
src/kernel.js
Normal file
234
src/kernel.js
Normal file
|
@ -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;
|
||||||
|
}
|
Loading…
Reference in a new issue