Skip to content

pvh: Support booting via PVH ELFNOTE #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Mar 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 21 additions & 8 deletions layout.ld
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
ENTRY(ram64_start)
ENTRY(linux64_start)

PHDRS
{
program PT_LOAD FILEHDR PHDRS ;
ram PT_LOAD FILEHDR PHDRS ;
note PT_NOTE ;
}

/* Loaders like to put stuff in low memory (< 1M), so we don't use it. */
Expand All @@ -13,16 +14,28 @@ stack_size = 64K;

SECTIONS
{
/* Mapping in the program headers makes it easier to mmap the whole file. */
/* Mapping the program headers and note into RAM makes the file smaller. */
. = ram_min;
. += SIZEOF_HEADERS;
.note : { *(.note) } :note :ram

.rodata : { *(.rodata .rodata.*) } :program
.text : { *(.text .text.*) } :program
.data : { *(.data .data.*) } :program
.bss : { *(.bss .bss.*) } :program
/* These sections are mapped into RAM from the file. Omitting :ram from
later sections avoids emitting empty sections in the final binary. */
data_start = .;
.rodata : { *(.rodata .rodata.*) } :ram
.text : { *(.text .text.*) }
.text32 : { *(.text32) }
.data : { *(.data .data.*) }
data_size = . - data_start;

firmware_ram_size = . - ram_min;
/* The BSS section isn't mapped from any file data. It is simply zeroed
in RAM. So our file size should be computed from here. */
file_size = . - ram_min;
.bss : {
bss_start = .;
*(.bss .bss.*)
bss_size = . - bss_start;
}

ASSERT((. <= ram_max - stack_size), "firmware size too big for RAM region")

Expand Down
32 changes: 32 additions & 0 deletions src/asm/gdt64.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.section .rodata, "a"

gdt64_ptr:
.short gdt64_end - gdt64_start - 1 # GDT length is actually (length - 1)
.quad gdt64_start

gdt64_start: # First descriptor is always null
.quad 0
code64_desc: # 64-bit Code-Segments always have: Base = 0, Limit = 4G
# CS.Limit[15:00] = 0 - Ignored
.short 0x0000
# CS.Base[15:00] = 0 - Ignored
.short 0x0000
# CS.Base[23:16] = 0 (bits 0-7) - Ignored
.byte 0x00
# CS.Accessed = 1 (bit 8) - Don't write to segment on first use
# CS.ReadEnable = 1 (bit 9) - Read/Execute Code-Segment
# CS.Conforming = 0 (bit 10) - Nonconforming, no lower-priv access
# CS.Executable = 1 (bit 11) - Code-Segement
# CS.S = 1 (bit 12) - Not a System-Segement
# CS.DPL = 0 (bits 13-14) - We only use this segment in Ring 0
# CS.P = 1 (bit 15) - Segment is present
.byte 0b10011011
# CS.Limit[19:16] = 0 (bits 16-19) - Ignored
# CS.AVL = 0 (bit 20) - Our software doesn't use this bit
# CS.L = 1 (bit 21) - This isn't a 64-bit segment
# CS.D = 0 (bit 22) - This is a 32-bit segment
# CS.G = 0 (bit 23) - Ignored
.byte 0b00100000
# CS.Base[31:24] = 0 (bits 24-31) - Ignored
.byte 0x00
gdt64_end:
3 changes: 3 additions & 0 deletions src/asm/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
global_asm!(include_str!("note.s"));
global_asm!(include_str!("ram32.s"));
global_asm!(include_str!("ram64.s"));
global_asm!(include_str!("gdt64.s"));
20 changes: 20 additions & 0 deletions src/asm/note.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.section .note, "a"

# From xen/include/public/elfnote.h, "Physical entry point into the kernel."
XEN_ELFNOTE_PHYS32_ENTRY = 18

# We don't bother defining an ELFNOTE macro, as we only have one note.
# This is equialent to the kernel's:
# ELFNOTE(Xen, XEN_ELFNOTE_PHYS32_ENTRY, .long pvh_start)
.align 4
.long name_end - name_start # namesz
.long desc_end - desc_start # descsz
.long XEN_ELFNOTE_PHYS32_ENTRY # type
name_start:
.asciz "Xen"
name_end:
.align 4
desc_start:
.long ram32_start
desc_end:
.align 4
46 changes: 46 additions & 0 deletions src/asm/ram32.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.section .text32, "ax"
.code32

ram32_start:
# Stash the PVH start_info struct in %rdi.
movl %ebx, %edi
# Zero out %rsi, its value is unspecificed in the PVH Boot Protocol.
xorl %esi, %esi

setup_page_tables:
# First L2 entry identity maps [0, 2 MiB)
movl $0b10000011, (L2_TABLES) # huge (bit 7), writable (bit 1), present (bit 0)
# First L3 entry points to L2 table
movl $L2_TABLES, %eax
orb $0b00000011, %al # writable (bit 1), present (bit 0)
movl %eax, (L3_TABLE)
# First L4 entry points to L3 table
movl $L3_TABLE, %eax
orb $0b00000011, %al # writable (bit 1), present (bit 0)
movl %eax, (L4_TABLE)

enable_paging:
# Load page table root into CR3
movl $L4_TABLE, %eax
movl %eax, %cr3

# Set CR4.PAE (Physical Address Extension)
movl %cr4, %eax
orb $0b00100000, %al # Set bit 5
movl %eax, %cr4
# Set EFER.LME (Long Mode Enable)
movl $0xC0000080, %ecx
rdmsr
orb $0b00000001, %ah # Set bit 8
wrmsr
# Set CRO.PG (Paging)
movl %cr0, %eax
orl $(1 << 31), %eax
movl %eax, %cr0

jump_to_64bit:
# We are now in 32-bit compatibility mode. To enter 64-bit mode, we need to
# load a 64-bit code segment into our GDT.
lgdtl gdt64_ptr
# Set CS to a 64-bit segment and jump to 64-bit code.
ljmpl $(code64_desc - gdt64_start), $ram64_start
13 changes: 7 additions & 6 deletions src/asm/ram64.s
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
.section .text, "ax"
.global ram64_start
.global linux64_start
.code64

ram64_start:
# Indicate (via serial) that we are in long/64-bit mode
movw $0x3f8, %dx
movb $'L', %al
outb %al, %dx
linux64_start:
# Zero out %rdi, its value is unspecificed in the Linux Boot Protocol.
xorq %rdi, %rdi

ram64_start:
# Setup the stack (at the end of our RAM region)
movq $ram_max, %rsp

# PVH start_info is in %rdi, the first paramter of the System V ABI.
# BootParams are in %rsi, the second paramter of the System V ABI.
jmp rust64_start
211 changes: 211 additions & 0 deletions src/boot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
use core::mem;

use crate::{
common,
fat::{Error, Read},
mem::MemoryRegion,
};

// Common data needed for all boot paths
pub trait Info {
// Starting address of the Root System Descriptor Pointer
fn rsdp_addr(&self) -> u64;
// The kernel command line (not including null terminator)
fn cmdline(&self) -> &[u8];
// Methods to access the E820 Memory map
fn num_entries(&self) -> u8;
fn entry(&self, idx: u8) -> E820Entry;
}

#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct E820Entry {
pub addr: u64,
pub size: u64,
pub entry_type: u32,
}

impl E820Entry {
pub const RAM_TYPE: u32 = 1;
}

// The so-called "zeropage"
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Params {
screen_info: ScreenInfo, // 0x000
apm_bios_info: ApmBiosInfo, // 0x040
_pad2: [u8; 4], // 0x054
tboot_addr: u64, // 0x058
ist_info: IstInfo, // 0x060
pub acpi_rsdp_addr: u64, // 0x070
_pad3: [u8; 8], // 0x078
hd0_info: HdInfo, // 0x080 - obsolete
hd1_info: HdInfo, // 0x090 - obsolete
sys_desc_table: SysDescTable, // 0x0a0 - obsolete
olpc_ofw_header: OlpcOfwHeader, // 0x0b0
ext_ramdisk_image: u32, // 0x0c0
ext_ramdisk_size: u32, // 0x0c4
ext_cmd_line_ptr: u32, // 0x0c8
_pad4: [u8; 0x74], // 0x0cc
edd_info: EdidInfo, // 0x140
efi_info: EfiInfo, // 0x1c0
alt_mem_k: u32, // 0x1e0
scratch: u32, // 0x1e4
e820_entries: u8, // 0x1e8
eddbuf_entries: u8, // 0x1e9
edd_mbr_sig_buf_entries: u8, // 0x1ea
kbd_status: u8, // 0x1eb
secure_boot: u8, // 0x1ec
_pad5: [u8; 2], // 0x1ed
sentinel: u8, // 0x1ef
_pad6: [u8; 1], // 0x1f0
pub hdr: Header, // 0x1f1
_pad7: [u8; 0x290 - HEADER_END],
edd_mbr_sig_buffer: [u32; 16], // 0x290
e820_table: [E820Entry; 128], // 0x2d0
_pad8: [u8; 0x30], // 0xcd0
eddbuf: [EddInfo; 6], // 0xd00
_pad9: [u8; 0x114], // 0xeec
}

impl Default for Params {
fn default() -> Self {
// SAFETY: Struct consists entirely of primitive integral types.
unsafe { mem::zeroed() }
}
}

impl Params {
pub fn set_entries(&mut self, info: &dyn Info) {
self.e820_entries = info.num_entries();
for i in 0..self.e820_entries {
self.e820_table[i as usize] = info.entry(i);
}
}
}

impl Info for Params {
fn rsdp_addr(&self) -> u64 {
self.acpi_rsdp_addr
}
fn cmdline(&self) -> &[u8] {
unsafe { common::from_cstring(self.hdr.cmd_line_ptr as u64) }
}
fn num_entries(&self) -> u8 {
self.e820_entries
}
fn entry(&self, idx: u8) -> E820Entry {
assert!(idx < self.num_entries());
self.e820_table[idx as usize]
}
}

const HEADER_START: usize = 0x1f1;
const HEADER_END: usize = HEADER_START + mem::size_of::<Header>();

#[derive(Clone, Copy, Debug)]
#[repr(C, packed)]
pub struct Header {
pub setup_sects: u8,
pub root_flags: u16,
pub syssize: u32,
pub ram_size: u16,
pub vid_mode: u16,
pub root_dev: u16,
pub boot_flag: u16,
pub jump: u16,
pub header: [u8; 4],
pub version: u16,
pub realmode_swtch: u32,
pub start_sys_seg: u16,
pub kernel_version: u16,
pub type_of_loader: u8,
pub loadflags: u8,
pub setup_move_size: u16,
pub code32_start: u32,
pub ramdisk_image: u32,
pub ramdisk_size: u32,
pub bootsect_kludge: u32,
pub heap_end_ptr: u16,
pub ext_loader_ver: u8,
pub ext_loader_type: u8,
pub cmd_line_ptr: u32,
pub initrd_addr_max: u32,
pub kernel_alignment: u32,
pub relocatable_kernel: u8,
pub min_alignment: u8,
pub xloadflags: u16,
pub cmdline_size: u32,
pub hardware_subarch: u32,
pub hardware_subarch_data: u64,
pub payload_offset: u32,
pub payload_length: u32,
pub setup_data: u64,
pub pref_address: u64,
pub init_size: u32,
pub handover_offset: u32,
}

impl Header {
// Read a kernel header from the first two sectors of a file
pub fn from_file(f: &mut dyn Read) -> Result<Self, Error> {
let mut data: [u8; 1024] = [0; 1024];
let mut region = MemoryRegion::from_bytes(&mut data);

f.seek(0)?;
f.load_file(&mut region)?;

#[repr(C)]
struct HeaderData {
before: [u8; HEADER_START],
hdr: Header,
after: [u8; 1024 - HEADER_END],
}
// SAFETY: Struct consists entirely of primitive integral types.
Ok(unsafe { mem::transmute::<_, HeaderData>(data) }.hdr)
}
}

// Right now the stucts below are unused, so we only need them to be the correct
// size. Update test_size_and_offset if a struct's real definition is added.
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct ScreenInfo([u8; 0x40]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct ApmBiosInfo([u8; 0x14]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct IstInfo([u8; 0x10]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct HdInfo([u8; 0x10]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct SysDescTable([u8; 0x10]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct OlpcOfwHeader([u8; 0x10]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct EdidInfo([u8; 0x80]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct EfiInfo([u8; 0x20]);
#[derive(Clone, Copy)]
#[repr(C, packed)]
struct EddInfo([u8; 0x52]);

#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_size_and_offset() {
assert_eq!(mem::size_of::<Header>(), 119);
assert_eq!(mem::size_of::<E820Entry>(), 20);
assert_eq!(mem::size_of::<Params>(), 4096);

assert_eq!(offset_of!(Params, hdr), HEADER_START);
}
}
Loading