Skip to content

Commit fd40b6e

Browse files
committed
bios: Add code to run as a BIOS image
Signed-off-by: Joe Richey <[email protected]>
1 parent 7bb1d31 commit fd40b6e

File tree

7 files changed

+159
-0
lines changed

7 files changed

+159
-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
x86_64 = "0.9"

layout.ld

+20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ PHDRS
44
{
55
ram PT_LOAD FILEHDR PHDRS ;
66
note PT_NOTE ;
7+
/* If we use PT_LOAD for the ROM, QEMU will load the our ROM code into the
8+
BIOS region when using PVH Boot, causing a conflict. We use PT_SHLIB (a
9+
reserved Program Header type) to prevent hypervisors from loading the
10+
ROM code or interpreting the ROM as a PT_NOTE. */
11+
rom PT_SHLIB ;
712
}
813

914
/* Loaders like to put stuff in low memory (< 1M), so we don't use it. */
@@ -39,6 +44,21 @@ SECTIONS
3944

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

47+
/* Get the size of the ROM sections without any padding */
48+
rom_size = SIZEOF(.romdata) + SIZEOF(.rom32) + SIZEOF(.rom16) + SIZEOF(.reset);
49+
/* If we have a ROM, we need to pad the file to be a multiple of 64K. */
50+
min_file_size = file_size + rom_size;
51+
pad_size = rom_size ? ALIGN(min_file_size, 64K) - min_file_size : 0;
52+
53+
/* The ROM code must be at the very end of the file, and mapped below 4G */
54+
. = (1 << 32) - rom_size - pad_size;
55+
rom_data_start = . - data_size;
56+
57+
.romdata : { . += pad_size; *(.romdata) } :rom
58+
.rom32 : { *(.rom32) }
59+
.rom16 : { *(.rom16) }
60+
.reset : { KEEP(*(.reset)) }
61+
4262
/* Match edk2's GccBase.lds DISCARD section */
4363
/DISCARD/ : {
4464
*(.note.GNU-stack)

src/asm/gdt32.s

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
.section .romdata, "a"
2+
3+
gdt32_ptr:
4+
.short gdt32_end - gdt32_start - 1 # GDT length is actually (length - 1)
5+
.long gdt32_start
6+
7+
gdt32_start: # First descriptor is always null
8+
.quad 0
9+
code32_desc: # Base = 0, Limit = 0xF_FFFF w/ 4K Ganularity = 4G
10+
# CS.Limit[15:00] = 0xFFFF
11+
.short 0xffff
12+
# CS.Base[15:00] = 0
13+
.short 0x0000
14+
# CS.Base[23:16] = 0 (bits 0-7)
15+
.byte 0x00
16+
# CS.Accessed = 1 (bit 8) - Don't write to segment on first use
17+
# CS.ReadEnable = 1 (bit 9) - Read/Execute Code-Segment
18+
# CS.Conforming = 0 (bit 10) - Nonconforming, no lower-priv access
19+
# CS.Executable = 1 (bit 11) - Code-Segement
20+
# CS.S = 1 (bit 12) - Not a System-Segement
21+
# CS.DPL = 0 (bits 13-14) - We only use this segment in Ring 0
22+
# CS.P = 1 (bit 15) - Segment is present
23+
.byte 0b10011011
24+
# CS.Limit[19:16] = 0xF (bits 16-19)
25+
# CS.AVL = 0 (bit 20) - Our software doesn't use this bit
26+
# CS.L = 0 (bit 21) - This isn't a 64-bit segment
27+
# CS.B = 1 (bit 22) - This is a 32-bit segment
28+
# CS.G = 1 (bit 23) - 4K Granularity
29+
.byte 0b11001111
30+
# CS.Base[31:24] = 0 (bits 24-31)
31+
.byte 0x00
32+
33+
data32_desc: # Base = 0, Limit = 0xF_FFFF w/ 4K Ganularity = 4G
34+
# DS.Limit[15:00] = 0xFFFF
35+
.short 0xffff
36+
# DS.Base[15:00] = 0
37+
.short 0x0000
38+
# DS.Base[23:16] = 0 (bits 0-7)
39+
.byte 0x00
40+
# DS.Accessed = 1 (bit 8) - Don't write to segment on first use
41+
# DS.WriteEnable = 1 (bit 9) - Read/Write Data-Segment
42+
# DS.Expansion = 0 (bit 10) - Expand-up
43+
# DS.Executable = 0 (bit 11) - Data-Segement
44+
# DS.S = 1 (bit 12) - Not a System-Segement
45+
# DS.DPL = 0 (bits 13-14) - We only use this segment in Ring 0
46+
# DS.P = 1 (bit 15) - Segment is present
47+
.byte 0b10010011
48+
# DS.Limit[19:16] = 0xF (bits 16-19)
49+
# DS.AVL = 0 (bit 20) - Our software doesn't use this bit
50+
# DS.L = 0 (bit 21) - This isn't a 64-bit segment
51+
# DS.B = 1 (bit 22) - This is a 32-bit segment
52+
# DS.G = 1 (bit 23) - 4K Granularity
53+
.byte 0b11001111
54+
# DS.Base[31:24] = 0 (bits 24-31)
55+
.byte 0x00
56+
gdt32_end:

src/asm/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,12 @@ global_asm!(include_str!("note.s"));
22
global_asm!(include_str!("ram32.s"));
33
global_asm!(include_str!("ram64.s"));
44
global_asm!(include_str!("gdt64.s"));
5+
6+
#[cfg(feature = "rom")]
7+
global_asm!(include_str!("reset.s"));
8+
#[cfg(feature = "rom")]
9+
global_asm!(include_str!("rom16.s"));
10+
#[cfg(feature = "rom")]
11+
global_asm!(include_str!("rom32.s"));
12+
#[cfg(feature = "rom")]
13+
global_asm!(include_str!("gdt32.s"));

src/asm/reset.s

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.section .reset, "ax"
2+
.code16
3+
4+
.align 16
5+
reset_vec: # 0x0_FFFF_FFF0
6+
jmp rom16_start
7+
8+
.align 16
9+
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+
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 serializes the processor.
25+
# Set CS to a 32-bit segment and jump to 32-bit code.
26+
ljmpl $(code32_desc - gdt32_start), $rom32_start

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 $(data32_desc - gdt32_start), %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

0 commit comments

Comments
 (0)