diff --git a/Cargo.toml b/Cargo.toml index aa258e5..8e5bce8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ repository = "https://git.bsd.gay/fef/bussy" [profile.dev] panic = "abort" -opt-level = 0 +opt-level = "s" lto = false codegen-units = 1 debug = true diff --git a/stage1/src/asm/disk.s b/stage1/src/asm/disk.s new file mode 100644 index 0000000..eaf7a1e --- /dev/null +++ b/stage1/src/asm/disk.s @@ -0,0 +1,19 @@ + + .section .bss + + .align 8 +GLOBL dap + .skip 0x10 +END dap + + .align 8 +GLOBL drive_parms + .skip 0x1a +END drive_parms + /* + * According to the GRUB: + * + * the ThinkPad X20 BIOS ignores the `size` member and writes + * garbage data out of bounds on the drive parameter structure + */ + .skip 0x10 diff --git a/stage1/src/asm/mod.rs b/stage1/src/asm/mod.rs index 526f75d..cfdc498 100644 --- a/stage1/src/asm/mod.rs +++ b/stage1/src/asm/mod.rs @@ -1,4 +1,4 @@ -use core::ptr::write_volatile; +use core::ptr::{read, write_volatile}; // XXX This doesn't emit proper debug symbols for assembly sources @@ -21,6 +21,18 @@ include_asm!("header.s"); // main bootstrap routine that calls the rust entry point include_asm!("boot.s"); +extern "C" { + static boot_drive: u8; + static boot_lba: u64; +} + +pub fn get_boot_drive() -> u8 { + unsafe { read(&boot_drive) } +} + +pub fn get_boot_lba() -> u64 { + unsafe { read(&boot_lba) } +} // switch between real and protected mode include_asm!("pmode.s"); @@ -78,3 +90,12 @@ pub unsafe fn do_bios_int(number: u8, mut regs: BiosIntRegs) -> BiosResult { Err(regs) } } + +// Rust doesn't support aligning global symbols directly; it has to be +// defined in the type. However, we can't put align(8) on the type +// either because that would destroy the layout of the data structure. +include_asm!("disk.s"); +extern "C" { + pub static mut drive_parms: crate::disk::DriveParams; + pub static mut dap: crate::disk::DiskAddressPacket; +} diff --git a/stage1/src/disk.rs b/stage1/src/disk.rs new file mode 100644 index 0000000..4b56281 --- /dev/null +++ b/stage1/src/disk.rs @@ -0,0 +1,107 @@ +use core::mem::size_of; +use core::ptr::{read_volatile, write_volatile}; +use core::sync::atomic::{compiler_fence, Ordering}; + +use crate::asm::{dap, do_bios_int, drive_parms, BiosIntRegs}; + +#[repr(C, packed)] +pub struct DriveParams { + size: u16, + flags: u16, + cylinders: u32, + heads: u32, + sectors_per_track: u32, + total_sectors: u64, + sector_size: u16, +} + +pub fn get_sector_size(drive_number: u8) -> Option { + // rustc whines about unaligned members due to repr(packed) + // but we know FOR SURE that everything is aligned properly + unsafe { + let drive_params_ptr = (&mut drive_parms) as *mut DriveParams; + write_volatile( + drive_params_ptr as *mut u16, + size_of::() as u16, + ); + + let regs = do_bios_int( + 0x13, + BiosIntRegs { + eax: 0x4800, + edx: drive_number as u32, + esi: drive_params_ptr as u32, + ..Default::default() + }, + ) + .ok()?; + + if regs.eax & 0xff00 == 0 { + Some(read_volatile( + (drive_params_ptr as *const u8).add(16) as *const u16 + )) + } else { + None + } + } +} + +#[repr(C, packed)] +pub struct DiskAddressPacket { + size: u8, + _rsvd: u8, + sector_count: u16, + buffer_offset: u16, + buffer_segment: u16, + lba: u64, +} + +/// Read one logical sector from a disk using BIOS int13/42. +/// +/// ## Safety +/// +/// Since this uses a BIOS call, there is no real safety here. +/// The best approximation to safety requires the following: +/// +/// - `dest` MUST have capacity for at least the size of a sector. +/// - If the sector size is a power of 2, `dest` MUST be aligned to a sector size. +/// - If the sector size is not a power of 2, `dest` MUST be aligned to the next +/// power of 2 greater than the sector size. +/// - `dest` SHOULD be aligned to at least 64 K. +/// - `dest` MUST be in low memory (i.e. below 1 M). +/// - `drive_number` MUST be a valid BIOS drive number. +/// - `lba` SHOULD fit into 48 bits. +pub unsafe fn read_lba(dest: *mut u8, drive_number: u8, lba: u64) -> Result<(), u8> { + debug_assert!((dest as usize) >= (1 << 16)); + debug_assert!((dest as usize) < (1 << 20)); + + let dap_ptr = (&mut dap) as *mut DiskAddressPacket; + dap = DiskAddressPacket { + size: size_of::() as u8, + _rsvd: 0, + sector_count: 1, + buffer_offset: ((dest as usize) & 0xf) as u16, + buffer_segment: ((dest as usize) >> 4) as u16, + lba, + }; + + compiler_fence(Ordering::SeqCst); + + let ah = do_bios_int( + 0x13, + BiosIntRegs { + eax: 0x4200, + edx: drive_number as u32, + esi: dap_ptr as u32, + ..Default::default() + }, + ) + .map(|regs| (regs.eax >> 8) as u8) + .map_err(|regs| (regs.eax >> 8) as u8)?; + + if ah == 0 { + Ok(()) + } else { + Err(ah) + } +} diff --git a/stage1/src/main.rs b/stage1/src/main.rs index eaf874f..1840d91 100644 --- a/stage1/src/main.rs +++ b/stage1/src/main.rs @@ -1,11 +1,15 @@ #![no_std] #![no_main] -use core::arch::asm; use core::panic::PanicInfo; /// Collection of all assembly sources and symbols they expose -pub mod asm; +mod asm; +/// BIOS disk access utilities +mod disk; + +use crate::disk::{get_sector_size, read_lba}; +use asm::{do_bios_int, get_boot_drive, get_boot_lba, BiosIntRegs}; use asm::{do_bios_int, BiosIntRegs}; @@ -18,9 +22,10 @@ pub extern "C" fn main() -> ! { #[panic_handler] pub fn rust_panic(_info: &PanicInfo) -> ! { + print("stage1 panicked, system halted.\r\n"); loop { unsafe { - asm!("cli", "hlt"); + core::arch::asm!("cli", "hlt"); } } } @@ -32,8 +37,8 @@ pub fn print(s: &str) { } let regs = BiosIntRegs { - ax: 0x0e00 | b as u16, - bx: 0x0001, + eax: 0x0e00 | b as u32, + ebx: 0x0001, ..Default::default() }; unsafe { diff --git a/stage1/stage1.ld b/stage1/stage1.ld index 1d7251f..bec7b2d 100644 --- a/stage1/stage1.ld +++ b/stage1/stage1.ld @@ -1,6 +1,8 @@ OUTPUT_FORMAT(elf32-i386) ENTRY(_start) +SEGMENT_SIZE = 0x10000; + SECTIONS { . = 0x0500; @@ -29,4 +31,13 @@ SECTIONS { *(.bss .bss.*) _bss_end = .; } + + .iomem(NOLOAD) : ALIGN(SEGMENT_SIZE) { + _disk_buf = .; + . += SEGMENT_SIZE; + _disk_buf_end = .; + _mem_map = .; + . += SEGMENT_SIZE; + _mem_map_end = .; + } }