stage1: add bootstrap code
parent
2bcfd294be
commit
ce19f70440
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Congratulations, you made it through stage0!
|
||||
* Now that we have PLENTY of space, we can proceed to doing just about the
|
||||
* same thing again with stage2. However, this time we don't have to count
|
||||
* how many bytes instructions will take up. We even adhere to a standard ABI!
|
||||
*
|
||||
* Parameters are passed through %(e)ax, %(e)dx, and then the stack.
|
||||
* Return values are passed through %(e)ax and then the stack, unless the
|
||||
* subroutine only returns a boolean in which case it sets CF=0 for true and
|
||||
* CF=1 for false. Scratch registers are %ax, %cx and %dx; the rest is
|
||||
* callee-saved.
|
||||
*
|
||||
* This file's sole responsibility is to transition from 16-bit Real Mode
|
||||
* to 32-bit Protected Mode; everything else happens in Rust.
|
||||
*/
|
||||
|
||||
.extern main /* main.rs */
|
||||
.extern _bss_start /* stage1.ld */
|
||||
.extern _bss_end /* stage1.ld */
|
||||
|
||||
.text
|
||||
.code16
|
||||
|
||||
GLOBL _boot
|
||||
cld
|
||||
|
||||
/*
|
||||
* Initialize all segment registers
|
||||
* (stage0 only guarantees a valid %cs and %ss)
|
||||
*/
|
||||
xor %ax, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
/* clear .bss */
|
||||
mov $_bss_start, %di
|
||||
mov $_bss_end, %cx /* XXX we can't subtract with external symbols */
|
||||
sub %di, %cx
|
||||
shr %cx
|
||||
rep stosw
|
||||
|
||||
/* stash away all the parameters stage0 pushed for us */
|
||||
pop %ax
|
||||
mov %al, boot_drive
|
||||
pop %ax
|
||||
mov %ax, (boot_lba + 4)
|
||||
pop %eax
|
||||
mov %eax, boot_lba
|
||||
mov %sp, %bp
|
||||
|
||||
/* say hello */
|
||||
mov $msg_loading, %ax
|
||||
call print
|
||||
|
||||
/* check the A20 line and attempt to enable it if required
|
||||
* (s/o to the engineers @ IBM, keep up the great work) */
|
||||
cli /* we don't wanna do this with interrupts */
|
||||
|
||||
call a20_check
|
||||
jnc a20_enabled
|
||||
|
||||
call a20_enable_bios /* try asking the BIOS */
|
||||
jc 1f
|
||||
call a20_check /* check if this acually worked */
|
||||
jnc a20_enabled
|
||||
|
||||
1: call a20_enable_fast /* now try the FAST A20 method */
|
||||
call a20_check
|
||||
jnc a20_enabled
|
||||
|
||||
call a20_enable /* lastly, try the old-school method */
|
||||
call a20_check
|
||||
jc err_no_a20 /* if this fails, we're screwed */
|
||||
|
||||
a20_enabled:
|
||||
/* int13/00 RESET DISK SYSTEM */
|
||||
mov $0, %ah
|
||||
int $0x13
|
||||
|
||||
/* make absolutely sure that %sp is aligned */
|
||||
and $0xfffc, %sp
|
||||
|
||||
/* make real_to_prot return directly to rust */
|
||||
pushw $main
|
||||
jmp real_to_prot
|
||||
|
||||
err_no_a20:
|
||||
mov $msg_err_no_a20, %ax
|
||||
jmp print_err_and_halt
|
||||
END _boot
|
||||
|
||||
/* bool a20_check(void) */
|
||||
LOCAL a20_check
|
||||
push %si
|
||||
push %gs
|
||||
|
||||
/*
|
||||
* If the A20 line is masked, memory address 0x10500 wraps around
|
||||
* to 0x00500 (in other words, 0x0000:0500 equals 0xffff:0510).
|
||||
* Check if the words at those addresses match.
|
||||
*/
|
||||
mov $0xffff, %ax
|
||||
mov %ax, %gs
|
||||
mov $_stage1_magic, %si
|
||||
mov $(_stage1_magic + 0x10), %di
|
||||
|
||||
mov (%si), %ax
|
||||
cmp %gs:(%di), %ax
|
||||
jne 1f
|
||||
|
||||
/*
|
||||
* Looks like the words at those addresses are the same.
|
||||
* Now make sure it's not just bad luck because something happened
|
||||
* to write our magic there. Flip the bits and see if the changes
|
||||
* are reflected in high memory.
|
||||
*/
|
||||
not %ax
|
||||
mov %ax, (%si)
|
||||
|
||||
push %ax
|
||||
xor %ax, %ax
|
||||
mov $0x80, %dx
|
||||
out %al, %dx
|
||||
out %al, %dx
|
||||
out %al, %dx
|
||||
out %al, %dx
|
||||
pop %ax
|
||||
|
||||
cmp %gs:(%di), %ax
|
||||
jne 1f
|
||||
|
||||
stc /* return false if not enabled */
|
||||
jmp 2f
|
||||
1: clc /* return true if not not enabled */
|
||||
2: pop %gs
|
||||
pop %si
|
||||
ret
|
||||
END a20_check
|
||||
|
||||
/*
|
||||
* void a20_enable(void)
|
||||
*
|
||||
* Enable the A20 gate using the old-school method introduced in the original
|
||||
* IBM PC by fucking with the keyboard controller for absolutely no reason.
|
||||
*/
|
||||
LOCAL a20_enable
|
||||
pushf
|
||||
cli
|
||||
|
||||
mov $2, %al
|
||||
call a20_wait
|
||||
mov $0xad, %al
|
||||
out %al, $0x64
|
||||
|
||||
mov $2, %al
|
||||
call a20_wait
|
||||
mov $0xd8, %al
|
||||
out %al, $0x64
|
||||
|
||||
mov $1, %al
|
||||
call a20_wait
|
||||
in $0x60, %al
|
||||
push %ax
|
||||
|
||||
mov $2, %al
|
||||
call a20_wait
|
||||
mov $0xd1, %al
|
||||
out %al, $0x64
|
||||
|
||||
mov $2, %al
|
||||
call a20_wait
|
||||
pop %ax
|
||||
or $1, %ax
|
||||
out %al, $0x60
|
||||
|
||||
mov $2, %al
|
||||
call a20_wait
|
||||
mov $0xae, %al
|
||||
out %al, $0x64
|
||||
|
||||
mov $2, %al
|
||||
call a20_wait
|
||||
|
||||
popf
|
||||
ret
|
||||
END a20_enable
|
||||
|
||||
/* void a20_wait(u8 bit) */
|
||||
LOCAL a20_wait
|
||||
xchg %ax, %cx
|
||||
1: in $0x64, %al
|
||||
test %cl, %al
|
||||
jnz 1b
|
||||
|
||||
ret
|
||||
END a20_wait
|
||||
|
||||
/*
|
||||
* void a20_enable_fast(void)
|
||||
*
|
||||
* Enable the A20 gate using the "FAST A20" method.
|
||||
*/
|
||||
LOCAL a20_enable_fast
|
||||
in $0x92, %al
|
||||
test $2, %al
|
||||
jnz 1f
|
||||
|
||||
or $2, %al
|
||||
/* make absolutely sure bit 0 is never 1 because that would
|
||||
* trigger a hot system reset, which we kinda don't want */
|
||||
and $-2, %al
|
||||
out %al, $0x92
|
||||
|
||||
1: ret
|
||||
END a20_enable_fast
|
||||
|
||||
/*
|
||||
* bool a20_enable_bios(void)
|
||||
*
|
||||
* Enable the A20 gate using BIOS int15/2403
|
||||
*/
|
||||
LOCAL a20_enable_bios
|
||||
pusha /* don't trust the BIOS */
|
||||
|
||||
mov $0x2403, %ax
|
||||
int $0x15 /* int15/2403 QUERY A20 GATE SUPPORT */
|
||||
jc 3f /* CF = 0 on success */
|
||||
test %ah, %ah
|
||||
jnz 2f /* %ah = 0 on success */
|
||||
|
||||
mov $0x2402, %ax
|
||||
int $0x15 /* int15/2402 GET A20 GATE STATUS */
|
||||
jc 3f /* CF = 0 on success */
|
||||
test %ah, %ah
|
||||
jnz 2f /* %ah = 0 on success */
|
||||
cmp $1, %al
|
||||
je 1f /* %al = 1 if enabled */
|
||||
|
||||
mov $0x2401, %ax
|
||||
int $0x15 /* int15/2401 ENABLE A20 GATE */
|
||||
jc 3f /* CF = 0 on success */
|
||||
test %ah, %ah
|
||||
jnz 2f /* %ah = 0 on success */
|
||||
|
||||
1: clc /* return true on success */
|
||||
jmp 3f
|
||||
|
||||
2: stc /* return false on failure */
|
||||
|
||||
3: popa
|
||||
ret
|
||||
END a20_enable_bios
|
||||
|
||||
/* void print_err_and_halt(const char *msg) __noreturn */
|
||||
LOCAL print_err_and_halt
|
||||
call print
|
||||
mov $msg_halted, %ax
|
||||
call print
|
||||
|
||||
1: cli
|
||||
hlt
|
||||
jmp 1b
|
||||
END print_err_and_halt
|
||||
|
||||
/* void print(const char *msg) */
|
||||
LOCAL print
|
||||
push %bx
|
||||
push %si
|
||||
|
||||
mov %ax, %si
|
||||
|
||||
1: lodsb
|
||||
test %al, %al
|
||||
jz 2f
|
||||
mov $0x0e, %ah
|
||||
mov $0x0001, %bx
|
||||
int $0x10
|
||||
jmp 1b
|
||||
|
||||
2: pop %si
|
||||
pop %bx
|
||||
ret
|
||||
END print
|
||||
|
||||
.section .rodata
|
||||
|
||||
LOCAL msg_loading, object
|
||||
.asciz "loading, please wait...\r\n"
|
||||
END msg_loading
|
||||
LOCAL msg_halted, object
|
||||
.asciz "System halted"
|
||||
END msg_halted
|
||||
LOCAL msg_err_no_a20, object
|
||||
.asciz "Error: Failed to enable the A20 line\r\n"
|
||||
END msg_err_no_a20
|
||||
|
||||
.section .bss
|
||||
|
||||
GLOBL boot_lba
|
||||
.quad 0
|
||||
END boot_lba
|
||||
|
||||
GLOBL boot_drive
|
||||
.byte 0
|
||||
END boot_drive
|
@ -0,0 +1,344 @@
|
||||
|
||||
.text
|
||||
|
||||
/*
|
||||
* Transition from Real Mode to Protected Mode as outlined in the
|
||||
* Intel(R) 64 and IA-32 Architectures Software Developer's Manual
|
||||
* vol. 3A, chapter 9.9.1. Lines prefixed with '>' are direct quotes.
|
||||
* Annotations within those lines are enclosed in [square brackets].
|
||||
* Must be called in Real Mode, returns in Protected Mode.
|
||||
*
|
||||
* ATTENTION: This messes with the stack! Before calling this, you
|
||||
* MUST ensure that the stack pointer is aligned to 4 bytes (that is,
|
||||
* *before* the call to this subroutine pushes the return address).
|
||||
*
|
||||
* ATTENTION: This trashes registers %ax and %dx, as well as EFLAGS.
|
||||
*
|
||||
> Intel 64 and IA-32 processors have slightly different requirements
|
||||
> for switching to protected mode. To ensure upwards and downwards
|
||||
> code compatibility with Intel 64 and IA-32 processors, we recommend
|
||||
> that you follow these steps:
|
||||
*/
|
||||
GLOBL real_to_prot
|
||||
.code16
|
||||
|
||||
/* okay we have to be a little careful with this one */
|
||||
|
||||
/*
|
||||
> 1. Disable interrupts. A CLI instruction disables maskable hardware
|
||||
> interrupts. NMI interrupts can be disabled with external circuitry.
|
||||
*
|
||||
* So we mask the Non-Maskable Interrupt. Truly an x86 moment.
|
||||
*/
|
||||
cli
|
||||
in $0x70, %al
|
||||
or $0x80, %al
|
||||
out %al, $0x70
|
||||
in $0x71, %al
|
||||
|
||||
/* Grab the return address and store the old %ss */
|
||||
xor %edx, %edx
|
||||
pop %dx /* %dx = return address */
|
||||
mov %ss, %ax
|
||||
mov %ax, real_ss
|
||||
|
||||
/*
|
||||
> 2. Execute the LGDT instruction to load the GDTR register
|
||||
> with the base address of the GDT.
|
||||
*/
|
||||
lgdt gdtr
|
||||
|
||||
/*
|
||||
> 3. Execute a MOV CR0 instruction that sets the PE flag
|
||||
> (and optionally the PG flag) in control register CR0.
|
||||
*
|
||||
* We don't use paging but we DO want to set the Protection Enable bit.
|
||||
*/
|
||||
mov %cr0, %eax
|
||||
or $1, %al
|
||||
mov %eax, %cr0
|
||||
|
||||
/*
|
||||
> 4. Immediately following the MOV CR0 instruction, execute a far
|
||||
> JMP or far CALL instruction. (This operation is typically a far
|
||||
> jump or call to the next instruction in the instruction stram.)
|
||||
*/
|
||||
ljmpl $0x08, $1f
|
||||
|
||||
.code32
|
||||
|
||||
/* recompute the (now linear) stack address */
|
||||
1: xor %eax, %eax
|
||||
mov %ss, %ax
|
||||
shl $4, %eax
|
||||
add %eax, %esp
|
||||
|
||||
/*
|
||||
> 5. The JMP or CALL instruction immediately after the MOV CR0
|
||||
> instruction changes the flow of execution and serializes
|
||||
> the processor.
|
||||
*
|
||||
* Ok.
|
||||
*
|
||||
> 6. If paging is enabled [...]
|
||||
*
|
||||
* It is not.
|
||||
*
|
||||
> 7. If a local descriptor table is going to be used [...]
|
||||
*
|
||||
* No.
|
||||
*/
|
||||
|
||||
/*
|
||||
> 8. Execute the LTR instruction to load the task register with
|
||||
> a segment selector to the initial protected-mode task or to
|
||||
> a writable area of memory that can be used to store TSS
|
||||
> information on a task switch.
|
||||
*
|
||||
* If you insist ...
|
||||
*/
|
||||
//mov $0x28, %ax
|
||||
//ltr %ax /* offset 0x28 into GDT, 32-bit TSS */
|
||||
|
||||
/*
|
||||
> 9. After entering protected mode, the segment registers continue
|
||||
> to hold the contents they had in real-address mode.
|
||||
> The JMP or CALL instruction in step 4 resets the CS register.
|
||||
> Perform one of the folowing operations to update the contents
|
||||
> of the remaining segment registers.
|
||||
>
|
||||
> - Reload segment registers DS, SS, ES, FS, and GS.
|
||||
> If the ES, FS, and/or GS registers are not going to be used,
|
||||
> load them with a null selector.
|
||||
> - [...]
|
||||
*/
|
||||
|
||||
mov $0x10, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %ss
|
||||
mov %ax, %es
|
||||
xor %eax, %eax
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
/*
|
||||
> 10. Execute the LIDT instruction to load the IDTR register
|
||||
> with the address and limit of the protected-mode IDT.
|
||||
*
|
||||
* Ok but before doing that, we need to stash away the old IDTR
|
||||
* so we can return to Real Mode and make BIOS calls later.
|
||||
*/
|
||||
sidt real_idtr
|
||||
lidt prot_idtr
|
||||
|
||||
/*
|
||||
> 11. Execute the STI instruction to enable maskable hardware
|
||||
> interrupts and perform the necessary hardware operation
|
||||
> to enable NMI interrupts.
|
||||
*
|
||||
* We don't really do interrupts in stage1, but we can enable NMI.
|
||||
*/
|
||||
in $0x70, %al
|
||||
and $0x7f, %al
|
||||
out %al, $0x70
|
||||
in $0x71, %al
|
||||
|
||||
jmp *%edx
|
||||
END real_to_prot
|
||||
|
||||
/*
|
||||
* Do the exact opposite of real_to_prot, i.e. transition from
|
||||
* Protected Mode to Real Mode (in order to make BIOS calls).
|
||||
* Again, this is quoted from the Intel SDM vol. 3A, but this time
|
||||
* from chapter 9.9.2.
|
||||
*
|
||||
* ATTENTION: This messes with the stack! Every call to prot_to_real
|
||||
* MUST be followed by a call to real_to_prot, and that call MUST
|
||||
* happen with the same stack pointer value.
|
||||
*
|
||||
> The processor switches from protected mode back to real-address
|
||||
> mode if software clears the PE bit in the CR0 register with a
|
||||
> MOV CR0 instruction. A procedure that re-enters real-address mode
|
||||
> should perform the following steps:
|
||||
*/
|
||||
GLOBL prot_to_real
|
||||
.code32
|
||||
|
||||
/*
|
||||
> 1. Disable interrupts. [...]
|
||||
*/
|
||||
cli
|
||||
in $0x70, %al
|
||||
or $0x80, %al
|
||||
out %al, $0x70
|
||||
in $0x71, %al
|
||||
|
||||
/* fuck with the stack */
|
||||
pop %edx /* %edx = return address */
|
||||
xor %eax, %eax
|
||||
mov real_ss, %ax
|
||||
shl $4, %eax
|
||||
sub %eax, %esp
|
||||
|
||||
/*
|
||||
> 2. If paging is enabled, [...]
|
||||
*
|
||||
* It is not.
|
||||
*
|
||||
> 3. Transfer program control to a readable segment that has a limit
|
||||
> of 64 KBytes (FFFFH). This operation loads the CS register with
|
||||
> the segment limit required in real-address mode.
|
||||
*/
|
||||
ljmp $0x18, $1f
|
||||
1: .code16
|
||||
|
||||
/*
|
||||
> 4. Load segment registers SS, DS, ES, FS, and GS with a selector
|
||||
> for a descriptor containing the following values, which are
|
||||
> appropriate for real-address mode:
|
||||
>
|
||||
> - [...]
|
||||
*
|
||||
* See offset 0x20 in _gdt.
|
||||
*/
|
||||
mov $0x20, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %ss
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
/*
|
||||
> 5. Execute the LIDT instruction to point to a real-address mode interrupt
|
||||
> table that is within the 1-MByte real-address mode address range.
|
||||
*/
|
||||
sidt prot_idtr
|
||||
lidt real_idtr
|
||||
|
||||
/*
|
||||
> 6. Clear the PE flag in the CR0 register to switch to real-address mode.
|
||||
*/
|
||||
mov %cr0, %eax
|
||||
and $0xfe, %al
|
||||
mov %eax, %cr0
|
||||
|
||||
/*
|
||||
> 7. Execute a far JMP instruction to jump to a real-address mode
|
||||
> program. This operation flushes the instruction queue and loads
|
||||
> the appropriate base-address value in the CS register.
|
||||
*/
|
||||
ljmp $0x00, $1f
|
||||
|
||||
/*
|
||||
> 8. Load the SS, DS, ES, FS, and GS registers as needed by the
|
||||
> real-address mode code. If any of the registers are not going
|
||||
> to be used in real-address mode, write 0s to them.
|
||||
*/
|
||||
1: mov real_ss, %ax
|
||||
mov %ax, %ss
|
||||
xor %ax, %ax
|
||||
mov %ax, %ds
|
||||
mov %ax, %es
|
||||
mov %ax, %fs
|
||||
mov %ax, %gs
|
||||
|
||||
/*
|
||||
> 9. Execute the STI instruction to enable maskable hardware
|
||||
> interrupts and perform the necessary hardware operation
|
||||
> to enable NMI interrupts.
|
||||
*/
|
||||
in $0x70, %al
|
||||
and $0x7f, %al
|
||||
out %al, $0x70
|
||||
in $0x71, %al
|
||||
|
||||
jmpw *%dx
|
||||
END prot_to_real
|
||||
|
||||
.data
|
||||
|
||||
.align 32
|
||||
LOCAL gdtr
|
||||
.word gdt_end - gdt - 1
|
||||
.long gdt
|
||||
END gdtr
|
||||
|
||||
/*
|
||||
* This figure is based on the Intel SDM vol. 3A, fig. 5-1.
|
||||
* Reordered so that the structure matches the definition below.
|
||||
*
|
||||
* | 0 1 | 2 3 |
|
||||
* +-------------------------------+-------------------------------+
|
||||
* | Segment Limit | Base Address |
|
||||
* +0 | | |
|
||||
* | 00:15 | 00:15 |
|
||||
* +---------------+-+---+-+-+-+-+-+-+-+-+-+-------+---------------+
|
||||
* | Base | | D |1|0|E|W|A| |B| |A| Limit | Base | < data
|
||||
* +4 | |P| P | Type |G|/|0|V| | |
|
||||
* | 16:23 | | L |1|1|C|R|A| |D| |L| 16:19 | 24:31 | < code
|
||||
* +---------------+-+---+-+-+-+-+-+-+-+-+-+-------+---------------+
|
||||
* | 4 | 5 | 6 | 7 |
|
||||
*
|
||||
* A Accessed E Expansion Direction (0=up)
|
||||
* AVL Available to System Programmers G Granularity (0=1B, 1=4K)
|
||||
* B Big (32-bit code) R Readable
|
||||
* C Conforming W Writable
|
||||
* D Default P Present
|
||||
* DPL Descriptor Privilege Level
|
||||
*
|
||||
* The GDT is a Very Normal data structure.
|
||||
*/
|
||||
.align 32
|
||||
LOCAL gdt
|
||||
/* 0x00 null descriptor */
|
||||
.word 0x0000, 0x0000
|
||||
.byte 0x00, 0x00, 0x00, 0x00
|
||||
|
||||
/* 0x08 32-bit code */
|
||||
/* | Limit = 0xfffff | Base = 0x00000000 | */
|
||||
.word 0xffff, 0x0000
|
||||
/* | |P 0 0 1|1 R |G D 0 /| 0xf | | */
|
||||
.byte 0x00, 0x9a, 0xcf, 0x00
|
||||
|
||||
/* 0x10 32-bit data */
|
||||
/* | Limit = 0xfffff | Base = 0x00000000 | */
|
||||
.word 0xffff, 0x0000
|
||||
/* | |P 0 0 1|0 W |G B 0 /| 0xf | | */
|
||||
.byte 0x00, 0x92, 0xcf, 0x00
|
||||
|
||||
/* 0x18 16-bit code */
|
||||
/* | Limit = 0x0ffff | Base = 0x00000000 | */
|
||||
.word 0xffff, 0x0000
|
||||
/* | |P 0 0 1|1 C R | 0 /| 0x0 | | */
|
||||
.byte 0x00, 0x9e, 0x00, 0x00
|
||||
|
||||
/* 0x20 16-bit data */
|
||||
/* | Limit = 0x0ffff | Base = 0x00000000 | */
|
||||
.word 0xffff, 0x0000
|
||||
/* | |P 0 0 1|0 W | 0 /| 0x0 | | */
|
||||
.byte 0x00, 0x92, 0x00, 0x00
|
||||
gdt_end:
|
||||
END gdt
|
||||
|
||||
.section .bss
|
||||
|
||||
/* dummy IDT Register value (0 entries) */
|
||||
LOCAL prot_idtr
|
||||
.word 0
|
||||
.long 0
|
||||
END prot_idtr
|
||||
|
||||
/* this is where we store the Real Mode IDT for later BIOS calls */
|
||||
LOCAL real_idtr
|
||||
.word 0
|
||||
.long 0
|
||||
END real_idtr
|
||||
|
||||
/* stores whether real mode sp was aligned */
|
||||
LOCAL real_sp_offset
|
||||
.word 0
|
||||
END real_sp_offset
|
||||
|
||||
LOCAL real_ss
|
||||
.word 0
|
||||
END real_ss
|
Loading…
Reference in New Issue