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]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
opt-level = "s"
|
opt-level = 0
|
||||||
lto = false
|
lto = false
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
debug = true
|
debug = true
|
||||||
|
|
|
@ -77,14 +77,18 @@ GLOBL _boot
|
||||||
a20_enabled:
|
a20_enabled:
|
||||||
/* int13/00 RESET DISK SYSTEM */
|
/* int13/00 RESET DISK SYSTEM */
|
||||||
mov $0, %ah
|
mov $0, %ah
|
||||||
|
mov boot_drive, %dl
|
||||||
int $0x13
|
int $0x13
|
||||||
|
|
||||||
/* make absolutely sure that %sp is aligned */
|
/* make absolutely sure that %sp is aligned */
|
||||||
and $0xfffc, %sp
|
and $0xfffc, %esp
|
||||||
|
|
||||||
/* make real_to_prot return directly to rust */
|
call real_to_prot
|
||||||
pushw $main
|
.code32
|
||||||
jmp real_to_prot
|
/* initialize an empty stack frame */
|
||||||
|
xor %ebp, %ebp
|
||||||
|
jmp main
|
||||||
|
.code16
|
||||||
|
|
||||||
err_no_a20:
|
err_no_a20:
|
||||||
mov $msg_err_no_a20, %ax
|
mov $msg_err_no_a20, %ax
|
||||||
|
|
|
@ -7,12 +7,17 @@
|
||||||
* goes back to Protected Mode again. Furthermore, the interrupt
|
* goes back to Protected Mode again. Furthermore, the interrupt
|
||||||
* number is written ahead of time by the callee.
|
* number is written ahead of time by the callee.
|
||||||
*/
|
*/
|
||||||
|
/* fn __do_bios_int(regs: &mut BiosIntRegs) -> u8 */
|
||||||
GLOBL __do_bios_int
|
GLOBL __do_bios_int
|
||||||
push %ebp /* '1 v */
|
pushal // '1 v
|
||||||
mov %esp, %ebp
|
|
||||||
pushal /* '2 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 (%ebp), %ax
|
||||||
mov 2(%ebp), %cx
|
mov 2(%ebp), %cx
|
||||||
mov 4(%ebp), %dx
|
mov 4(%ebp), %dx
|
||||||
|
@ -20,30 +25,32 @@ GLOBL __do_bios_int
|
||||||
mov 8(%ebp), %si
|
mov 8(%ebp), %si
|
||||||
mov 10(%ebp), %di
|
mov 10(%ebp), %di
|
||||||
|
|
||||||
push %ebp /* '3 v */
|
push %ebp // '2 v
|
||||||
|
|
||||||
push %eax /* '4 v */
|
push %eax // '3 v
|
||||||
push %edx /* '5 v */
|
push %edx // '4 v
|
||||||
call prot_to_real
|
call prot_to_real
|
||||||
.code16
|
.code16
|
||||||
pop %edx /* '5 ^ */
|
pop %edx // '4 ^
|
||||||
pop %eax /* '4 ^ */
|
pop %eax // '3 ^
|
||||||
|
|
||||||
/* call that polymorphism! */
|
/* call that polymorphism! */
|
||||||
.byte 0xcd /* opcode for `int imm8` */
|
.byte 0xcd /* opcode for `int imm8` */
|
||||||
GLOBL __bios_int_number, object
|
GLOBL __bios_int_number, object
|
||||||
.byte 0x18 /* this is the imm8 */
|
.byte 0x18 /* this is the imm8 (overwritten by caller) */
|
||||||
|
|
||||||
push %eax /* '4 v */
|
/* be careful not to touch EFLAGS now because that's our return value */
|
||||||
push %edx /* '5 v */
|
|
||||||
pushfl /* '6 v */
|
push %eax // '3 v
|
||||||
|
push %edx // '4 v
|
||||||
|
pushfl // '5 v
|
||||||
call real_to_prot
|
call real_to_prot
|
||||||
.code32
|
.code32
|
||||||
popfl /* '6 ^ */
|
popfl // '5 ^
|
||||||
pop %edx /* '5 ^ */
|
pop %edx // '4 ^
|
||||||
pop %eax /* '4 ^ */
|
pop %eax // '3 ^
|
||||||
|
|
||||||
pop %ebp /* '3 ^ */
|
pop %ebp // '2 ^
|
||||||
|
|
||||||
mov %ax, (%ebp)
|
mov %ax, (%ebp)
|
||||||
mov %cx, 2(%ebp)
|
mov %cx, 2(%ebp)
|
||||||
|
@ -51,11 +58,10 @@ GLOBL __bios_int_number, object
|
||||||
mov %bx, 6(%ebp)
|
mov %bx, 6(%ebp)
|
||||||
mov %si, 8(%ebp)
|
mov %si, 8(%ebp)
|
||||||
mov %di, 10(%ebp)
|
mov %di, 10(%ebp)
|
||||||
mov %al, 12(%ebp)
|
|
||||||
|
|
||||||
popal /* '2 ^ */
|
popal // '1 ^
|
||||||
setc %al
|
setc %al
|
||||||
|
/* this is not really required, but just in case */
|
||||||
movzbl %al, %eax
|
movzbl %al, %eax
|
||||||
pop %ebx /* '1 ^ */
|
|
||||||
ret
|
ret
|
||||||
END __do_bios_int
|
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 {
|
macro_rules! include_asm {
|
||||||
($name:literal) => {
|
($name:literal) => {
|
||||||
::core::arch::global_asm!(
|
::core::arch::global_asm!(
|
||||||
::core::concat!(".file \"", $name, "\""), // for better debugging
|
::core::concat!(".file \"", $name, "\""),
|
||||||
::core::include_str!($name),
|
::core::include_str!($name),
|
||||||
::core::concat!(".file \"", ::core::file!(), "\""),
|
::core::concat!(".file \"", ::core::file!(), "\""),
|
||||||
options(raw, att_syntax),
|
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");
|
include_asm!("common.s");
|
||||||
|
|
||||||
// stage1 header containing its magic, size, checksum, and entry point
|
// stage1 header containing its magic, size, checksum, and entry point
|
||||||
|
@ -20,15 +24,17 @@ extern "C" {
|
||||||
pub static _stage1_csum: u32;
|
pub static _stage1_csum: u32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// main bootstrap routine that calls the rust entry point
|
||||||
include_asm!("boot.s");
|
include_asm!("boot.s");
|
||||||
|
|
||||||
|
// switch between real and protected mode
|
||||||
include_asm!("pmode.s");
|
include_asm!("pmode.s");
|
||||||
|
|
||||||
|
// do bios interrupts from protected mode
|
||||||
include_asm!("int.s");
|
include_asm!("int.s");
|
||||||
extern "C" {
|
extern "C" {
|
||||||
/// Temporarily return to Real Mode and do a BIOS interrupt with the
|
/// Temporarily return to Real Mode and do a BIOS interrupt with the
|
||||||
/// specified register values. Returns the value of the carry flag
|
/// specified register values. Returns the value of the carry flag.
|
||||||
/// (`true` if CF=1, `false` if CF=0).
|
|
||||||
///
|
///
|
||||||
/// ## Safety
|
/// ## Safety
|
||||||
///
|
///
|
||||||
|
@ -62,9 +68,13 @@ pub type BiosResult = Result<BiosIntRegs, BiosIntRegs>;
|
||||||
/// clear after the interrupt, and `Err` if it was set. In either
|
/// clear after the interrupt, and `Err` if it was set. In either
|
||||||
/// case, the wrapped value contains the state of the registers after
|
/// case, the wrapped value contains the state of the registers after
|
||||||
/// the interrupt.
|
/// 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 {
|
pub unsafe fn do_bios_int(number: u8, mut regs: BiosIntRegs) -> BiosResult {
|
||||||
// this might be mildly unsafe
|
// this might be mildly unsafe
|
||||||
__bios_int_number = number;
|
write_volatile(&mut __bios_int_number, number);
|
||||||
let cf = __do_bios_int(&mut regs);
|
let cf = __do_bios_int(&mut regs);
|
||||||
if cf == 0 {
|
if cf == 0 {
|
||||||
Ok(regs)
|
Ok(regs)
|
||||||
|
|
|
@ -6,13 +6,17 @@
|
||||||
* Intel(R) 64 and IA-32 Architectures Software Developer's Manual
|
* Intel(R) 64 and IA-32 Architectures Software Developer's Manual
|
||||||
* vol. 3A, chapter 9.9.1. Lines prefixed with '>' are direct quotes.
|
* vol. 3A, chapter 9.9.1. Lines prefixed with '>' are direct quotes.
|
||||||
* Annotations within those lines are enclosed in [square brackets].
|
* 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
|
* ATTENTION: This messes with the stack! Before calling this function,
|
||||||
* MUST ensure that the stack pointer is aligned to 4 bytes (that is,
|
* you MUST ensure that %sp is aligned to 4 bytes. The stack pointer
|
||||||
* *before* the call to this subroutine pushes the return address).
|
* 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
|
> Intel 64 and IA-32 processors have slightly different requirements
|
||||||
> for switching to protected mode. To ensure upwards and downwards
|
> 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.)
|
> jump or call to the next instruction in the instruction stram.)
|
||||||
*/
|
*/
|
||||||
ljmpl $0x08, $1f
|
ljmpl $0x08, $1f
|
||||||
|
1: .code32
|
||||||
.code32
|
|
||||||
|
|
||||||
/* recompute the (now linear) stack address */
|
/* recompute the (now linear) stack address */
|
||||||
1: xor %eax, %eax
|
xor %eax, %eax
|
||||||
mov %ss, %ax
|
mov %ss, %ax
|
||||||
shl $4, %eax
|
shl $4, %eax
|
||||||
add %eax, %esp
|
add %eax, %esp
|
||||||
|
@ -87,20 +90,11 @@ GLOBL real_to_prot
|
||||||
> 7. If a local descriptor table is going to be used [...]
|
> 7. If a local descriptor table is going to be used [...]
|
||||||
*
|
*
|
||||||
* No.
|
* 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 ...
|
> 8. Execute the LTR instruction [...]
|
||||||
*/
|
*
|
||||||
//mov $0x28, %ax
|
* We don't do tasks here
|
||||||
//ltr %ax /* offset 0x28 into GDT, 32-bit TSS */
|
*
|
||||||
|
|
||||||
/*
|
|
||||||
> 9. After entering protected mode, the segment registers continue
|
> 9. After entering protected mode, the segment registers continue
|
||||||
> to hold the contents they had in real-address mode.
|
> to hold the contents they had in real-address mode.
|
||||||
> The JMP or CALL instruction in step 4 resets the CS register.
|
> The JMP or CALL instruction in step 4 resets the CS register.
|
||||||
|
@ -252,8 +246,9 @@ GLOBL prot_to_real
|
||||||
out %al, $0x70
|
out %al, $0x70
|
||||||
in $0x71, %al
|
in $0x71, %al
|
||||||
|
|
||||||
jmpw *%dx
|
jmp *%dx
|
||||||
END prot_to_real
|
END prot_to_real
|
||||||
|
.code32
|
||||||
|
|
||||||
.data
|
.data
|
||||||
|
|
||||||
|
@ -265,7 +260,8 @@ END gdtr
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This figure is based on the Intel SDM vol. 3A, fig. 5-1.
|
* 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 |
|
* | 0 1 | 2 3 |
|
||||||
* +-------------------------------+-------------------------------+
|
* +-------------------------------+-------------------------------+
|
||||||
|
@ -322,23 +318,18 @@ END gdt
|
||||||
|
|
||||||
.section .bss
|
.section .bss
|
||||||
|
|
||||||
/* dummy IDT Register value (0 entries) */
|
/* dummy IDT Register value (0 entries) */
|
||||||
LOCAL prot_idtr
|
LOCAL prot_idtr
|
||||||
.word 0
|
.word 0
|
||||||
.long 0
|
.long 0
|
||||||
END prot_idtr
|
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
|
LOCAL real_idtr
|
||||||
.word 0
|
.word 0
|
||||||
.long 0
|
.long 0
|
||||||
END real_idtr
|
END real_idtr
|
||||||
|
|
||||||
/* stores whether real mode sp was aligned */
|
|
||||||
LOCAL real_sp_offset
|
|
||||||
.word 0
|
|
||||||
END real_sp_offset
|
|
||||||
|
|
||||||
LOCAL real_ss
|
LOCAL real_ss
|
||||||
.word 0
|
.word 0
|
||||||
END real_ss
|
END real_ss
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
use crate::asm::{do_bios_int, BiosIntRegs};
|
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
use core::panic::PanicInfo;
|
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]
|
#[no_mangle]
|
||||||
pub extern "C" fn main() -> ! {
|
pub extern "C" fn main() -> ! {
|
||||||
|
@ -25,6 +27,10 @@ pub fn rust_panic(_info: &PanicInfo) -> ! {
|
||||||
|
|
||||||
pub fn print(s: &str) {
|
pub fn print(s: &str) {
|
||||||
for b in s.bytes() {
|
for b in s.bytes() {
|
||||||
|
if b == b'\0' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
let regs = BiosIntRegs {
|
let regs = BiosIntRegs {
|
||||||
ax: 0x0e00 | b as u16,
|
ax: 0x0e00 | b as u16,
|
||||||
bx: 0x0001,
|
bx: 0x0001,
|
||||||
|
|
Loading…
Reference in a new issue