Skip to content

Commit 9c62748

Browse files
committed
bios: Add code to boot as Firmware ROM
The new assembly files handle: - rom16.s: Jumping from reset, 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 23d32d0 commit 9c62748

File tree

6 files changed

+116
-0
lines changed

6 files changed

+116
-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

+36
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. */
@@ -23,6 +24,7 @@ SECTIONS
2324
.text : { *(.text .text.*) }
2425
.text32 : { *(.text32) }
2526
.data : { *(.data .data.*) }
27+
data_end = .;
2628
data_size = . - data_start;
2729

2830
/* The BSS section isn't mapped from file data. It is just zeroed in RAM. */
@@ -38,6 +40,40 @@ 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+
/* When using the ROM, the entire firmware is loaded right below 4 GiB. Code
44+
at file offset X, which expects to execute at (ram_min + X), is loaded at
45+
address (four_gig - file_size + X). We use the linker script to compute
46+
"offset", the difference between these two values. Note that the BSS and
47+
Stack don't contribute to file size, we calculate from data_end. */
48+
four_gig = 1 << 32;
49+
rom_code_size = SIZEOF(.rom32) + SIZEOF(.rom16);
50+
offset = four_gig - data_end - rom_code_size - SIZEOF(.romdata);
51+
/* QEMU requires that the ROM size be 64K-aligned. If the file ends after
52+
the reset vector code, this is equivalent to "offset" being 64K-aligned,
53+
because ram_min and four_gig are both 64K-algined. */
54+
offset = -ALIGN(-offset, 64K); /* = ALIGN_DOWN(offset, 64K) */
55+
56+
/* With the ROM, the data at [rom_data_start, rom_data_end) expects to
57+
execute in RAM, so we will need to copy it to [data_start, data_end). */
58+
rom_data_start = data_start + offset;
59+
rom_data_end = data_end + offset;
60+
61+
/* This code/data expects to be in the ROM memory region, so we set the
62+
virtual adddress accordingly. We place the ROM data right after the RAM
63+
data (in the file) and place the ROM code at the very end of the region.
64+
This is necessary for the code to be correct:
65+
- If we placed all the code/data after rom_data_end, the reset vector
66+
code wouldn't be right before four_gig.
67+
- If we placed all the code/data at the end, the linker would still
68+
place the ROM data right after the RAM data in the file, making our
69+
virtual addresses inconsistent with the file layout. */
70+
. = rom_data_end;
71+
.romdata : { *(.romdata) } :rom
72+
/* This gap can't be optimized away, as these sections use the same PHDR. */
73+
. = four_gig - rom_code_size;
74+
.rom32 : { *(.rom32) }
75+
.rom16 : { KEEP(*(.rom16)) }
76+
4177
/* Match edk2's GccBase.lds DISCARD section */
4278
/DISCARD/ : {
4379
*(.note.GNU-stack)

src/asm/mod.rs

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

src/asm/rom16.s

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.section .rom16, "ax"
2+
.code16
3+
4+
rom16_start:
5+
# Order of instructions from Intel SDM 9.9.1 "Switching to Protected Mode"
6+
# Step 1: Disable interrupts
7+
cli
8+
9+
# Step 2: Load the GDT
10+
# We are currently in 16-bit real mode. To enter 32-bit protected mode, we
11+
# need to load 32-bit code/data segments into our GDT. The gdt32 in ROM is
12+
# at too high of an address (right below 4G) for the data segment to reach.
13+
#
14+
# But we can load gdt32 via the code segement. After a reset, the base of
15+
# the CS register is 0xFFFF0000, which means we can access gdt32.
16+
movw $(GDT32_PTR - 0xFFFF0000), %bx
17+
lgdtl %cs:(%bx)
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
27+
28+
# The reset vector must go at the end of ROM, exactly 16 bytes from the end.
29+
reset_vec: # 0x0_FFFF_FFF0
30+
jmp rom16_start
31+
.space (reset_vec + 0x10) - . # Pad to the end with zeros
32+
four_gigs: # 0x1_0000_0000

src/asm/rom32.s

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

0 commit comments

Comments
 (0)