stage1: fix more mode switching bugs

BIOS calls from Rust should work now.
This commit is contained in:
anna 2023-05-30 00:35:53 +02:00
parent 2ec426677f
commit bbe3375b8a
Signed by: fef
GPG key ID: 2585C2DC6D79B485
6 changed files with 79 additions and 62 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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