Compare commits

...

2 Commits

Author SHA1 Message Date
anna 2ec426677f
stage1: add bios call hook 12 months ago
anna ce19f70440
stage1: add bootstrap code 12 months ago

@ -16,7 +16,7 @@ repository = "https://git.bsd.gay/fef/bussy"
[profile.dev]
panic = "abort"
opt-level = "s"
lto = true
lto = false
codegen-units = 1
debug = true
overflow-checks = true
@ -24,7 +24,7 @@ overflow-checks = true
[profile.release]
panic = "abort"
opt-level = "s"
lto = true
lto = false
codegen-units = 1
debug = true
overflow-checks = false

@ -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

@ -53,5 +53,5 @@ END _stage1_csum
* stage1 entry point. stage0 jumps here.
*/
GLOBL _start
1: jmp 1b
jmp _boot
END _start

@ -0,0 +1,61 @@
.text
.code32
/*
* This has got to be one of the most cursed routines within stage1.
* Temporarily returns to Real Mode, does a BIOS interrupt, and then
* goes back to Protected Mode again. Furthermore, the interrupt
* number is written ahead of time by the callee.
*/
GLOBL __do_bios_int
push %ebp /* '1 v */
mov %esp, %ebp
pushal /* '2 v */
mov 8(%ebp), %ebp
mov (%ebp), %ax
mov 2(%ebp), %cx
mov 4(%ebp), %dx
mov 6(%ebp), %bx
mov 8(%ebp), %si
mov 10(%ebp), %di
push %ebp /* '3 v */
push %eax /* '4 v */
push %edx /* '5 v */
call prot_to_real
.code16
pop %edx /* '5 ^ */
pop %eax /* '4 ^ */
/* call that polymorphism! */
.byte 0xcd /* opcode for `int imm8` */
GLOBL __bios_int_number, object
.byte 0x18 /* this is the imm8 */
push %eax /* '4 v */
push %edx /* '5 v */
pushfl /* '6 v */
call real_to_prot
.code32
popfl /* '6 ^ */
pop %edx /* '5 ^ */
pop %eax /* '4 ^ */
pop %ebp /* '3 ^ */
mov %ax, (%ebp)
mov %cx, 2(%ebp)
mov %dx, 4(%ebp)
mov %bx, 6(%ebp)
mov %si, 8(%ebp)
mov %di, 10(%ebp)
mov %al, 12(%ebp)
popal /* '2 ^ */
setc %al
movzbl %al, %eax
pop %ebx /* '1 ^ */
ret
END __do_bios_int

@ -19,3 +19,56 @@ extern "C" {
pub static _stage1_len: u16;
pub static _stage1_csum: u32;
}
include_asm!("boot.s");
include_asm!("pmode.s");
include_asm!("int.s");
extern "C" {
/// Temporarily return to Real Mode and do a BIOS interrupt with the
/// specified register values. Returns the value of the carry flag
/// (`true` if CF=1, `false` if CF=0).
///
/// ## Safety
///
/// This is horrendously unsafe and there is nothing you can do about it.
/// Before calling, you MUST set [`__bios_int_number`] to the interrupt
/// number you wish to invoke. Cache shouldn't be an issue because going
/// from protected to real mode flushes the pipeline and prefetch cache
/// anyway, but don't quote me on that.
fn __do_bios_int(regs: &mut BiosIntRegs) -> u8;
/// This is located within [`__do_bios_int`] and writing to it modifies
/// the interrupt number that is invoked when calling the function.
static mut __bios_int_number: u8;
}
#[repr(C)]
#[derive(Copy, Clone, Default)]
pub struct BiosIntRegs {
pub ax: u16,
pub cx: u16,
pub dx: u16,
pub bx: u16,
pub si: u16,
pub di: u16,
}
pub type BiosResult = Result<BiosIntRegs, BiosIntRegs>;
/// Temporarily return to Real Mode and do a BIOS interrupt with the
/// specified register values. Returns `Ok` if the carry flag was
/// clear after the interrupt, and `Err` if it was set. In either
/// case, the wrapped value contains the state of the registers after
/// the interrupt.
pub unsafe fn do_bios_int(number: u8, mut regs: BiosIntRegs) -> BiosResult {
// this might be mildly unsafe
__bios_int_number = number;
let cf = __do_bios_int(&mut regs);
if cf == 0 {
Ok(regs)
} else {
Err(regs)
}
}

@ -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

@ -1,6 +1,7 @@
#![no_std]
#![no_main]
use crate::asm::{do_bios_int, BiosIntRegs};
use core::arch::asm;
use core::panic::PanicInfo;
@ -8,6 +9,8 @@ mod asm;
#[no_mangle]
pub extern "C" fn main() -> ! {
print("hi im gay uwu");
loop {}
}
@ -19,3 +22,16 @@ pub fn rust_panic(_info: &PanicInfo) -> ! {
}
}
}
pub fn print(s: &str) {
for b in s.bytes() {
let regs = BiosIntRegs {
ax: 0x0e00 | b as u16,
bx: 0x0001,
..Default::default()
};
unsafe {
let _ = do_bios_int(0x10, regs);
}
}
}

Loading…
Cancel
Save