Skip to content

Commit 6defd88

Browse files
committed
bios: Add code to boot as Firmware ROM
The new assembly files handle: - reset.s: Jumping from reset - rom16.s: Transitioning to 32-bit mode - rom32.s: Copying data from ROM to RAM, jumping to PVH entry point To place this code correctly, we add a new Program Header for the code and data that expect to be in ROM. See the comments in layout.ld for more information. We also place the 32-bit GDT in the ROM. This is mostly for convenience, as it lets us use the GDT directly from the ROM code without having to do any complex offset calculations. As laying out the code for a ROM makes the binary ~45% bigger, we gate building as a ROM behind an optional feature. Signed-off-by: Joe Richey <[email protected]>
1 parent b95f628 commit 6defd88

File tree

7 files changed

+114
-0
lines changed

7 files changed

+114
-0
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ log-serial = []
2222
# Log panics to serial output. Disabling this (without disabling log-serial)
2323
# gets you most of the code size reduction, without losing _all_ debugging.
2424
log-panic = ["log-serial"]
25+
# Support builing the firmware as a BIOS ROM (i.e. starting in real mode).
26+
rom = []
2527

2628
[dependencies]
2729
bitflags = "1.2"

layout.ld

+29
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ PHDRS
44
{
55
ram PT_LOAD FILEHDR PHDRS ;
66
note PT_NOTE ;
7+
rom PT_LOAD ;
78
}
89

910
/* Loaders like to put stuff in low memory (< 1M), so we don't use it. */
@@ -24,6 +25,7 @@ SECTIONS
2425
.text32 : { *(.text32) }
2526
.data : { *(.data .data.*) }
2627
data_size = . - data_start;
28+
file_size = . - ram_min;
2729

2830
/* The BSS section isn't mapped from file data. It is just zeroed in RAM. */
2931
.bss : {
@@ -38,6 +40,33 @@ SECTIONS
3840
/* ram32.s only maps the first 2 MiB, and that must include the stack. */
3941
ASSERT((. <= 2M), "Stack overflows initial identity-mapped memory region")
4042

43+
/* This is correct because all of the code sections have alignment 16. */
44+
rom_size = ALIGN(SIZEOF(.gdt32), 16) + ALIGN(SIZEOF(.rom32), 16)
45+
+ ALIGN(SIZEOF(.rom16), 16) + ALIGN(SIZEOF(.reset), 16);
46+
/* Our file's length (RAM code/data + ROM) without any additional padding */
47+
file_size += rom_size;
48+
/* QEMU requires the ROM size to be a multiple of 64K. To achieve this for
49+
our `sstrip`ed binary, we insert enough padding before the ROM code,
50+
so that the end of the ROM code falls on a 64K file bounary. */
51+
pad_size = ALIGN(file_size, 64K) - file_size;
52+
53+
/* When using the ROM, the entire firmware is loaded right below 4 GiB.
54+
We work backwards from the end to figure where to place the ROM code,
55+
32-bit GDT, padding, and the remainder of the firmware. */
56+
pad_start = (1 << 32) - rom_size - pad_size;
57+
/* The remainder of the firmware code/data expects to be run at addresses
58+
[data_start, data_start + data_size), but will initially be located in
59+
ROM, at addresses [rom_data_start, rom_data_start + data_size). As the
60+
padding comes right after the remainder of the firmware, we have: */
61+
rom_data_start = pad_start - data_size;
62+
63+
/* Only insert the padding if we are building as a ROM (to save size). */
64+
.pad pad_start : { . += rom_size ? pad_size : 0; } :NONE
65+
.gdt32 : { *(.gdt32) } :rom
66+
.rom32 : { *(.rom32) }
67+
.rom16 : { *(.rom16) }
68+
.reset : { KEEP(*(.reset)) }
69+
4170
/* Strip symbols from the output binary (comment out to get symbols) */
4271
/DISCARD/ : {
4372
*(.symtab)

src/asm/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
1+
#[cfg(feature = "rom")]
2+
global_asm!(include_str!("reset.s"));
3+
4+
global_asm!(include_str!("rom16.s"));
5+
global_asm!(include_str!("rom32.s"));
16
global_asm!(include_str!("ram32.s"));
27
global_asm!(include_str!("ram64.s"));

src/asm/reset.s

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.section .reset, "ax"
2+
.code16
3+
4+
# The reset vector must go at the end of ROM, exactly 16 bytes from the end.
5+
.align 16
6+
reset_vec: # 0x0_FFFF_FFF0
7+
jmp rom16_start
8+
9+
.align 16, 0
10+
reset_end: # 0x1_0000_0000

src/asm/rom16.s

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.section .rom16, "ax"
2+
.code16
3+
4+
.align 16
5+
rom16_start:
6+
# Order of instructions from Intel SDM 9.9.1 "Switching to Protected Mode"
7+
# Step 1: Disable interrupts
8+
cli
9+
10+
# Step 2: Load the GDT
11+
# We are currently in 16-bit real mode. To enter 32-bit protected mode, we
12+
# need to load 32-bit code/data segments into our GDT. The gdt32 in ROM is
13+
# at too high of an address (right below 4G) for the data segment to reach.
14+
#
15+
# But we can load gdt32 via the code segement. After a reset, the base of
16+
# the CS register is 0xFFFF0000, which means we can access gdt32.
17+
lgdtl %cs:(GDT32_PTR - 0xFFFF0000)
18+
19+
# Step 3: Set CRO.PE (Protected Mode Enable)
20+
movl %cr0, %eax
21+
orb $0b00000001, %al # Set bit 0
22+
movl %eax, %cr0
23+
24+
# Step 4: Far JMP to change execution flow and serialize the processor.
25+
# Set CS to a 32-bit Code-Segment and jump to 32-bit code.
26+
ljmpl $0x08, $rom32_start

src/asm/rom32.s

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.section .rom32, "ax"
2+
.code32
3+
4+
.align 16
5+
rom32_start:
6+
# Now that we are in 32-bit mode, setup all the Data-Segments to be 32-bit.
7+
movw $0x10, %ax
8+
movw %ax, %ds
9+
movw %ax, %es
10+
movw %ax, %ss
11+
movw %ax, %fs
12+
movw %ax, %gs
13+
14+
# Needed for the REP instructions below
15+
cld
16+
17+
copy_rom_to_ram:
18+
# This is equivalent to: memcpy(data_start, rom_data_start, data_size)
19+
movl $rom_data_start, %esi
20+
movl $data_start, %edi
21+
movl $data_size, %ecx
22+
rep movsb (%esi), (%edi)
23+
24+
zero_bss_in_ram:
25+
# This is equivalent to: memset(bss_start, 0, bss_size)
26+
xorb %al, %al
27+
movl $bss_start, %edi
28+
movl $bss_size, %ecx
29+
rep stosb %al, (%edi)
30+
31+
jump_to_ram:
32+
# Zero out %ebx, as we don't have a PVH StartInfo struct.
33+
xorl %ebx, %ebx
34+
35+
# Jumping all that way from ROM (~4 GiB) to RAM (~1 MiB) is too far for a
36+
# 32-bit relative jump, so we use a 32-bit absolute jump.
37+
movl $ram32_start, %eax
38+
jmp *%eax

src/gdt.rs

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ impl Pointer {
6060
static GDT64_PTR: Pointer = Pointer::new(&GDT64);
6161
static GDT64: [Descriptor; 2] = [Descriptor::empty(), Descriptor::CODE64];
6262

63+
// Our 32-bit GDT lives in ROM, so it can be directly used by the ROM code. We
64+
// should never reference or access this GDT when we are running in RAM.
6365
#[no_mangle]
66+
#[link_section = ".gdt32"]
6467
static GDT32_PTR: Pointer = Pointer::new(&GDT32);
68+
#[link_section = ".gdt32"]
6569
static GDT32: [Descriptor; 3] = [Descriptor::empty(), Descriptor::CODE32, Descriptor::DATA32];

0 commit comments

Comments
 (0)