stage1: fix more mode switching bugs
BIOS calls from Rust should work now.
This commit is contained in:
parent
2ec426677f
commit
bbe3375b8a
6 changed files with 79 additions and 62 deletions
|
@ -15,7 +15,7 @@ repository = "https://git.bsd.gay/fef/bussy"
|
|||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
opt-level = "s"
|
||||
opt-level = 0
|
||||
lto = false
|
||||
codegen-units = 1
|
||||
debug = true
|
||||
|
|
|
@ -77,14 +77,18 @@ GLOBL _boot
|
|||
a20_enabled:
|
||||
/* int13/00 RESET DISK SYSTEM */
|
||||
mov $0, %ah
|
||||
mov boot_drive, %dl
|
||||
int $0x13
|
||||
|
||||
/* make absolutely sure that %sp is aligned */
|
||||
and $0xfffc, %sp
|
||||
and $0xfffc, %esp
|
||||
|
||||
/* make real_to_prot return directly to rust */
|
||||
pushw $main
|
||||
jmp real_to_prot
|
||||
call real_to_prot
|
||||
.code32
|
||||
/* initialize an empty stack frame */
|
||||
xor %ebp, %ebp
|
||||
jmp main
|
||||
.code16
|
||||
|
||||
err_no_a20:
|
||||
mov $msg_err_no_a20, %ax
|
||||
|
|
|
@ -7,12 +7,17 @@
|
|||
* goes back to Protected Mode again. Furthermore, the interrupt
|
||||
* number is written ahead of time by the callee.
|
||||
*/
|
||||
/* fn __do_bios_int(regs: &mut BiosIntRegs) -> u8 */
|
||||
GLOBL __do_bios_int
|
||||
push %ebp /* '1 v */
|
||||
mov %esp, %ebp
|
||||
pushal /* '2 v */
|
||||
pushal // '1 v
|
||||
|
||||
mov 8(%ebp), %ebp
|
||||
/*
|
||||
* Move the `regs` parameter into %ebp. `call` pushes one longword
|
||||
* (4 bytes) for the return address, then the `pushal` above pushes
|
||||
* 8 longwords (32 bytes), so the pointer is at offset 36.
|
||||
*/
|
||||
mov 36(%esp), %ebp
|
||||
/* now load all registers with their desired values */
|
||||
mov (%ebp), %ax
|
||||
mov 2(%ebp), %cx
|
||||
mov 4(%ebp), %dx
|
||||
|
@ -20,30 +25,32 @@ GLOBL __do_bios_int
|
|||
mov 8(%ebp), %si
|
||||
mov 10(%ebp), %di
|
||||
|
||||
push %ebp /* '3 v */
|
||||
push %ebp // '2 v
|
||||
|
||||
push %eax /* '4 v */
|
||||
push %edx /* '5 v */
|
||||
push %eax // '3 v
|
||||
push %edx // '4 v
|
||||
call prot_to_real
|
||||
.code16
|
||||
pop %edx /* '5 ^ */
|
||||
pop %eax /* '4 ^ */
|
||||
pop %edx // '4 ^
|
||||
pop %eax // '3 ^
|
||||
|
||||
/* call that polymorphism! */
|
||||
.byte 0xcd /* opcode for `int imm8` */
|
||||
GLOBL __bios_int_number, object
|
||||
.byte 0x18 /* this is the imm8 */
|
||||
.byte 0x18 /* this is the imm8 (overwritten by caller) */
|
||||
|
||||
push %eax /* '4 v */
|
||||
push %edx /* '5 v */
|
||||
pushfl /* '6 v */
|
||||
/* be careful not to touch EFLAGS now because that's our return value */
|
||||
|
||||
push %eax // '3 v
|
||||
push %edx // '4 v
|
||||
pushfl // '5 v
|
||||
call real_to_prot
|
||||
.code32
|
||||
popfl /* '6 ^ */
|
||||
pop %edx /* '5 ^ */
|
||||
pop %eax /* '4 ^ */
|
||||
popfl // '5 ^
|
||||
pop %edx // '4 ^
|
||||
pop %eax // '3 ^
|
||||
|
||||
pop %ebp /* '3 ^ */
|
||||
pop %ebp // '2 ^
|
||||
|
||||
mov %ax, (%ebp)
|
||||
mov %cx, 2(%ebp)
|
||||
|
@ -51,11 +58,10 @@ GLOBL __bios_int_number, object
|
|||
mov %bx, 6(%ebp)
|
||||
mov %si, 8(%ebp)
|
||||
mov %di, 10(%ebp)
|
||||
mov %al, 12(%ebp)
|
||||
|
||||
popal /* '2 ^ */
|
||||
popal // '1 ^
|
||||
setc %al
|
||||
/* this is not really required, but just in case */
|
||||
movzbl %al, %eax
|
||||
pop %ebx /* '1 ^ */
|
||||
ret
|
||||
END __do_bios_int
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
use core::ptr::write_volatile;
|
||||
|
||||
// XXX This doesn't emit proper debug symbols for assembly sources
|
||||
|
||||
macro_rules! include_asm {
|
||||
($name:literal) => {
|
||||
::core::arch::global_asm!(
|
||||
::core::concat!(".file \"", $name, "\""), // for better debugging
|
||||
::core::concat!(".file \"", $name, "\""),
|
||||
::core::include_str!($name),
|
||||
::core::concat!(".file \"", ::core::file!(), "\""),
|
||||
options(raw, att_syntax),
|
||||
|
@ -9,7 +13,7 @@ macro_rules! include_asm {
|
|||
};
|
||||
}
|
||||
|
||||
// common macros for the other assembly files, keep at the beginning
|
||||
// common macros for the other assembly files, keep this at the beginning
|
||||
include_asm!("common.s");
|
||||
|
||||
// stage1 header containing its magic, size, checksum, and entry point
|
||||
|
@ -20,15 +24,17 @@ extern "C" {
|
|||
pub static _stage1_csum: u32;
|
||||
}
|
||||
|
||||
// main bootstrap routine that calls the rust entry point
|
||||
include_asm!("boot.s");
|
||||
|
||||
// switch between real and protected mode
|
||||
include_asm!("pmode.s");
|
||||
|
||||
// do bios interrupts from protected mode
|
||||
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).
|
||||
/// specified register values. Returns the value of the carry flag.
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
|
@ -62,9 +68,13 @@ pub type BiosResult = Result<BiosIntRegs, BiosIntRegs>;
|
|||
/// 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.
|
||||
///
|
||||
/// ## Safety
|
||||
///
|
||||
/// This is horrendously unsafe and there is nothing you can do about it.
|
||||
pub unsafe fn do_bios_int(number: u8, mut regs: BiosIntRegs) -> BiosResult {
|
||||
// this might be mildly unsafe
|
||||
__bios_int_number = number;
|
||||
write_volatile(&mut __bios_int_number, number);
|
||||
let cf = __do_bios_int(&mut regs);
|
||||
if cf == 0 {
|
||||
Ok(regs)
|
||||
|
|
|
@ -6,13 +6,17 @@
|
|||
* 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 messes with the stack! Before calling this function,
|
||||
* you MUST ensure that %sp is aligned to 4 bytes. The stack pointer
|
||||
* will be automatically translated to its linear address equivalent
|
||||
* (i.e. %esp = %ss * 0x10 + %sp), however, it DOES NOT touch %ebp.
|
||||
*
|
||||
* ATTENTION: This trashes registers %ax and %dx, as well as EFLAGS.
|
||||
* ATTENTION: You MUST call this from real mode (.code16), and any code
|
||||
* following the call MUST be for 32-bit protected mode (.code32).
|
||||
*
|
||||
* ATTENTION: This trashes %eax, %edx, eflags, and ALL segment registers.
|
||||
* Furthermore, it clears the IF flag (i.e. it disables interrupts).
|
||||
*
|
||||
> Intel 64 and IA-32 processors have slightly different requirements
|
||||
> for switching to protected mode. To ensure upwards and downwards
|
||||
|
@ -64,11 +68,10 @@ GLOBL real_to_prot
|
|||
> jump or call to the next instruction in the instruction stram.)
|
||||
*/
|
||||
ljmpl $0x08, $1f
|
||||
|
||||
.code32
|
||||
1: .code32
|
||||
|
||||
/* recompute the (now linear) stack address */
|
||||
1: xor %eax, %eax
|
||||
xor %eax, %eax
|
||||
mov %ss, %ax
|
||||
shl $4, %eax
|
||||
add %eax, %esp
|
||||
|
@ -87,20 +90,11 @@ GLOBL real_to_prot
|
|||
> 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 */
|
||||
|
||||
/*
|
||||
> 8. Execute the LTR instruction [...]
|
||||
*
|
||||
* We don't do tasks here
|
||||
*
|
||||
> 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.
|
||||
|
@ -252,8 +246,9 @@ GLOBL prot_to_real
|
|||
out %al, $0x70
|
||||
in $0x71, %al
|
||||
|
||||
jmpw *%dx
|
||||
jmp *%dx
|
||||
END prot_to_real
|
||||
.code32
|
||||
|
||||
.data
|
||||
|
||||
|
@ -265,7 +260,8 @@ END gdtr
|
|||
|
||||
/*
|
||||
* This figure is based on the Intel SDM vol. 3A, fig. 5-1.
|
||||
* Reordered so that the structure matches the definition below.
|
||||
* Reordered so that the structure matches the definition below, i.e.
|
||||
* little endian byte order (bits WITHIN the bytes are big endian).
|
||||
*
|
||||
* | 0 1 | 2 3 |
|
||||
* +-------------------------------+-------------------------------+
|
||||
|
@ -322,23 +318,18 @@ END gdt
|
|||
|
||||
.section .bss
|
||||
|
||||
/* dummy IDT Register value (0 entries) */
|
||||
/* 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 */
|
||||
/* 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,11 +1,13 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use crate::asm::{do_bios_int, BiosIntRegs};
|
||||
use core::arch::asm;
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
mod asm;
|
||||
/// Collection of all assembly sources and symbols they expose
|
||||
pub mod asm;
|
||||
|
||||
use asm::{do_bios_int, BiosIntRegs};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn main() -> ! {
|
||||
|
@ -25,6 +27,10 @@ pub fn rust_panic(_info: &PanicInfo) -> ! {
|
|||
|
||||
pub fn print(s: &str) {
|
||||
for b in s.bytes() {
|
||||
if b == b'\0' {
|
||||
break;
|
||||
}
|
||||
|
||||
let regs = BiosIntRegs {
|
||||
ax: 0x0e00 | b as u16,
|
||||
bx: 0x0001,
|
||||
|
|
Loading…
Reference in a new issue