diff --git a/boot0.s b/boot0.s index cce1163..2e1dd6c 100644 --- a/boot0.s +++ b/boot0.s @@ -3,7 +3,7 @@ ; -------------------------------------------------------------------- ; R | 0x000000 - 0x000400: real-mode interrupt vector table ; R | 0x000400 - 0x000500: bios data area -; U | 0x000500 - 0x004000: stack +; U | 0x000500 - 0x004000: main stack ; U | 0x004000 - 0x006a00: globals ; U | 0x006a00 - 0x007c00: memory map ; U | 0x007c00 - 0x007e00: boot sector diff --git a/boot1/asm/bios_call.s b/boot1/asm/bios_call.s new file mode 100644 index 0000000..cad000c --- /dev/null +++ b/boot1/asm/bios_call.s @@ -0,0 +1,169 @@ +%include "defines.s" + +extern gdt_flat_slice +extern gdt_flat +extern GDT_FLAT_IDX_CODE_32 +extern GDT_FLAT_IDX_CODE_16 +extern GDT_FLAT_IDX_DATA + + +section .text + +[bits 32] + +; FIXME: use a separate stack for real mode +bios_call: + .PARAMS_START equ 36 + .PARAM_BIOS_INTR_NUM equ .PARAMS_START + .PARAM_BIOS_INTR_ARGS_PTR equ .PARAMS_START + 4 + + pushad + mov ebp, esp + push ds + push es + push fs + push gs + push ss + + ; Push cr0, the GDT and the IDT to the stack so we can restore them once we're finished with real mode. + mov eax, cr0 + push eax + sub esp, 8 + sidt [esp] + sub esp, 8 + sgdt [esp] + + ; Load the address of the `int` instruction we want to run, and store it on the stack for later. + mov eax, [ebp + .PARAM_BIOS_INTR_NUM] + lea eax, [int_fns + (3 * eax)] + push eax + + ; Copy the BIOS call arguments provided by the caller onto the stack, for us to pop later. + sub esp, 28 + mov ecx, 28 + mov esi, [ebp + .PARAM_BIOS_INTR_ARGS_PTR] + mov edi, esp + rep movsb + + ; Switch to a 16-bit code segment, but remain in protected mode. + jmp GDT_FLAT_IDX_CODE_16:.protected_mode_16 + +[bits 16] +.protected_mode_16: + + ; Disable paging and protected mode. + mov eax, cr0 + and eax, 0x7ffffffe + mov cr0, eax + + jmp 0:.real_mode + +.real_mode: + + xor ax, ax + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + + ; Load the caller-provided register values off the stack. + pop eax + pop ebx + pop ecx + pop edx + pop esi + pop edi + pop ds + pop es + + ; Call the function which runs the correct `int` instruction. + push bp + mov bp, sp + call [bp + 2] + pop bp + + ; TODO: return resulting registers to the caller + + ; Reset the segment registers that were changed to the caller-provided values. + xor ax, ax + mov ds, ax + mov es, ax + + ; Reload the GDT in case the BIOS call changed it. + mov bx, sp + lgdt [bx] + add sp, 8 + + mov bx, sp + lidt [bx] + add sp, 8 + + ; Restore cr0, re-enabling protected mode. + pop eax + mov cr0, eax + + jmp GDT_FLAT_IDX_CODE_32:.protected_mode_32 + +[bits 32] +.protected_mode_32: + + pop ss + pop gs + pop fs + pop es + pop ds + popad + ret + +global bios_call + + +%macro int_fn 1 + int %1 + ret +%endmacro + + +int_fns: + int_fn 0x00 + int_fn 0x01 + int_fn 0x02 + int_fn 0x03 + int_fn 0x04 + int_fn 0x05 + int_fn 0x06 + int_fn 0x07 + int_fn 0x08 + int_fn 0x09 + int_fn 0x0a + int_fn 0x0b + int_fn 0x0c + int_fn 0x0d + int_fn 0x0e + int_fn 0x0f + int_fn 0x10 + int_fn 0x11 + int_fn 0x12 + int_fn 0x13 + int_fn 0x14 + int_fn 0x15 + int_fn 0x16 + int_fn 0x17 + int_fn 0x18 + int_fn 0x19 + int_fn 0x1a + int_fn 0x1b + int_fn 0x1c + int_fn 0x1d + int_fn 0x1e + int_fn 0x1f + + +section .rodata + +idt_real: + dw 0x3ff + dd 0 + + diff --git a/boot1/asm/gdt.s b/boot1/asm/gdt.s new file mode 100644 index 0000000..17f5f0a --- /dev/null +++ b/boot1/asm/gdt.s @@ -0,0 +1,95 @@ +section .rodata + +gdt_flat_slice: + dw GDT_FLAT_LEN + dd gdt_flat + +global gdt_flat_slice + +; Segment descriptor layout +; | Range (bits) | Field | +; |--------------|---------------| +; | 0-16 | limit | +; | 16-32 | base | +; | 32-40 | base cont. | +; | 40-48 | access | +; | 48-52 | limit cont. | +; | 52-56 | flags | +; | 56-64 | base cont. | +; +; Flags +; - 0: reserved +; - 1: long-mode code segment +; - 2: size +; - unset: 16-bit +; - set: 32-bit +; - 3: granularity +; - unset: limit is measured in bytes +; - set: limit is measured in 4KiB pages +; +; Access +; - 0: accessed +; - unset: CPU will set it when the segment is accessed +; - 1: readable / writable +; - data segments: is segment writable (data segments are always readable) +; - code segments: is segment readable (code segments are never writable) +; - 2: direction / conforming +; - data segments: whether segment grows down +; - code segments: whether this can be executed from a lower-privilege ring +; - 3: executable +; - unset: this is a data segment +; - set: this is a code segment +; - 4: descriptor type +; - unset: this is a task state segment +; - set: this is a data or code segment +; - 5-6: privilege level (ring number) +; - 7: present (must be set) +; +align 8 +gdt_flat: + ; First GDT entry must be 0. + dq 0 + +; 32-bit code segment. +; Bytes 0x0000 - 0xffff. +.segment_code_32: + db 0xff, 0xff, \ + 0x00, 0x00, \ + 0x00, \ + 10011011b, \ + 01000000b, \ + 0x00 + +; 16-bit code segment, to use if we want to switch back to real mode. +; Bytes 0x0000 - 0xffff. +.segment_code_16: + db 0xff, 0xff, \ + 0x00, 0x00, \ + 0x00, \ + 10011011b, \ + 00000000b, \ + 0x00 + +; Data segment. +; Pages 0x000000 - 0x0fffff, which covers the entire 32-bit address space (start of 0xfffff-th page +; is 0xfffff * 4096 = 0xfffff000, end of page exclusive is 0xfffff000 + 4096 = 0x100000000). +.segment_data: + db 0xff, 0xff, \ + 0x00, 0x00, \ + 0x00, \ + 10010011b, \ + 11001111b, \ + 0x00 + +global gdt_flat + +GDT_FLAT_LEN equ ($ - gdt_flat) + +GDT_FLAT_IDX_CODE_32 equ (gdt_flat.segment_code_32 - gdt_flat) +global GDT_FLAT_IDX_CODE_32 + +GDT_FLAT_IDX_CODE_16 equ (gdt_flat.segment_code_16 - gdt_flat) +global GDT_FLAT_IDX_CODE_16 + +GDT_FLAT_IDX_DATA equ (gdt_flat.segment_data - gdt_flat) +global GDT_FLAT_IDX_DATA diff --git a/boot1/asm/prelude.s b/boot1/asm/prelude.s index c4388d4..b1c10da 100644 --- a/boot1/asm/prelude.s +++ b/boot1/asm/prelude.s @@ -1,4 +1,5 @@ %include "defines.s" +%include "ps2.s" [bits 16] @@ -7,6 +8,12 @@ extern boot1_bin_sectors extern boot1_magic extern _start +extern gdt_flat_slice +extern gdt_flat +extern GDT_FLAT_IDX_CODE_32 +extern GDT_FLAT_IDX_CODE_16 +extern GDT_FLAT_IDX_DATA + section .prelude %macro copy_stack_var_to_globals 2 @@ -335,22 +342,22 @@ prelude_main: cli ; Load our flat-address-space GDT. - lgdt [flat_gdt_slice] + lgdt [gdt_flat_slice] ; Set the protected-mode bit in cr0. mov eax, cr0 or al, 0x01 mov cr0, eax - ; Long jump to set the code segment to flat_gdt.segment_code, and to clear the instruction + ; Long jump to set the code segment to gdt_flat.segment_code, and to clear the instruction ; pipeline. - jmp (flat_gdt.segment_code_32 - flat_gdt):.protected_mode + jmp GDT_FLAT_IDX_CODE_32:.protected_mode_32 [bits 32] -.protected_mode: +.protected_mode_32: - ; Set the data segments to flat_gdt.segment_data. - mov eax, (flat_gdt.segment_data - flat_gdt) + ; Set the data segments to gdt_flat.segment_data. + mov eax, GDT_FLAT_IDX_DATA mov ds, eax mov es, eax mov fs, eax @@ -363,87 +370,3 @@ prelude_main: jmp _start jmp panic_simple - - -section .data -flat_gdt_slice: - dw FLAT_GDT_LEN - dd flat_gdt - -; Segment descriptor layout -; | Range (bits) | Field | -; |--------------|---------------| -; | 0-16 | limit | -; | 16-32 | base | -; | 32-40 | base cont. | -; | 40-48 | access | -; | 48-52 | limit cont. | -; | 52-56 | flags | -; | 56-64 | base cont. | -; -; Flags -; - 0: reserved -; - 1: long-mode code segment -; - 2: size -; - unset: 16-bit -; - set: 32-bit -; - 3: granularity -; - unset: limit is measured in bytes -; - set: limit is measured in 4KiB pages -; -; Access -; - 0: accessed -; - unset: CPU will set it when the segment is accessed -; - 1: readable / writable -; - data segments: is segment writable (data segments are always readable) -; - code segments: is segment readable (code segments are never writable) -; - 2: direction / conforming -; - data segments: whether segment grows down -; - code segments: whether this can be executed from a lower-privilege ring -; - 3: executable -; - unset: this is a data segment -; - set: this is a code segment -; - 4: descriptor type -; - unset: this is a task state segment -; - set: this is a data or code segment -; - 5-6: privilege level (ring number) -; - 7: present (must be set) -; -align 8 -flat_gdt: -; First GDT entry must be 0. -.segment_null: - dq 0 - -; 32-bit code segment. -; Bytes 0x0000 - 0xffff. -.segment_code_32: - db 0xff, 0xff, \ - 0x00, 0x00, \ - 0x00, \ - 10011011b, \ - 01000000b, \ - 0x00 - -; 16-bit code segment, to use if we want to switch back to real mode. -; Bytes 0x0000 - 0xffff. -.segment_code_16: - db 0xff, 0xff, \ - 0x00, 0x00, \ - 0x00, \ - 10011011b, \ - 00000000b, \ - 0x00 - -; Data segment. -; Pages 0x000000 - 0x0fffff, which covers the entire 32-bit address space (start of 0xfffff-th page -; is 0xfffff * 4096 = 0xfffff000, end of page exclusive is 0xfffff000 + 4096 = 0x100000000). -.segment_data: - db 0xff, 0xff, \ - 0x00, 0x00, \ - 0x00, \ - 10010011b, \ - 11001111b, \ - 0x00 - - FLAT_GDT_LEN equ ($ - flat_gdt) diff --git a/boot1/build.rs b/boot1/build.rs index 9d8ead6..2e87e91 100644 --- a/boot1/build.rs +++ b/boot1/build.rs @@ -5,9 +5,9 @@ fn main() { .expect("OUT_DIR not set"); let out_dir = PathBuf::from(out_dir); - let path = SearchPath::load(); + let search_path = SearchPath::load(); - let nasm_path = path.search("nasm") + let nasm_path = search_path.search("nasm") .next() .expect("failed to find nasm in PATH"); let nasm = Nasm::new(nasm_path); @@ -38,13 +38,42 @@ fn link_obj(path: &Path) { } fn build_asm(nasm: &Nasm, out_dir: &Path) { - let prelude_obj_path = out_dir.join("prelude.o"); - nasm.assemble( - &prelude_obj_path, - &["asm/prelude.s".as_ref()], - &["../include".as_ref()], - ).unwrap(); - link_obj(&prelude_obj_path); + let asm_srcs = fs::read_dir("asm") + .expect("failed to get asm sources"); + + for asm_src in asm_srcs { + let asm_src = asm_src.expect("failed to get asm source"); + + let ty = asm_src.file_type().expect("failed to get file type"); + if !ty.is_file() { + continue; + } + + let src_path = asm_src.path(); + + let is_asm = src_path + .extension() + .map(|ext| ext == "s") + .unwrap_or(false); + if !is_asm { + continue; + } + + let out_filename = src_path + .file_name() + .unwrap() + .apply(PathBuf::from) + .with_extension("o"); + let out_path = out_dir.join(out_filename); + + nasm.assemble( + &out_path, + &[&src_path], + &["../include".as_ref()] + ).expect("failed to assemble"); + + link_obj(&out_path); + } } struct Nasm { @@ -165,3 +194,20 @@ enum CmdErrorKind { Status(process::Output), } +trait Apply: Sized { + fn apply(self, f: F) -> T + where + F: FnOnce(Self) -> T; +} + +impl Apply for U +where + U: Sized, +{ + fn apply(self, f: F) -> T + where + F: FnOnce(Self) -> T + { + f(self) + } +} diff --git a/boot1/src/main.rs b/boot1/src/main.rs index 0bb885d..c9be456 100644 --- a/boot1/src/main.rs +++ b/boot1/src/main.rs @@ -8,35 +8,31 @@ const VGA_HEIGHT: usize = 25; const VGA_SIZE: usize = VGA_WIDTH * VGA_HEIGHT; const VGA_ADDR: usize = 0xb8000; -const STR: &[u8] = b"the quick brown fox jumps over the lazy dog"; +const STR: &[u8] = b"sphinx of black quartz, judge my vow"; -// extern "C" { -// fn prelude() -> !; -// } +#[repr(C)] +#[derive(Clone, Debug)] +struct BiosIntr { + eax: u32, + ebx: u32, + ecx: u32, + edx: u32, + esi: u32, + edi: u32, + ds: u16, + es: u16, + eflags: u32, +} + +unsafe extern "C" { + unsafe fn bios_call(intr: u32, args: *mut BiosIntr); +} #[panic_handler] fn panic(_info: &PanicInfo) -> ! { hlt() } -// ```asm -// 00000000 E800000000 call 0x5 ; call / pop gadget to load eip into ecx -// 00000005 59 pop ecx -// 00000006 81C15F000000 add ecx,0x5f -// 0000000C 31C0 xor eax,eax -// 0000000E 8D89D4FFFFFF lea ecx,[ecx-0x2c] ; length of string is 0x2b -// 00000014 0FB611 movzx edx,byte [ecx] -// 00000017 81CA001F0000 or edx,0x1f00 -// 0000001D 6689940000800B00 mov [eax+eax+0xb8000],dx -// 00000025 40 inc eax -// 00000026 41 inc ecx -// 00000027 83F82B cmp eax,byte +0x2b -// 0000002A 75E8 jnz 0x14 -// 0000002C 66C70500800B0002 mov word [dword 0xb8000],0x1f02 -// -1F -// 00000035 F4 hlt -// 00000036 EBFD jmp short 0x35 -// ``` #[no_mangle] pub extern "C" fn _start() -> ! { let vga_buf: &'static mut [u16; VGA_SIZE] = unsafe { &mut *(VGA_ADDR as *mut [u16; VGA_SIZE]) }; @@ -45,6 +41,26 @@ pub extern "C" fn _start() -> ! { vga_buf[i] = 0x1f00 | u16::from(b); } + let mut args = BiosIntr { + eax: 0xe820, + ebx: 0, + ecx: 24, + edx: 0x534d4150, + esi: 0, + edi: 0x6a00, + ds: 0, + es: 0, + eflags: 0, + }; + + unsafe { + bios_call(0x15, &raw mut args); + } + + for (i, b) in STR.iter().copied().enumerate() { + vga_buf[i + VGA_WIDTH] = 0x1f00 | u16::from(b); + } + hlt() } diff --git a/include/defines.s b/include/defines.s index e6e389b..ae70d7a 100644 --- a/include/defines.s +++ b/include/defines.s @@ -19,20 +19,6 @@ %define VGA_WIDTH 80 %define VGA_HEIGHT 25 -%define INTEL_8042_IO_DATA 0x60 -%define INTEL_8042_IN_STATUS 0x64 -%define INTEL_8042_OUT_CMD 0x64 - -%define INTEL_8042_STATUS_BIT_OBUF 0 -%define INTEL_8042_STATUS_BIT_IBUF 1 -%define INTEL_8042_STATUS_MASK_OBUF (1 << INTEL_8042_STATUS_BIT_OBUF) -%define INTEL_8042_STATUS_MASK_IBUF (1 << INTEL_8042_STATUS_BIT_IBUF) - -%define INTEL_8042_CMD_PS2_1_DISABLE 0xad -%define INTEL_8042_CMD_PS2_1_ENABLE 0xae -%define INTEL_8042_CMD_CONTROLLER_OUT_PORT_READ 0xd0 -%define INTEL_8042_CMD_CONTROLLER_OUT_PORT_WRITE 0xd1 - ; ASCII string "SMAP", which is used by e820 as a magic number argument and return value. %define E820_MAGIC 0x534d4150 ; Size of entry returned by e820. This is less than MEMMAP_ENT_SIZE because we add some fields of diff --git a/include/ps2.s b/include/ps2.s new file mode 100644 index 0000000..8fe1a2a --- /dev/null +++ b/include/ps2.s @@ -0,0 +1,18 @@ +%ifndef BOOT_PS2_H +%define BOOT_PS2_H + +%define INTEL_8042_IO_DATA 0x60 +%define INTEL_8042_IN_STATUS 0x64 +%define INTEL_8042_OUT_CMD 0x64 + +%define INTEL_8042_STATUS_BIT_OBUF 0 +%define INTEL_8042_STATUS_BIT_IBUF 1 +%define INTEL_8042_STATUS_MASK_OBUF (1 << INTEL_8042_STATUS_BIT_OBUF) +%define INTEL_8042_STATUS_MASK_IBUF (1 << INTEL_8042_STATUS_BIT_IBUF) + +%define INTEL_8042_CMD_PS2_1_DISABLE 0xad +%define INTEL_8042_CMD_PS2_1_ENABLE 0xae +%define INTEL_8042_CMD_CONTROLLER_OUT_PORT_READ 0xd0 +%define INTEL_8042_CMD_CONTROLLER_OUT_PORT_WRITE 0xd1 + +%endif