Skip to content

Commit ecb4507

Browse files
committed
Support building a flat BIOS image
Signed-off-by: Joe Richey <joerichey@google.com>
1 parent 0669ebe commit ecb4507

File tree

5 files changed

+163
-1
lines changed

5 files changed

+163
-1
lines changed

layout.ld

+7
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,17 @@ SECTIONS
2828
.text : {
2929
*(.text .text.*)
3030
*(.ram64)
31+
*(.ram32)
3132
} :text
3233

3334
firmware_ram_size = . - ram_min;
3435

36+
/* The ROM code must be at the end of the file (for the reset vector), */
37+
/* and the filesize must a multiple of 64K to boot on QEMU. */
38+
. = ALIGN(. + SIZEOF(.rom), 64K) - SIZEOF(.rom);
39+
.rom : { KEEP(*(.rom)) } :text
40+
firmware_rom_size = . - ram_min;
41+
3542
/* Memory for identity mapping, keep synced with ADDRESS_SPACE_GIB */
3643
address_space_gib = 4;
3744
. = ALIGN(4K);

src/asm/ram32.s

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
.section .ram32, "ax"
2+
.code32
3+
4+
ram32_start:
5+
# Indicate (via serial) that we are executing out of RAM
6+
movw $0x3f8, %dx
7+
movb $'R', %al
8+
outb %al, %dx
9+
10+
setup_page_tables:
11+
# First PML2 entry identity maps [0, 2 MiB)
12+
movl $0b10000011, (pml2t) # huge (bit 7), writable (bit 1), present (bit 0)
13+
# First PML3 entry points to PML2 table
14+
movl $pml2t, %eax
15+
orb $0b00000011, %al # writable (bit 1), present (bit 0)
16+
movl %eax, (pml3t)
17+
# First PML4 entry points to PML3 table
18+
movl $pml3t, %eax
19+
orb $0b00000011, %al # writable (bit 1), present (bit 0)
20+
movl %eax, (pml4t)
21+
22+
enable_paging:
23+
# Load page table root into CR3
24+
movl $pml4t, %eax
25+
movl %eax, %cr3
26+
27+
# Set CR4.PAE (Physical Address Extension)
28+
movl %cr4, %eax
29+
orb $0b00100000, %al # Set bit 5
30+
movl %eax, %cr4
31+
# Set EFER.LME (Long Mode Enable)
32+
movl $0xC0000080, %ecx
33+
rdmsr
34+
orb $0b00000001, %ah # Set bit 8
35+
wrmsr
36+
# Set CRO.PG (Paging)
37+
movl %cr0, %eax
38+
orl $(1 << 31), %eax
39+
movl %eax, %cr0
40+
41+
# Indicate (via serial) that we have enabled paging
42+
movw $0x3f8, %dx
43+
movb $'P', %al
44+
outb %al, %dx
45+
46+
jump_to_64bit:
47+
# We are now in 32-bit compatibility mode. To enter 64-bit mode, we need to
48+
# load a 64-bit code segment into our GDT.
49+
lgdtl gdt64_ptr
50+
# Set CS to a 64-bit segment and jump to 64-bit code.
51+
ljmpl $(code64_desc - gdt64_start), $ram64_start
52+
53+
gdt64_ptr:
54+
.short gdt64_end - gdt64_start - 1 # GDT length is actually (length - 1)
55+
.long gdt64_start
56+
gdt64_start:
57+
.quad 0 # First descriptor is null
58+
code64_desc:
59+
.quad (1<<43) | (1<<44) | (1<<47) | (1<<53) # Only these bits do anything
60+
gdt64_end:

src/asm/ram64.s

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ ram64_start:
2626

2727
halt_loop:
2828
hlt
29-
jmp halt_loop
29+
jmp halt_loop

src/asm/rom.s

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
.section .rom, "ax"
2+
3+
# This ROM will be mapped right at the end of the 32-bit address space, but the
4+
# linker assumes all code executes in RAM, and gives symbols addresses in that
5+
# range. To get around this, we manully compute ROM addresses.
6+
gdt32_addr32 = (1 << 32) - (rom_end - gdt32_start)
7+
rom32_addr32 = (1 << 32) - (rom_end - rom32_start)
8+
gdt32_ptr_addr16 = (1 << 16) - (rom_end - gdt32_ptr)
9+
10+
gdt32_ptr:
11+
.short gdt32_end - gdt32_start - 1 # GDT length is actually (length - 1)
12+
.long gdt32_addr32
13+
# Note: Out GDT descriptors must be marked "accessed", or the processor will
14+
# hang when it attempts to update them (as the gdt32 is in ROM).
15+
gdt32_start:
16+
.quad 0 # First descriptor is always unused
17+
code32_desc: # base = 0x00000000, limit = 0xfffff x 4K
18+
.short 0xffff # limit[0..16] = 0xffff
19+
.short 0x0000 # base [0..16] = 0x0000
20+
.byte 0x00 # base[16..24] = 0x00
21+
.byte 0b10011011 # present, DPL = 0, system, code seg, grows up, readable, accessed
22+
.byte 0b11001111 # 4K gran, 32-bit, limit[16..20] = 0x1111 = 0xf
23+
.byte 0x00 # base[24..32] = 0x00
24+
data32_desc: # base = 0x00000000, limit = 0xfffff x 4K
25+
.short 0xffff # limit 15:0
26+
.short 0x0000 # base 15:0
27+
.byte 0x00 # base[16..24] = 0x00
28+
.byte 0b10010011 # present, DPL = 0, system, data seg, ring0 only, writable, accessed
29+
.byte 0b11001111 # 4K gran, 32-bit, limit[16..20] = 0x1111 = 0xf
30+
.byte 0x00 # base[24..32] = 0x00
31+
gdt32_end:
32+
33+
.code32
34+
rom32_start:
35+
# Now that we are in 32-bit mode, setup all the data segments to be 32-bit.
36+
movw $(data32_desc - gdt32_start), %ax
37+
movw %ax, %ds
38+
movw %ax, %es
39+
movw %ax, %ss
40+
movw %ax, %fs
41+
movw %ax, %gs
42+
43+
# The rest of the firmware assumes it executes from RAM in a region just
44+
# above ram_min, so we copy all of that code into RAM and jump to it.
45+
movl $ram_min, %edi
46+
# Ideally we would define:
47+
# rom_min = (1 << 32) - firmware_rom_size
48+
# above, and just do
49+
# movl $rom_min, %esi
50+
# However, firmware_rom_size is not known until link time, so the assembler
51+
# can't handle such code. Thus, the firmware has to do the addreess math.
52+
xorl %esi, %esi
53+
# For 32-bit registers: 0 - offset = (1 << 32) - offset
54+
subl $firmware_rom_size, %esi
55+
movl $firmware_ram_size, %ecx
56+
57+
# This code is essentially: memcpy(ram_min, rom_min, firmware_ram_size)
58+
cld
59+
rep movsb (%esi), (%edi)
60+
61+
# Jumping all that way from ROM (~4 GiB) to RAM (~1 MiB) is too far for a
62+
# relative jump, so we use an aboslute jump.
63+
movl $ram32_start, %eax
64+
jmpl *%eax
65+
66+
.code16
67+
rom16_protected:
68+
# We are now in 16-bit protected mode, To enter 32-bit protected mode, we
69+
# need to load 32-bit code/data segments into our GDT. The gdt32 in ROM is
70+
# at too high of an address (4 GiB - offset) for the data segment to reach.
71+
# So, we load gdt32 via the 16-bit code segement, using a 16-bit address.
72+
movw $gdt32_ptr_addr16, %bx
73+
lgdtl %cs:(%bx)
74+
75+
# Set CS to a 32-bit segment and jump to 32-bit code.
76+
ljmpl $(code32_desc - gdt32_start), $rom32_addr32
77+
78+
.align 16
79+
reset_vector: # 0xffff_fff0
80+
# This code must be 16 bytes or less, so be careful when adding anyting.
81+
cli
82+
83+
# Set CRO.PE (Protected Mode Enable)
84+
movl %cr0, %eax
85+
orb $0b00000001, %al # Set bit 0
86+
movl %eax, %cr0
87+
88+
jmp rom16_protected
89+
90+
.align 16
91+
rom_end: # 0x1_0000_0000

src/main.rs

+4
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ mod pci;
3838
mod pe;
3939
mod virtio;
4040

41+
#[cfg(not(test))]
42+
global_asm!(include_str!("asm/rom.s"));
43+
#[cfg(not(test))]
44+
global_asm!(include_str!("asm/ram32.s"));
4145
#[cfg(not(test))]
4246
global_asm!(include_str!("asm/ram64.s"));
4347

0 commit comments

Comments
 (0)