├── .cargo └── config.toml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── i586-rust_dos.json ├── link.x ├── rust-toolchain └── src ├── dos.rs ├── dos ├── allocator.rs ├── console.rs ├── cooperative_multitasking │ ├── cooperative_task_switching.S │ ├── mod.rs │ └── task.rs ├── datetime.rs ├── error_code.rs ├── file.rs ├── io.rs ├── kbc.rs ├── math.rs └── panic.rs ├── dos_tests ├── allocator_test.rs ├── cooperative_multitasking_test.rs ├── datetime.rs ├── file.rs └── mod.rs ├── dpkey.rs ├── lib.rs └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | build-std = ["core", "compiler_builtins", "alloc"] 3 | 4 | [build] 5 | target = "i586-rust_dos.json" 6 | rustflags = ["-C", "link-arg=-Tlink.x"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | /Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_dos" 3 | version = "0.1.0" 4 | authors = ["Hayato Ohhashi "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [profile.release] 10 | opt-level = "z" 11 | 12 | [dependencies] 13 | rlibc = "1.0.0" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hayato Ohhashi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust DOS (Creating a DOS executable with Rust) 2 | 3 | It is possible to create a DOS executable or 1st stage bootloader with Rust. 4 | This is a quick demo of creating COM executable for DOS. 5 | 6 | ## Building 7 | You need a binutils and llvm-tools-preview. 8 | 9 | ```shell 10 | cargo install cargo-binutils 11 | rustup component add llvm-tools-preview 12 | ``` 13 | 14 | Then you can build the project by running: 15 | 16 | ```shell 17 | cargo build --release 18 | ``` 19 | 20 | To create a COM executable for DOS, run: 21 | 22 | ```shell 23 | cargo objcopy --release -- -O binary --binary-architecture=i386:x86 rust_dos.com 24 | ``` 25 | 26 | ## Running 27 | 28 | ### QEMU 29 | 30 | You can copy `rust_dos.com` to your DOS image. 31 | 32 | examples on Linux 33 | 34 | ```shell 35 | $ sudo partx -av freedos.img 36 | partition: none, disk: freedos.img, lower: 0, upper: 0 37 | Trying to use '/dev/loop1' for the loop device 38 | /dev/loop1: partition table type 'dos' detected 39 | range recount: max partno=1, lower=0, upper=0 40 | /dev/loop1: partition #1 added 41 | $ sudo mount /dev/loop1p1 /mnt 42 | $ sudo cp rust_dos.com /mnt/ 43 | $ sudo umount /mnt 44 | $ sudo partx -dv /dev/loop1 45 | ``` 46 | 47 | Then, you can test it using QEMU: 48 | 49 | ```shell 50 | qemu-system-i386 freedos.img -boot c 51 | ``` 52 | 53 | You can use the `println!` macro. 54 | Below is an example of HelloWorld: 55 | 56 | ![sample](https://github.com/o8vm/rust_dos/blob/images/rust_dos_hello.png) 57 | 58 | ### DOSBox 59 | 60 | First install DOSBox. Some examples if you like using package managers: 61 | 62 | #### Debian / Ubuntu 63 | 64 | ``` 65 | sudo apt install dosbox 66 | ``` 67 | 68 | #### macOS (Homebrew) 69 | 70 | ``` 71 | brew install dosbox 72 | ``` 73 | 74 | #### Windows (Chocolatey) 75 | 76 | ``` 77 | choco install dosbox 78 | ``` 79 | 80 | Once installed, you can launch DOSBox and give it the path to your executable. For example, you can just give it the current working directory like the following: 81 | 82 | ``` 83 | dosbox . 84 | ``` 85 | 86 | And this will open DOSBox and have the "C:\" drive be the current working directory. It's usually good to do this from another console so you don't have to close DOSBox every time you want to compile your application again. 87 | 88 | ### Others 89 | dpkey module steals key input processing from DOS and converts scan code to ascii code. 90 | about scan code: see [PS/2 Keyboard - OSDev Wiki](https://wiki.osdev.org/PS/2_Keyboard). 91 | 92 | ![sample2](https://github.com/o8vm/rust_dos/blob/images/dpkey.gif) 93 | -------------------------------------------------------------------------------- /i586-rust_dos.json: -------------------------------------------------------------------------------- 1 | { 2 | "arch": "x86", 3 | "cpu": "pentium", 4 | "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", 5 | "dynamic-linking": false, 6 | "executables": true, 7 | "has-rpath": true, 8 | "is-builtin": false, 9 | "linker-flavor": "ld.lld", 10 | "linker": "rust-lld", 11 | "llvm-target": "i586-unknown-none-code16", 12 | "max-atomic-width": 64, 13 | "os": "none", 14 | "relocation-model": "static", 15 | "position-independent-executables": false, 16 | "relro-level": "off", 17 | "target-c-int-width": "32", 18 | "target-endian": "little", 19 | "target-pointer-width": "32", 20 | "vendor": "unknown", 21 | "panic-strategy": "abort", 22 | "disable-redzone": true 23 | } 24 | -------------------------------------------------------------------------------- /link.x: -------------------------------------------------------------------------------- 1 | ENTRY(_start) 2 | 3 | MEMORY { 4 | dos : org = 0x100, len = (0xFFFF - 0x100) 5 | } 6 | 7 | SECTIONS { 8 | .text : { *(.startup) *(.text .text.*) } > dos 9 | .rodata : { *(.rodata .rodata.*) } > dos 10 | .data : { *(.data) } > dos 11 | .bss : { *(.bss) } > dos 12 | .stack : { *(.stack) } > dos 13 | _heap = ALIGN(4); 14 | } 15 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /src/dos.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | pub mod console; 3 | pub mod allocator; 4 | pub mod io; 5 | pub mod kbc; 6 | pub mod datetime; 7 | pub mod file; 8 | pub mod error_code; 9 | pub mod panic; 10 | pub mod math; 11 | pub mod cooperative_multitasking; 12 | use core::arch::asm; 13 | 14 | pub use alloc::string::String as String; 15 | pub use alloc::boxed::Box as Box; 16 | pub use alloc::vec::Vec as Vec; 17 | pub use alloc::vec as vec; 18 | 19 | pub fn exit(rt: u8) -> ! { 20 | unsafe { 21 | asm!("mov ah, 0x4C", 22 | "int 0x21", in("al") rt); 23 | } 24 | loop {} 25 | } 26 | -------------------------------------------------------------------------------- /src/dos/allocator.rs: -------------------------------------------------------------------------------- 1 | //! Memory heap allocation for DOS programs. 2 | //! Uses conventional memory for DOS programs, from _heap segment start to extended BIOS data area (EBDA) 3 | //! Uses linear algorithm for allocating memory, which is not optimal, but it's simple and works. 4 | 5 | use core::alloc::{GlobalAlloc, Layout}; 6 | use core::arch::asm; 7 | use core::cmp::min; 8 | use core::mem::size_of; 9 | 10 | struct AllocatorBlock { 11 | next: Option<*mut AllocatorBlock>, 12 | prev: Option<*mut AllocatorBlock>, 13 | size: usize, // includes size of this block 14 | used: bool, 15 | } 16 | 17 | pub struct DosAllocator { 18 | first_block_ptr: *mut AllocatorBlock, 19 | } 20 | 21 | impl DosAllocator { 22 | const LAST_MEMORY_BYTE_ADDR: u32 = 0x9FBFF; // (0X9000 << 4) + 0XFBFF, last byte of memory before extended BIOS data area 23 | const ALLOCATOR_BLOCK_SIZE: usize = size_of::(); 24 | const MIN_BLOCK_USEFUL_SIZE: usize = 16; 25 | 26 | fn diff_between_blocks_ptr(first_block: *mut AllocatorBlock, second_block: *mut AllocatorBlock) -> usize { 27 | let first_block_addr = first_block as usize; 28 | let second_block_addr = second_block as usize; 29 | assert!(first_block_addr < second_block_addr); 30 | second_block_addr - first_block_addr 31 | } 32 | 33 | fn free_space_before_next_block(block: *mut AllocatorBlock) -> usize { 34 | assert_ne!(block, core::ptr::null_mut()); 35 | assert!((block as u32) < Self::LAST_MEMORY_BYTE_ADDR); 36 | let next_block = unsafe { (*block).next }; 37 | if next_block.is_none() { 38 | return Self::LAST_MEMORY_BYTE_ADDR as usize - block as usize; 39 | } 40 | let next_block = next_block.unwrap(); 41 | Self::diff_between_blocks_ptr(block, next_block) - Self::ALLOCATOR_BLOCK_SIZE 42 | } 43 | 44 | /// Converts block address to pointer usable by the program 45 | fn block_addr_to_useful_ptr(block: *mut AllocatorBlock) -> *mut u8 { 46 | assert_ne!(block, core::ptr::null_mut()); 47 | (block as usize + Self::ALLOCATOR_BLOCK_SIZE) as *mut u8 48 | } 49 | } 50 | 51 | unsafe impl GlobalAlloc for DosAllocator { 52 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 53 | // Look for next free block 54 | let mut current_block_ptr = self.first_block_ptr; 55 | while (*current_block_ptr).used || (*current_block_ptr).size < layout.size() + Self::ALLOCATOR_BLOCK_SIZE { 56 | current_block_ptr = match (*current_block_ptr).next { 57 | Some(ptr) => ptr, 58 | None => return core::ptr::null_mut(), // No free block found, return null ptr 59 | }; 60 | } 61 | 62 | let free_space_before_next_block = Self::free_space_before_next_block(current_block_ptr); 63 | if free_space_before_next_block <= Self::MIN_BLOCK_USEFUL_SIZE + Self::ALLOCATOR_BLOCK_SIZE { 64 | // No space for new block, just use the whole space 65 | (*current_block_ptr).used = true; 66 | (*current_block_ptr).size = free_space_before_next_block; 67 | return Self::block_addr_to_useful_ptr(current_block_ptr); 68 | } 69 | 70 | // Create a new unused block between current and next 71 | 72 | let current_block_size = layout.size() + Self::ALLOCATOR_BLOCK_SIZE; 73 | 74 | // Create a new block just after current block 75 | let new_block_ptr = (current_block_size + current_block_ptr as usize) as *mut AllocatorBlock; 76 | (*new_block_ptr).next = (*current_block_ptr).next; 77 | (*new_block_ptr).prev = Some(current_block_ptr); 78 | (*new_block_ptr).size = free_space_before_next_block - current_block_size; 79 | (*new_block_ptr).used = false; 80 | 81 | (*current_block_ptr).next = Some(new_block_ptr); 82 | (*current_block_ptr).size = current_block_size; 83 | (*current_block_ptr).used = true; 84 | 85 | Self::block_addr_to_useful_ptr(current_block_ptr) 86 | } 87 | 88 | unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) { 89 | // Freeing null pointer is a no-op 90 | if ptr == core::ptr::null_mut() { 91 | return; 92 | } 93 | 94 | let current_block_ptr = (ptr as u32 - Self::ALLOCATOR_BLOCK_SIZE as u32) as *mut AllocatorBlock; 95 | // Mark block as free 96 | (*current_block_ptr).used = false; 97 | 98 | // Merge with next block if it's free 99 | let next_block_ptr = (*current_block_ptr).next; 100 | if next_block_ptr.is_some() { 101 | let next_block_ptr = next_block_ptr.unwrap(); 102 | if !(*next_block_ptr).used { 103 | if (*next_block_ptr).next.is_some() { 104 | (*(*next_block_ptr).next.unwrap()).prev = Some(current_block_ptr); 105 | } 106 | (*current_block_ptr).size += (*next_block_ptr).size; 107 | (*current_block_ptr).next = (*next_block_ptr).next; 108 | } 109 | } 110 | 111 | // Merge with previous block if it's free 112 | let prev_block_ptr = (*current_block_ptr).prev; 113 | if prev_block_ptr.is_some() { 114 | let prev_block_ptr = prev_block_ptr.unwrap(); 115 | if !(*prev_block_ptr).used { 116 | if (*current_block_ptr).next.is_some() { 117 | (*(*current_block_ptr).next.unwrap()).prev = Some(prev_block_ptr); 118 | } 119 | (*prev_block_ptr).size += (*current_block_ptr).size; 120 | (*prev_block_ptr).next = (*current_block_ptr).next; 121 | } 122 | } 123 | } 124 | 125 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 126 | assert_ne!(ptr, core::ptr::null_mut()); // Avoid undefined behavior 127 | let current_block_ptr = (ptr as u32 - Self::ALLOCATOR_BLOCK_SIZE as u32) as *mut AllocatorBlock; 128 | if (*current_block_ptr).size >= new_size + Self::ALLOCATOR_BLOCK_SIZE { 129 | return ptr; 130 | } 131 | let new_ptr = self.alloc(Layout::from_size_align(new_size, layout.align()).unwrap()); 132 | new_ptr.copy_from_nonoverlapping(ptr, min(layout.size(), new_size)); 133 | self.dealloc(ptr, layout); 134 | new_ptr 135 | } 136 | } 137 | 138 | impl DosAllocator { 139 | #[allow(unused_assignments)] 140 | pub fn init(&mut self) { 141 | let mut heap_segment_number: u32 = 0; 142 | unsafe { 143 | asm!("mov ax, _heap", out("ax") heap_segment_number); 144 | } 145 | // Compute heap address from segment number 146 | let heap_addr = ((heap_segment_number & 0xFFFF) << 4) as u32; 147 | let heap_ptr_as_block = heap_addr as *mut AllocatorBlock; 148 | 149 | // Create an empty block at the beginning of the heap, containing all free space 150 | assert!(heap_addr as u32 <= DosAllocator::LAST_MEMORY_BYTE_ADDR); 151 | let first_block_size = DosAllocator::LAST_MEMORY_BYTE_ADDR - heap_addr; 152 | unsafe { 153 | *heap_ptr_as_block = AllocatorBlock { 154 | next: None, 155 | prev: None, 156 | size: first_block_size as usize, 157 | used: false, 158 | }; 159 | } 160 | self.first_block_ptr = heap_ptr_as_block 161 | } 162 | 163 | const fn new() -> Self { 164 | Self { 165 | first_block_ptr: core::ptr::null_mut(), 166 | } 167 | } 168 | } 169 | 170 | #[alloc_error_handler] 171 | fn alloc_error_handler(layout: Layout) -> ! { 172 | panic!("allocation error: {:?}", layout); 173 | } 174 | 175 | #[global_allocator] 176 | pub(crate) static mut GLOBAL_ALLOCATOR: DosAllocator = DosAllocator::new(); -------------------------------------------------------------------------------- /src/dos/console.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::fmt::{self, Write}; 3 | 4 | #[macro_export] 5 | macro_rules! print { 6 | ($($arg:tt)*) => { 7 | $crate::dos::console::_print(format_args!($($arg)*)) 8 | }; 9 | } 10 | 11 | #[macro_export] 12 | macro_rules! println { 13 | ($fmt:expr) => { 14 | print!(concat!($fmt, "\r\n")) 15 | }; 16 | ($fmt:expr, $($arg:tt)*) => { 17 | print!(concat!($fmt, "\r\n"), $($arg)*) 18 | }; 19 | } 20 | 21 | pub fn _print(args: fmt::Arguments) { 22 | let mut writer = DosWriter {}; 23 | writer.write_fmt(args).unwrap(); 24 | } 25 | 26 | struct DosWriter; 27 | 28 | impl Write for DosWriter { 29 | fn write_str(&mut self, s: &str) -> fmt::Result { 30 | for c in s.bytes() { 31 | printc(c); 32 | } 33 | Ok(()) 34 | } 35 | } 36 | 37 | fn printc(ch: u8) { 38 | unsafe { asm!("int 0x21", in("ah") 0x02_u8, in("dl") ch) } 39 | } 40 | -------------------------------------------------------------------------------- /src/dos/cooperative_multitasking/cooperative_task_switching.S: -------------------------------------------------------------------------------- 1 | # inspired from https://wiki.osdev.org/Cooperative_Multitasking 2 | 3 | ; .section .text 4 | ; .global cooperative_task_switching_assembly 5 | cooperative_task_switching_assembly: 6 | pushad # 32 bytes 7 | pushfd # 4 bytes # pushf 8 | mov eax, cr3 #Push CR3 9 | push eax 10 | 11 | mov eax, [44+esp] #The first argument, where to save 12 | mov [4+eax], ebx 13 | mov [8+eax], ecx 14 | mov [12+eax], edx 15 | mov [16+eax], esi 16 | mov [20+eax], edi 17 | 18 | mov ebx, [36+esp] # EAX 19 | mov ecx, [40+esp] # IP 20 | mov edx, [20+esp] # ESP 21 | add edx, 4 # Remove the return value 22 | mov esi, [16+esp] # EBP 23 | mov edi, [4+esp] # EFLAGS 24 | 25 | mov [eax], ebx 26 | 27 | mov [24+eax], edx 28 | mov [28+eax], esi 29 | mov [32+eax], ecx 30 | mov [36+eax], edi 31 | pop ebx # CR3 32 | mov [40+eax], ebx 33 | push ebx # Goodbye again 34 | mov eax, [48+esp] # The second argument, where to load 35 | 36 | mov ebx, [4+eax] # EBX 37 | mov ecx, [8+eax] # ECX 38 | mov edx, [12+eax] # EDX 39 | mov esi, [16+eax] # ESI 40 | mov edi, [20+eax] # EDI 41 | mov ebp, [28+eax] # EBP 42 | 43 | push eax 44 | mov eax, [36+eax] # EFLAGS 45 | push eax 46 | popfd 47 | pop eax 48 | 49 | mov esp, [24+eax] # ESP ## error ? 50 | push eax 51 | 52 | mov eax, [40+eax] # CR3 53 | mov cr3, eax 54 | pop eax 55 | 56 | push eax 57 | mov eax, [32+eax] # EIP 58 | xchg eax, [esp] # Cannot use a tmp storage 59 | mov eax, [eax] # EAX 60 | ret 61 | -------------------------------------------------------------------------------- /src/dos/cooperative_multitasking/mod.rs: -------------------------------------------------------------------------------- 1 | use alloc::collections::VecDeque; 2 | use core::arch::{asm, global_asm}; 3 | use crate::dos::cooperative_multitasking::task::{Registers, Task}; 4 | 5 | mod task; 6 | 7 | global_asm!(include_str!("cooperative_task_switching.S")); 8 | 9 | extern "C" { 10 | fn cooperative_task_switching_assembly(from: *mut Registers, to: *mut Registers) -> (); 11 | } 12 | 13 | pub struct Tasking{ 14 | task_list: Option>, 15 | current_task_id: u8, 16 | eflags_register: u32, 17 | cr3_register: u32, 18 | initialized: bool, 19 | } 20 | 21 | impl Tasking { 22 | const MAX_TASKS: usize = 10; 23 | 24 | pub fn init(&mut self) { 25 | if self.task_list.is_some() { 26 | self.task_list = None; 27 | } 28 | let (eflags, cr3) = Self::get_eflags_and_cr3_registers(); 29 | 30 | // Create main task 31 | self.task_list = Some(VecDeque::with_capacity(Self::MAX_TASKS)); 32 | self.current_task_id = 0; 33 | self.eflags_register = eflags; 34 | self.cr3_register = cr3; 35 | self.initialized = true; 36 | self.task_list.as_mut().unwrap().push_back(Task { 37 | registers: Registers { 38 | eax: 0, 39 | ebx: 0, 40 | ecx: 0, 41 | edx: 0, 42 | esi: 0, 43 | edi: 0, 44 | esp: 0, 45 | ebp: 0, 46 | eip: 0, 47 | eflags, 48 | cr3, 49 | }, 50 | }); 51 | } 52 | 53 | pub fn add_task(&mut self, main_function: *mut fn()) -> Result<(), &'static str> { 54 | if !self.initialized { 55 | return Err("Cooperative tasking manager is not initialized"); 56 | } 57 | if self.task_list.as_ref().unwrap().len() >= Self::MAX_TASKS { 58 | return Err("Maximum number of tasks reached"); 59 | } 60 | let task_list = self.task_list.as_mut().unwrap(); 61 | task_list.push_back(Task::new(main_function, self.eflags_register, self.cr3_register as *mut u32, task_list.len() as u8)); 62 | Ok(()) 63 | } 64 | 65 | pub fn yield_task(&mut self) { 66 | if !self.initialized { 67 | panic!("Cooperative tasking manager is not initialized"); 68 | } 69 | 70 | let task_list = self.task_list.as_mut().unwrap(); 71 | 72 | let current_task_registers_ptr = &mut task_list[self.current_task_id as usize].registers as *mut Registers; 73 | 74 | self.current_task_id += 1; 75 | if self.current_task_id >= task_list.len() as u8 { 76 | self.current_task_id = 0; 77 | } 78 | 79 | let next_task_registers_ptr = &mut task_list[self.current_task_id as usize].registers as *mut Registers; 80 | 81 | unsafe { 82 | cooperative_task_switching_assembly(current_task_registers_ptr, next_task_registers_ptr); 83 | } 84 | } 85 | 86 | fn get_eflags_and_cr3_registers() -> (u32, u32) { 87 | let mut eflags: u32; 88 | let mut cr3: u32; 89 | unsafe { 90 | // Read CR3 91 | asm!("mov {}, cr3", out(reg) cr3); 92 | // Read EFLAGS 93 | asm!("pushfd; mov eax, [esp]; mov {}, eax; popfd;", out(reg) eflags); 94 | } 95 | (eflags, cr3) 96 | } 97 | } 98 | 99 | pub static mut TASKING: Tasking = Tasking { 100 | task_list: None, 101 | current_task_id: 0, 102 | eflags_register: 0, 103 | cr3_register: 0, 104 | initialized: false, 105 | }; 106 | 107 | #[macro_export] 108 | macro_rules! yield_cooperative_task { 109 | () => { 110 | unsafe { 111 | $crate::dos::cooperative_multitasking::TASKING.yield_task(); 112 | } 113 | }; 114 | } 115 | 116 | #[macro_export] 117 | macro_rules! add_cooperative_task { 118 | ($main_function: expr) => { 119 | unsafe { 120 | $crate::dos::cooperative_multitasking::TASKING.add_task($main_function as *mut fn()) 121 | } 122 | }; 123 | } -------------------------------------------------------------------------------- /src/dos/cooperative_multitasking/task.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] // To ensure that the struct is laid out in the same way as the assembly code expects 2 | #[derive(Copy, Clone, Debug)] 3 | pub(crate)struct Registers { 4 | pub eax: u32, 5 | pub ebx: u32, 6 | pub ecx: u32, 7 | pub edx: u32, 8 | pub esi: u32, 9 | pub edi: u32, 10 | pub esp: u32, 11 | pub ebp: u32, 12 | pub eip: u32, 13 | pub eflags: u32, 14 | pub cr3: u32, 15 | } 16 | 17 | #[derive(Debug)] 18 | pub(crate) struct Task { 19 | pub(crate) registers: Registers, 20 | } 21 | 22 | // In order to use heap as stack, we need to change ss stack segment register 23 | impl Task { 24 | const TASK_STACK_SIZE: usize = 4096; 25 | 26 | /// Max stack for each task, including the main task, is 4KB 27 | pub fn new(main_function: *mut fn(), flags: u32, pagedir: *mut u32, task_index: u8) -> Task { 28 | Task { 29 | registers: Registers { 30 | eax: 0, 31 | ebx: 0, 32 | ecx: 0, 33 | edx: 0, 34 | esi: 0, 35 | edi: 0, 36 | esp: 0xffff as u32 - (Self::TASK_STACK_SIZE as u32 * task_index as u32), 37 | ebp: 0, 38 | eip: main_function as u32, 39 | eflags: flags, 40 | cr3: pagedir as u32, 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/dos/datetime.rs: -------------------------------------------------------------------------------- 1 | use core::{arch::asm}; 2 | 3 | #[allow(dead_code)] 4 | #[derive(Debug)] 5 | #[repr(u8)] 6 | pub enum Day { 7 | Sunday = 0, 8 | Monday, 9 | Tuesday, 10 | Wednesday, 11 | Thursday, 12 | Friday, 13 | Saturday, 14 | UnknownDay, 15 | } 16 | 17 | impl From for Day { 18 | fn from(value: u8) -> Self { 19 | match value { 20 | 0 => Self::Sunday, 21 | 1 => Self::Monday, 22 | 2 => Self::Tuesday, 23 | 3 => Self::Wednesday, 24 | 4 => Self::Thursday, 25 | 5 => Self::Friday, 26 | 6 => Self::Saturday, 27 | _ => Self::UnknownDay 28 | } 29 | } 30 | } 31 | 32 | impl Default for Day { 33 | fn default() -> Self { 34 | Self::Sunday 35 | } 36 | } 37 | 38 | #[derive(Default)] 39 | pub struct Date { 40 | pub year: u16, 41 | pub month: u8, 42 | pub day: u8, 43 | pub day_of_week: Day 44 | } 45 | 46 | impl Date { 47 | pub fn now() -> Self { 48 | let mut date = Date::default(); 49 | let mut day_of_week: u8; 50 | 51 | unsafe { 52 | asm!("mov ah, 0x2A", 53 | "int 0x21", 54 | out("cx") date.year, 55 | out("dh") date.month, 56 | out("dl") date.day, 57 | out("al") day_of_week); 58 | } 59 | 60 | date.day_of_week = Day::from(day_of_week); 61 | 62 | date 63 | } 64 | 65 | pub fn save(&self) -> Result<(), ()> { 66 | let mut result: u8; 67 | 68 | unsafe { 69 | asm!("mov ah, 0x2B", 70 | "int 0x21", 71 | in("cx") self.year, 72 | in("dh") self.month, 73 | in("dl") self.day, 74 | out("al") result); 75 | } 76 | 77 | if result == 0 { 78 | Ok(()) 79 | } else { 80 | Err(()) 81 | } 82 | } 83 | } 84 | 85 | impl core::fmt::Debug for Date { 86 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 87 | write!(f, "{}-{:02}-{:02} ({:?})", self.year, self.month, self.day, self.day_of_week) 88 | } 89 | } 90 | 91 | #[derive(Default)] 92 | pub struct Time { 93 | pub hour: u8, 94 | pub minute: u8, 95 | pub second: u8, 96 | pub centisecond: u8, 97 | } 98 | 99 | impl Time { 100 | pub fn now() -> Self { 101 | let mut time = Time::default(); 102 | 103 | unsafe { 104 | asm!("mov ah, 0x2C", 105 | "int 0x21", 106 | out("ch") time.hour, 107 | out("cl") time.minute, 108 | out("dh") time.second, 109 | out("dl") time.centisecond); 110 | } 111 | 112 | time 113 | } 114 | 115 | pub fn save(&self) -> Result<(), ()> { 116 | let mut result: u8; 117 | 118 | unsafe { 119 | asm!("mov ah, 0x2D", 120 | "int 0x21", 121 | in("ch") self.hour, 122 | in("cl") self.minute, 123 | in("dh") self.second, 124 | in("dl") self.centisecond, 125 | out("al") result); 126 | } 127 | 128 | if result == 0 { 129 | Ok(()) 130 | } else { 131 | Err(()) 132 | } 133 | } 134 | } 135 | 136 | impl core::fmt::Debug for Time { 137 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 138 | write!(f, "{:02}:{:02}:{:02}.{:02}", self.hour, self.minute, self.second, self.centisecond) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/dos/error_code.rs: -------------------------------------------------------------------------------- 1 | use core::convert::TryFrom; 2 | use core::fmt; 3 | 4 | #[derive(Debug, Clone, Copy)] 5 | pub enum ErrorCode { 6 | Success = 0, 7 | InvalidFunctionNumber = 1, 8 | FileNotFound = 2, 9 | PathNotFound = 3, 10 | TooManyOpenFiles = 4, 11 | AccessDenied = 5, 12 | InvalidHandle = 6, 13 | MemoryControlBlockDestroyed = 7, 14 | InsufficientMemory = 8, 15 | InvalidMemoryBlockAddress = 9, 16 | InvalidEnvironment = 10, 17 | InvalidFormat = 11, 18 | InvalidAccessCode = 12, 19 | InvalidData = 13, 20 | // 14 is reserved 21 | InvalidDrive = 15, 22 | AttemptedToRemoveCurrentDirectory = 16, 23 | NotSameDevice = 17, 24 | NoMoreFiles = 18, 25 | DiskWriteProtected = 19, 26 | UnknownUnit = 20, 27 | DriveNotReady = 21, 28 | UnknownCommand = 22, 29 | DataErrorCrcFailed = 23, 30 | BadRequestStructureLength = 24, 31 | SeekError = 25, 32 | UnknownMediaType = 26, 33 | SectorNotFound = 27, 34 | PrinterOutOfPaper = 28, 35 | WriteFault = 29, 36 | ReadFault = 30, 37 | GeneralFailure = 31, 38 | SharingViolation = 32, 39 | LockViolation = 33, 40 | InvalidDiskChange = 34, 41 | FcbUnavailable = 35, 42 | InvalidSharingBuffer = 36, 43 | CodePageMismatch = 37, // Reserved under MS-DOS 4.0 44 | CannotCompleteFileOperation = 38, // Reserved under MS-DOS 4.0 45 | InsufficientDiskSpace = 39, // Reserved under MS-DOS 4.0 46 | // 40 to 49 are reserved 47 | NetworkRequestNotSupported = 50, 48 | RemoteComputerNotListening = 51, 49 | DuplicateNameOnNetwork = 52, 50 | NetworkNameNotFound = 53, 51 | NetworkBusy = 54, 52 | NetworkDeviceNoLongerExists = 55, 53 | NetworkBIOSCommandLimitExceeded = 56, 54 | NetworkAdapterHardwareError = 57, 55 | IncorrectResponseFromNetwork = 58, 56 | UnexpectedNetworkError = 59, 57 | IncompatibleRemoteAdapter = 60, 58 | PrintQueueFull = 61, 59 | NotEnoughSpaceForPrintFile = 62, 60 | PrintFileDeleted = 63, 61 | NetworkNameDeleted = 64, 62 | NetworkAccessDenied = 65, 63 | NetworkDeviceTypeIncorrect = 66, 64 | NetworkNameNotFound2 = 67, 65 | NetworkNameLimitExceeded = 68, 66 | NetworkBIOSSessionLimitExceeded = 69, 67 | TemporarilyPaused = 70, 68 | NetworkRequestNotAccepted = 71, 69 | NetworkPrintAndDiskRedirectionPaused = 72, 70 | InvalidNetworkVersion = 73, // Reserved on DOS, used only on LANtastic 71 | AccountExpired = 74, // Reserved on DOS, used only on LANtastic 72 | PasswordExpired = 75, // Reserved on DOS, used only on LANtastic 73 | LoginAttemptedInvalidAtThisTime = 76, // Reserved on DOS, used only on LANtastic 74 | DiskLimitExceedOnNetworkNode = 77, // Reserved on DOS, used only on LANtastic 75 | NotLoggedInToNetworkMode = 78, // Reserved on DOS, used only on LANtastic 76 | // 79 is reserved 77 | FileAlreadyExists = 80, 78 | // 81 is reserved 79 | CannotMakeDirectory = 82, 80 | FailOnInterrupt24h = 83, 81 | TooManyRedirections = 84, // Reserved under MS-DOS 3.3 82 | DuplicateRedirection = 85, // Reserved under MS-DOS 3.3 83 | InvalidPassword = 86, // Reserved under MS-DOS 3.3 84 | InvalidParameter = 87, // Reserved under MS-DOS 3.3 85 | NetworkDeviceFault = 88, // Reserved under MS-DOS 3.3 86 | FunctionNotSupportedByNetwork = 89, // Reserved under MS-DOS 4.0 87 | RequiredSystemComponentNotInstalled = 90, // Reserved under MS-DOS 4.0 88 | UnknownError = 255, // This error doesn't exist in MS-DOS 89 | } 90 | 91 | impl ErrorCode { 92 | pub fn from_u8(value: u8) -> Option { 93 | match value { 94 | 0 => Some(ErrorCode::Success), 95 | 1 => Some(ErrorCode::InvalidFunctionNumber), 96 | 2 => Some(ErrorCode::FileNotFound), 97 | 3 => Some(ErrorCode::PathNotFound), 98 | 4 => Some(ErrorCode::TooManyOpenFiles), 99 | 5 => Some(ErrorCode::AccessDenied), 100 | 6 => Some(ErrorCode::InvalidHandle), 101 | 7 => Some(ErrorCode::MemoryControlBlockDestroyed), 102 | 8 => Some(ErrorCode::InsufficientMemory), 103 | 9 => Some(ErrorCode::InvalidMemoryBlockAddress), 104 | 10 => Some(ErrorCode::InvalidEnvironment), 105 | 11 => Some(ErrorCode::InvalidFormat), 106 | 12 => Some(ErrorCode::InvalidAccessCode), 107 | 13 => Some(ErrorCode::InvalidData), 108 | 15 => Some(ErrorCode::InvalidDrive), 109 | 16 => Some(ErrorCode::AttemptedToRemoveCurrentDirectory), 110 | 17 => Some(ErrorCode::NotSameDevice), 111 | 18 => Some(ErrorCode::NoMoreFiles), 112 | 19 => Some(ErrorCode::DiskWriteProtected), 113 | 20 => Some(ErrorCode::UnknownUnit), 114 | 21 => Some(ErrorCode::DriveNotReady), 115 | 22 => Some(ErrorCode::UnknownCommand), 116 | 23 => Some(ErrorCode::DataErrorCrcFailed), 117 | 24 => Some(ErrorCode::BadRequestStructureLength), 118 | 25 => Some(ErrorCode::SeekError), 119 | 26 => Some(ErrorCode::UnknownMediaType), 120 | 27 => Some(ErrorCode::SectorNotFound), 121 | 28 => Some(ErrorCode::PrinterOutOfPaper), 122 | 29 => Some(ErrorCode::WriteFault), 123 | 30 => Some(ErrorCode::ReadFault), 124 | 31 => Some(ErrorCode::GeneralFailure), 125 | 32 => Some(ErrorCode::SharingViolation), 126 | 33 => Some(ErrorCode::LockViolation), 127 | 34 => Some(ErrorCode::InvalidDiskChange), 128 | 35 => Some(ErrorCode::FcbUnavailable), 129 | 36 => Some(ErrorCode::InvalidSharingBuffer), 130 | 37 => Some(ErrorCode::CodePageMismatch), 131 | 38 => Some(ErrorCode::CannotCompleteFileOperation), 132 | 39 => Some(ErrorCode::InsufficientDiskSpace), 133 | 50 => Some(ErrorCode::NetworkRequestNotSupported), 134 | 51 => Some(ErrorCode::RemoteComputerNotListening), 135 | 52 => Some(ErrorCode::DuplicateNameOnNetwork), 136 | 53 => Some(ErrorCode::NetworkNameNotFound), 137 | 54 => Some(ErrorCode::NetworkBusy), 138 | 55 => Some(ErrorCode::NetworkDeviceNoLongerExists), 139 | 56 => Some(ErrorCode::NetworkBIOSCommandLimitExceeded), 140 | 57 => Some(ErrorCode::NetworkAdapterHardwareError), 141 | 58 => Some(ErrorCode::IncorrectResponseFromNetwork), 142 | 59 => Some(ErrorCode::UnexpectedNetworkError), 143 | 60 => Some(ErrorCode::IncompatibleRemoteAdapter), 144 | 61 => Some(ErrorCode::PrintQueueFull), 145 | 62 => Some(ErrorCode::NotEnoughSpaceForPrintFile), 146 | 63 => Some(ErrorCode::PrintFileDeleted), 147 | 64 => Some(ErrorCode::NetworkNameDeleted), 148 | 65 => Some(ErrorCode::NetworkAccessDenied), 149 | 66 => Some(ErrorCode::NetworkDeviceTypeIncorrect), 150 | 67 => Some(ErrorCode::NetworkNameNotFound2), 151 | 68 => Some(ErrorCode::NetworkNameLimitExceeded), 152 | 69 => Some(ErrorCode::NetworkBIOSSessionLimitExceeded), 153 | 70 => Some(ErrorCode::TemporarilyPaused), 154 | 71 => Some(ErrorCode::NetworkRequestNotAccepted), 155 | 72 => Some(ErrorCode::NetworkPrintAndDiskRedirectionPaused), 156 | 73 => Some(ErrorCode::InvalidNetworkVersion), 157 | 74 => Some(ErrorCode::AccountExpired), 158 | 75 => Some(ErrorCode::PasswordExpired), 159 | 76 => Some(ErrorCode::LoginAttemptedInvalidAtThisTime), 160 | 77 => Some(ErrorCode::DiskLimitExceedOnNetworkNode), 161 | 78 => Some(ErrorCode::NotLoggedInToNetworkMode), 162 | 80 => Some(ErrorCode::FileAlreadyExists), 163 | 82 => Some(ErrorCode::CannotMakeDirectory), 164 | 83 => Some(ErrorCode::FailOnInterrupt24h), 165 | 84 => Some(ErrorCode::TooManyRedirections), 166 | 85 => Some(ErrorCode::DuplicateRedirection), 167 | 86 => Some(ErrorCode::InvalidPassword), 168 | 87 => Some(ErrorCode::InvalidParameter), 169 | 88 => Some(ErrorCode::NetworkDeviceFault), 170 | 89 => Some(ErrorCode::FunctionNotSupportedByNetwork), 171 | 90 => Some(ErrorCode::RequiredSystemComponentNotInstalled), 172 | 255 => Some(ErrorCode::UnknownError), 173 | _ => None, 174 | } 175 | } 176 | 177 | pub fn to_u8(&self) -> u8 { 178 | return *self as u8; 179 | } 180 | 181 | pub fn as_str(&self) -> &'static str { 182 | match self { 183 | ErrorCode::Success => "Success", 184 | ErrorCode::InvalidFunctionNumber => "Invalid function number", 185 | ErrorCode::FileNotFound => "File not found", 186 | ErrorCode::PathNotFound => "Path not found", 187 | ErrorCode::TooManyOpenFiles => "Too many open files", 188 | ErrorCode::AccessDenied => "Access denied", 189 | ErrorCode::InvalidHandle => "Invalid handle", 190 | ErrorCode::MemoryControlBlockDestroyed => "Memory control block destroyed", 191 | ErrorCode::InsufficientMemory => "Insufficient memory", 192 | ErrorCode::InvalidMemoryBlockAddress => "Invalid memory block address", 193 | ErrorCode::InvalidEnvironment => "Invalid environment", 194 | ErrorCode::InvalidFormat => "Invalid format", 195 | ErrorCode::InvalidAccessCode => "Invalid access code", 196 | ErrorCode::InvalidData => "Invalid data", 197 | ErrorCode::InvalidDrive => "Invalid drive", 198 | ErrorCode::AttemptedToRemoveCurrentDirectory => "Attempted to remove current directory", 199 | ErrorCode::NotSameDevice => "Not same device", 200 | ErrorCode::NoMoreFiles => "No more files", 201 | ErrorCode::DiskWriteProtected => "Disk write protected", 202 | ErrorCode::UnknownUnit => "Unknown unit", 203 | ErrorCode::DriveNotReady => "Drive not ready", 204 | ErrorCode::UnknownCommand => "Unknown command", 205 | ErrorCode::DataErrorCrcFailed => "Data error (CRC failed)", 206 | ErrorCode::BadRequestStructureLength => "Bad request structure length", 207 | ErrorCode::SeekError => "Seek error", 208 | ErrorCode::UnknownMediaType => "Unknown media type", 209 | ErrorCode::SectorNotFound => "Sector not found", 210 | ErrorCode::PrinterOutOfPaper => "Printer out of paper", 211 | ErrorCode::WriteFault => "Write fault", 212 | ErrorCode::ReadFault => "Read fault", 213 | ErrorCode::GeneralFailure => "General failure", 214 | ErrorCode::SharingViolation => "Sharing violation", 215 | ErrorCode::LockViolation => "Lock violation", 216 | ErrorCode::InvalidDiskChange => "Invalid disk change", 217 | ErrorCode::FcbUnavailable => "FCB unavailable", 218 | ErrorCode::InvalidSharingBuffer => "Invalid sharing buffer", 219 | ErrorCode::CodePageMismatch => "Code page mismatch", 220 | ErrorCode::CannotCompleteFileOperation => "Cannot complete file operation", 221 | ErrorCode::InsufficientDiskSpace => "Insufficient disk space", 222 | ErrorCode::NetworkRequestNotSupported => "Network request not supported", 223 | ErrorCode::RemoteComputerNotListening => "Remote computer not listening", 224 | ErrorCode::DuplicateNameOnNetwork => "Duplicate name on network", 225 | ErrorCode::NetworkNameNotFound => "Network name not found", 226 | ErrorCode::NetworkBusy => "Network busy", 227 | ErrorCode::NetworkDeviceNoLongerExists => "Network device no longer exists", 228 | ErrorCode::NetworkBIOSCommandLimitExceeded => "Network BIOS command limit exceeded", 229 | ErrorCode::NetworkAdapterHardwareError => "Network adapter hardware error", 230 | ErrorCode::IncorrectResponseFromNetwork => "Incorrect response from network", 231 | ErrorCode::UnexpectedNetworkError => "Unexpected network error", 232 | ErrorCode::IncompatibleRemoteAdapter => "Incompatible remote adapter", 233 | ErrorCode::PrintQueueFull => "Print queue full", 234 | ErrorCode::NotEnoughSpaceForPrintFile => "Not enough space for print file", 235 | ErrorCode::PrintFileDeleted => "Print file deleted", 236 | ErrorCode::NetworkNameDeleted => "Network name deleted", 237 | ErrorCode::NetworkAccessDenied => "Network access denied", 238 | ErrorCode::NetworkDeviceTypeIncorrect => "Network device type incorrect", 239 | ErrorCode::NetworkNameNotFound2 => "Network name not found", 240 | ErrorCode::NetworkNameLimitExceeded => "Network name limit exceeded", 241 | ErrorCode::NetworkBIOSSessionLimitExceeded => "Network BIOS session limit exceeded", 242 | ErrorCode::TemporarilyPaused => "Temporarily paused", 243 | ErrorCode::NetworkRequestNotAccepted => "Network request not accepted", 244 | ErrorCode::NetworkPrintAndDiskRedirectionPaused => "Network print and disk redirection paused", 245 | ErrorCode::InvalidNetworkVersion => "Invalid network version", 246 | ErrorCode::AccountExpired => "Account expired", 247 | ErrorCode::PasswordExpired => "Password expired", 248 | ErrorCode::LoginAttemptedInvalidAtThisTime => "Login attempt invalid at this time", 249 | ErrorCode::DiskLimitExceedOnNetworkNode => "Disk limit exceeded on network node", 250 | ErrorCode::NotLoggedInToNetworkMode => "Not logged in to network mode", 251 | ErrorCode::FileAlreadyExists => "File already exists", 252 | ErrorCode::CannotMakeDirectory => "Cannot make directory", 253 | ErrorCode::FailOnInterrupt24h => "Fail on interrupt 24h", 254 | ErrorCode::TooManyRedirections => "Too many redirections", 255 | ErrorCode::DuplicateRedirection => "Duplicate redirection", 256 | ErrorCode::InvalidPassword => "Invalid password", 257 | ErrorCode::InvalidParameter => "Invalid parameter", 258 | ErrorCode::NetworkDeviceFault => "Network device fault", 259 | ErrorCode::FunctionNotSupportedByNetwork => "Function not supported by network", 260 | ErrorCode::RequiredSystemComponentNotInstalled => "Required system component not installed", 261 | ErrorCode::UnknownError => "Unknown error", 262 | } 263 | } 264 | } 265 | 266 | impl fmt::Display for ErrorCode { 267 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 268 | write!(f, "{}", self.as_str()) 269 | } 270 | } 271 | 272 | impl Into for ErrorCode { 273 | fn into(self) -> u8 { 274 | return self.to_u8(); 275 | } 276 | } 277 | 278 | impl TryFrom for ErrorCode { 279 | type Error = &'static str; 280 | 281 | fn try_from(value: u8) -> Result { 282 | match ErrorCode::from_u8(value) { 283 | Some(error_code) => Ok(error_code), 284 | None => Err("Unknown error code"), 285 | } 286 | } 287 | } -------------------------------------------------------------------------------- /src/dos/file.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::cmp::min; 3 | use crate::dos::error_code::ErrorCode; 4 | 5 | extern crate rlibc; 6 | 7 | #[allow(dead_code)] 8 | pub struct File { 9 | handle: u16, 10 | } 11 | #[allow(dead_code)] 12 | pub enum SeekFrom { 13 | Start(u32), 14 | End(u32), 15 | Current(u32), 16 | } 17 | 18 | impl SeekFrom { 19 | fn to_dos_seek_code(&self) -> u8 { 20 | match self { 21 | SeekFrom::Start(_) => 0, 22 | SeekFrom::End(_) => 2, 23 | SeekFrom::Current(_) => 1, 24 | } 25 | } 26 | 27 | fn to_seek_offset(&self) -> u32 { 28 | match self { 29 | SeekFrom::Start(offset) => *offset, 30 | SeekFrom::End(offset) => *offset, 31 | SeekFrom::Current(offset) => *offset, 32 | } 33 | } 34 | } 35 | 36 | #[allow(dead_code)] 37 | #[allow(unused_assignments)] 38 | impl File { 39 | pub fn open(filename: &str) -> Result { 40 | let mut is_open_success: u16 = 1; // 0: success, 1: fail 41 | let mut error_code_or_handle: u16 = 0; 42 | // DOS PATH length limit is 66 bytes. 43 | let mut filename_array: [u8; 70] = [0; 70]; // To be sure of the segment 44 | for i in 0..min(filename_array.len(), filename.len()) { 45 | filename_array[i] = filename.as_bytes()[i]; 46 | } 47 | let filename_ptr = filename_array.as_ptr(); 48 | unsafe { 49 | asm!("mov al, 0x40", "mov ah, 0x3d", "int 0x21", "setc dl", "movzx cx, dl", in("dx") filename_ptr as u16, lateout("cx") is_open_success, lateout("ax") error_code_or_handle); 50 | } 51 | if is_open_success == 1 { 52 | return Err(ErrorCode::from_u8(error_code_or_handle as u8).unwrap_or(ErrorCode::UnknownError)); 53 | } 54 | Ok(Self { 55 | handle: error_code_or_handle, 56 | }) 57 | } 58 | 59 | pub fn read(&self, buffer: &mut [u8]) -> Result { 60 | let mut total_bytes_read: usize = 0; 61 | for buffer_write_pos in 0..buffer.len() { 62 | let mut is_read_success: u16 = 1; // 0: success, 1: fail 63 | let mut error_code_or_bytes_read: u16 = 0; 64 | let mut tmp_stack_buffer: [u8; 1] = [0; 1]; // To be sure of the segment 65 | let tmp_buffer_ptr = tmp_stack_buffer.as_mut_ptr(); 66 | unsafe { 67 | asm!("push ax", "push bx", "push cx", "push dx"); 68 | asm!("mov cx, 1", "mov ah, 0x3f", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, in("dx") tmp_buffer_ptr, lateout("cx") is_read_success, lateout("ax") error_code_or_bytes_read); 69 | asm!("pop dx", "pop cx", "pop bx", "pop ax"); 70 | } 71 | if is_read_success == 1 { 72 | return Err(ErrorCode::from_u8(error_code_or_bytes_read as u8).unwrap_or(ErrorCode::UnknownError)); 73 | } 74 | if error_code_or_bytes_read == 0 { 75 | // End of file 76 | break; 77 | } 78 | //total_bytes_read += error_code_or_bytes_read as usize; // = 1 79 | total_bytes_read += 1 as usize; 80 | buffer[buffer_write_pos] = tmp_stack_buffer[0]; 81 | } 82 | 83 | // fill the rest of the buffer with 0s 84 | for buffer_write_pos in total_bytes_read..buffer.len() { 85 | buffer[buffer_write_pos] = 0; 86 | } 87 | Ok(total_bytes_read) 88 | } 89 | 90 | // TODO check 91 | pub fn write(&self, buffer: &[u8]) -> Result { 92 | let mut total_bytes_written: usize = 0; 93 | for buffer_read_pos in 0..buffer.len() { 94 | let mut is_write_success: u16 = 1; // 0: success, 1: fail 95 | let mut error_code_or_bytes_written: u16 = 0; 96 | let mut tmp_stack_buffer: [u8; 1] = [0; 1]; // To be sure of the segment 97 | tmp_stack_buffer[0] = buffer[buffer_read_pos]; 98 | let tmp_buffer_ptr = tmp_stack_buffer.as_ptr(); 99 | unsafe { 100 | asm!("push ax", "push bx", "push cx", "push dx"); 101 | asm!("mov cx, 1", "mov ah, 0x40", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, in("dx") tmp_buffer_ptr, lateout("cx") is_write_success, lateout("ax") error_code_or_bytes_written); 102 | asm!("pop dx", "pop cx", "pop bx", "pop ax"); 103 | } 104 | if is_write_success == 1 { 105 | return Err(ErrorCode::from_u8(error_code_or_bytes_written as u8).unwrap_or(ErrorCode::UnknownError)); 106 | } 107 | //total_bytes_written += error_code_or_bytes_written as usize; // = 1 108 | total_bytes_written += 1 as usize; 109 | } 110 | Ok(total_bytes_written) 111 | } 112 | 113 | pub fn close(self) -> Result<(), ErrorCode> { 114 | self.close_with_ref() 115 | } 116 | 117 | fn close_with_ref(&self) -> Result<(), ErrorCode> { 118 | let mut is_close_success: u16 = 1; // 0: success, 1: fail 119 | let mut error_code: u16 = 0; // 6 = unknown handle 120 | unsafe { 121 | asm!("push ax", "push bx", "push cx", "push dx"); 122 | asm!("mov ah, 0x3e", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, lateout("cx") is_close_success, lateout("ax") error_code); 123 | asm!("pop dx", "pop cx", "pop bx", "pop ax"); 124 | } 125 | if is_close_success == 1 { 126 | return Err(ErrorCode::from_u8(error_code as u8).unwrap_or(ErrorCode::UnknownError)); 127 | } 128 | Ok(()) 129 | } 130 | 131 | /// Seek to an offset, in bytes, in a stream. 132 | /// Returns number of bytes from the start of the stream if success, or an error code otherwise. 133 | pub fn seek(&self, pos: SeekFrom) -> Result { 134 | let mut is_seek_success: u16 = 1; // 0: success, 1: fail 135 | let mut error_code_or_new_pos_low_from_start: u16 = 0; 136 | let mut new_pos_high_from_start: u16 = 0; 137 | let requested_relative_new_pos: u32 = pos.to_seek_offset(); 138 | let requested_relative_new_pos_low = (requested_relative_new_pos & 0xffff) as u16; 139 | let requested_relative_new_pos_high = ((requested_relative_new_pos >> 16) & 0xffff) as u16; 140 | let seek_from: u8 = pos.to_dos_seek_code(); 141 | unsafe { 142 | asm!("push ax", "push bx", "push cx", "push dx"); 143 | asm!("mov ah, 0x42", "int 0x21", "setc dl", "movzx cx, dl", in("bx") self.handle, in("cx") requested_relative_new_pos_high as u16, in("dx") requested_relative_new_pos_low, in("al") seek_from, lateout("cx") is_seek_success, lateout("ax") error_code_or_new_pos_low_from_start, lateout("dx") new_pos_high_from_start); 144 | asm!("pop dx", "pop cx", "pop bx", "pop ax"); 145 | } 146 | if is_seek_success == 1 { 147 | return Err(ErrorCode::from_u8(error_code_or_new_pos_low_from_start as u8).unwrap_or(ErrorCode::UnknownError)); 148 | } 149 | Ok((new_pos_high_from_start as u32) << 16 | (error_code_or_new_pos_low_from_start as u32)) 150 | } 151 | } 152 | 153 | impl Drop for File { 154 | fn drop(&mut self) { 155 | let _ = self.close_with_ref(); 156 | } 157 | } -------------------------------------------------------------------------------- /src/dos/io.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | pub fn inb(port: usize) -> u8 { 4 | let mut ret: u8; 5 | unsafe { 6 | asm!("in al, dx", out("al") ret, in("dx") port); 7 | } 8 | ret 9 | } 10 | 11 | pub fn inw(port: usize) -> u16 { 12 | let mut ret: u16; 13 | unsafe { asm!("in ax, dx", out("ax") ret, in("dx") port) } 14 | ret 15 | } 16 | 17 | pub fn outb(data: u8, port: usize) { 18 | unsafe { 19 | asm!("out dx, al", in("dx") port, in("al") data); 20 | } 21 | } 22 | 23 | pub fn outw(data: u16, port: usize) { 24 | unsafe { 25 | asm!("out dx, ax", in("dx") port, in("ax") data); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/dos/kbc.rs: -------------------------------------------------------------------------------- 1 | use super::io::{inb, outb}; 2 | 3 | // 8042 keyboard controller definition 4 | pub const KBC_CTRL: usize = 0x64; 5 | pub const KBC_DATA: usize = 0x60; 6 | pub const IO_WAIT: usize = 0x80; 7 | 8 | // status 9 | pub const KBC_OBF: u8 = 0x01; 10 | pub const KBC_IBF: u8 = 0x02; 11 | pub const KBC_BUSY: u8 = KBC_OBF | KBC_IBF; 12 | 13 | // 8042 command byte 14 | pub const KBC_GET_CTRL: u8 = 0x20; 15 | pub const KBC_SET_CTRL: u8 = 0x60; 16 | 17 | // 8042 control mode 18 | pub const KBC_DO_XLAT: u8 = 0x40; 19 | pub const KBC_DIS_MOUSE: u8 = 0x20; 20 | pub const KBC_DIS_KEY: u8 = 0x10; 21 | pub const KBC_SYS_FLAG: u8 = 0x04; 22 | pub const KBC_INT_MOUSE: u8 = 0x02; 23 | pub const KBC_INT_KEY: u8 = 0x01; 24 | 25 | // Device command byte 26 | pub const KBC_WRITE_LED: u8 = 0xED; 27 | pub const KBC_SET_REPEAT: u8 = 0xF3; 28 | pub const KBC_ACK: u8 = 0xFA; 29 | 30 | // 8259 programmable interrupt controller 31 | pub const PIC_MIMR: usize = 0x21; 32 | pub const PIC_IMR_KEY: u8 = 0x2; 33 | 34 | // read the status register of 8042 KBC 35 | // return data 36 | pub fn kbc_status() -> u8 { 37 | inb(KBC_CTRL) 38 | } 39 | 40 | pub fn kbc_command(cmd: u8) { 41 | loop { 42 | if kbc_status() & KBC_IBF == 0 { 43 | break; 44 | } 45 | inb(IO_WAIT); 46 | } 47 | outb(cmd, KBC_CTRL); 48 | } 49 | 50 | pub fn kbc_write(data: u8) { 51 | inb(KBC_DATA); 52 | loop { 53 | if kbc_status() & KBC_BUSY == 0 { 54 | break; 55 | } 56 | inb(IO_WAIT); 57 | } 58 | inb(IO_WAIT); 59 | outb(data, KBC_DATA); 60 | } 61 | 62 | pub fn kbc_read() -> u8 { 63 | loop { 64 | if kbc_status() & KBC_OBF != 0 { 65 | break; 66 | } 67 | inb(IO_WAIT); 68 | } 69 | inb(IO_WAIT); 70 | inb(KBC_DATA) 71 | } 72 | 73 | pub fn disable_keyint() { 74 | let mut imr: u8; 75 | imr = inb(PIC_MIMR); 76 | imr |= PIC_IMR_KEY; 77 | outb(imr, PIC_MIMR); 78 | } 79 | 80 | pub fn enable_keyint() { 81 | let mut imr: u8; 82 | imr = inb(PIC_MIMR); 83 | imr &= !PIC_IMR_KEY; 84 | outb(imr, PIC_MIMR); 85 | } 86 | -------------------------------------------------------------------------------- /src/dos/math.rs: -------------------------------------------------------------------------------- 1 | const fn num_bits() -> usize { core::mem::size_of::() * 8 } 2 | 3 | pub const fn log_2(x: usize) -> usize { 4 | num_bits::() as usize - x.leading_zeros() as usize - 1 5 | } -------------------------------------------------------------------------------- /src/dos/panic.rs: -------------------------------------------------------------------------------- 1 | use super::exit; 2 | use core::panic::PanicInfo; 3 | 4 | #[panic_handler] 5 | fn panic(_info: &PanicInfo) -> ! { 6 | println!("{}", _info); 7 | exit(1); 8 | } 9 | -------------------------------------------------------------------------------- /src/dos_tests/allocator_test.rs: -------------------------------------------------------------------------------- 1 | use rust_dos::*; 2 | use dos::*; 3 | 4 | #[allow(dead_code)] 5 | pub(crate) fn allocator_test() { 6 | let mut box1 = Box::new(5); 7 | assert_eq!(*box1, 5); 8 | *box1 = 10; 9 | assert_eq!(*box1, 10); 10 | 11 | let mut string1 = String::from("konnichiwa"); 12 | assert_eq!(string1, "konnichiwa"); 13 | string1 += " sekai"; 14 | assert_eq!(string1, "konnichiwa sekai"); 15 | let string2 = String::from("こんにちわ 世界!"); 16 | string1 += "! "; 17 | string1 += &*string2; 18 | assert_eq!(string1, "konnichiwa sekai! こんにちわ 世界!"); // Don't try to print this in the console, the result is weird. 19 | 20 | let mut vec1 = vec![12; 200]; 21 | assert_eq!(vec1.len(), 200); 22 | for i in 0..200 { 23 | assert_eq!(vec1[i], 12); 24 | } 25 | vec1.push(13); 26 | assert_eq!(vec1.len(), 201); 27 | assert_eq!(vec1[200], 13); 28 | vec1.resize(300, 14); 29 | assert_eq!(vec1.len(), 300); 30 | for i in 0..200 { 31 | assert_eq!(vec1[i], 12); 32 | } 33 | assert_eq!(vec1[200], 13); 34 | for i in 201..300 { 35 | assert_eq!(vec1[i], 14); 36 | } 37 | vec1.resize(10, 15); 38 | assert_eq!(vec1.len(), 10); 39 | for i in 0..10 { 40 | assert_eq!(vec1[i], 12); 41 | } 42 | } -------------------------------------------------------------------------------- /src/dos_tests/cooperative_multitasking_test.rs: -------------------------------------------------------------------------------- 1 | use dos::*; 2 | use rust_dos::*; 3 | 4 | #[allow(dead_code)] 5 | pub(crate) fn cooperative_multitasking_test() { 6 | add_cooperative_task!(task_2_main).unwrap(); 7 | println!("Hello from main task!"); 8 | yield_cooperative_task!(); 9 | println!("Hello from main task! (bis)"); 10 | yield_cooperative_task!(); 11 | println!("Hello from main task! (tris)"); 12 | } 13 | 14 | fn task_2_main() { 15 | add_cooperative_task!(task_3_main).unwrap(); 16 | for _ in 0..2 { 17 | let task_number = 2; 18 | let task2_string = String::from("Hello from task 2!"); 19 | println!("Message from task{}: {}", task_number, task2_string); 20 | yield_cooperative_task!(); 21 | } 22 | } 23 | 24 | fn task_3_main() { 25 | for _ in 0..2 { 26 | let task_number = 3; 27 | let task2_string = String::from("Hello from task 3!"); 28 | println!("Message from task{}: {}", task_number, task2_string); 29 | yield_cooperative_task!(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/dos_tests/datetime.rs: -------------------------------------------------------------------------------- 1 | use rust_dos::{*, dos::datetime::{Date, Time}}; 2 | 3 | #[allow(dead_code)] 4 | pub(crate) fn datetime_test() { 5 | let date = Date::now(); 6 | let time = Time::now(); 7 | 8 | println!("Today's date is {:?}", date); 9 | println!("Current time is {:?}", time); 10 | 11 | /* Commented out because this will set the hardware clock on DOS 3.3+ 12 | let mut date = Date::now(); 13 | let mut time = Time::now(); 14 | 15 | date.day += 1; 16 | date.save().unwrap(); 17 | let date2 = Date::now(); 18 | println!("The date has been set to {:?}", date2); 19 | 20 | // Note that this does not work in DOSBox 21 | time.hour += 1; 22 | time.save().unwrap(); 23 | let time2 = Time::now(); 24 | println!("The time has been set to {:?}", time2); 25 | */ 26 | } -------------------------------------------------------------------------------- /src/dos_tests/file.rs: -------------------------------------------------------------------------------- 1 | use rust_dos::*; 2 | 3 | #[allow(dead_code)] 4 | pub(crate) fn file_read_test() { 5 | let test_file = dos::file::File::open("C:\\AUTOEXEC.BAT"); 6 | let test_file = test_file.unwrap_or(dos::file::File::open("README.md").unwrap()); 7 | let mut buffer = [0; 100]; 8 | let bytes_read = test_file.read(&mut buffer).unwrap(); 9 | println!("{} bytes read", bytes_read); 10 | println!("{}", core::str::from_utf8(&buffer).unwrap()); 11 | match test_file.close() { 12 | Ok(_) => println!("File closed"), 13 | Err(_) => println!("Error closing file") 14 | } 15 | } -------------------------------------------------------------------------------- /src/dos_tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod file; 2 | pub(crate) mod allocator_test; 3 | pub(crate) mod datetime; 4 | pub(crate) mod cooperative_multitasking_test; 5 | -------------------------------------------------------------------------------- /src/dpkey.rs: -------------------------------------------------------------------------------- 1 | // dpkey.rs: Dos Play with KEY board controler module 2 | // system scan code to ascii code demo 3 | // 4 | // Details: 5 | // When you hit the keyboard from A to ' , 6 | // this module reads the system scan code, 7 | // converts it, and displays the ASCII code. 8 | // Enjoy! 9 | // 10 | // Dependency: 11 | // rust_dos::dos::kbc.rs 12 | // 13 | // Note: 14 | // As far as I've tested, qemu doesn't support this operation, so play with bochs. 15 | 16 | use crate::dos::kbc::*; 17 | 18 | const MOD_ALT: u8 = 8; 19 | const MOD_CTRL: u8 = 4; 20 | const MOD_SHIFT: u8 = 2; 21 | const MOD_CAPS: u8 = 1; 22 | 23 | const ESC: u8 = 0x01; 24 | const CTRL: u8 = 0x1D; 25 | const KEY_A: u8 = 0x1E; 26 | const KEY_SQ: u8 = 0x28; 27 | const SHIFT: u8 = 0x2A; 28 | const ALT: u8 = 0x38; 29 | const CAPS: u8 = 0x3A; 30 | 31 | static MAP_PLAIN: [u8; 11] = [ 32 | b'a', b's', b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';', b':', 33 | ]; 34 | static MAP_CTRL: [u8; 11] = [ 35 | 0x01, 0x13, 0x04, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 0x7F, 0x7F, 36 | ]; 37 | static MAP_SHIFT: [u8; 11] = [ 38 | b'A', b'S', b'D', b'F', b'G', b'H', b'J', b'K', b'L', b':', b'"', 39 | ]; 40 | static MAP_ALT: [u8; 11] = [ 41 | 0x81, 0x93, 0x84, 0x86, 0x87, 0x88, 0x8A, 0x8B, 0x8C, 0xFF, 0xFF, 42 | ]; 43 | 44 | pub fn keymap() { 45 | let mut ret: u8; 46 | let mut up: u8; 47 | let mut ch: u8; 48 | let mut map: &[u8]; 49 | 50 | let mut modifier: u8 = 0; 51 | let mut capslock: u8 = 0; 52 | 53 | kbc_command(KBC_GET_CTRL); 54 | ret = kbc_read(); 55 | kbc_command(KBC_SET_CTRL); 56 | ret &= !KBC_INT_KEY; 57 | kbc_write(ret); 58 | 59 | loop { 60 | ret = kbc_read(); 61 | up = ret & 0x80; 62 | 63 | ret &= 0x7F; 64 | match ret { 65 | ESC => break, 66 | ALT => { 67 | if up != 0 { 68 | modifier &= !MOD_ALT; 69 | } else { 70 | modifier |= MOD_ALT; 71 | } 72 | continue; 73 | } 74 | CTRL => { 75 | if up != 0 { 76 | modifier &= !MOD_CTRL; 77 | } else { 78 | modifier |= MOD_CTRL; 79 | } 80 | continue; 81 | } 82 | SHIFT => { 83 | if up != 0 { 84 | modifier &= !MOD_SHIFT; 85 | } else { 86 | modifier |= MOD_SHIFT; 87 | } 88 | continue; 89 | } 90 | CAPS => { 91 | if up != 0 { 92 | modifier &= !MOD_CAPS; 93 | capslock ^= 1; 94 | } else { 95 | modifier |= MOD_CAPS; 96 | } 97 | continue; 98 | } 99 | KEY_A..=KEY_SQ => { 100 | if up != 0 { 101 | continue; 102 | } else { 103 | if modifier & MOD_SHIFT != 0 { 104 | map = &MAP_SHIFT; 105 | } else if modifier & MOD_CTRL != 0 { 106 | map = &MAP_CTRL; 107 | } else if modifier & MOD_ALT != 0 { 108 | map = &MAP_ALT; 109 | } else { 110 | map = &MAP_PLAIN; 111 | } 112 | } 113 | ch = if let Some(&num) = map.get((ret - KEY_A) as usize) { 114 | num 115 | } else { 116 | b'X' 117 | }; 118 | if capslock == 1 { 119 | if modifier & MOD_SHIFT == 0 { 120 | if ch >= b'a' && ch <= b'z' { 121 | ch -= 0x20; 122 | } 123 | } else { 124 | if ch >= b'A' && ch <= b'Z' { 125 | ch += 0x20; 126 | } 127 | } 128 | } 129 | } 130 | _ => continue, 131 | } 132 | print!("{:02X}", ch); 133 | print!(" "); 134 | } 135 | 136 | kbc_command(KBC_GET_CTRL); 137 | ret = kbc_read(); 138 | kbc_command(KBC_SET_CTRL); 139 | ret |= KBC_INT_KEY; 140 | kbc_write(ret); 141 | } 142 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(alloc_error_handler)] 3 | 4 | #[macro_use] 5 | pub mod dos; 6 | pub mod dpkey; 7 | extern crate rlibc; 8 | extern crate alloc; 9 | 10 | use crate::dos::allocator::GLOBAL_ALLOCATOR; 11 | use crate::dos::cooperative_multitasking::TASKING; 12 | 13 | #[link_section = ".startup"] 14 | #[no_mangle] 15 | fn _start() -> ! { 16 | unsafe { 17 | GLOBAL_ALLOCATOR.init(); 18 | TASKING.init(); // Relies on the allocator 19 | } 20 | extern "Rust" { 21 | fn main() -> (); 22 | } 23 | unsafe { 24 | main(); 25 | } 26 | dos::exit(0); 27 | } 28 | 29 | #[macro_export] 30 | macro_rules! entry { 31 | ($path:path) => { 32 | #[export_name = "main"] 33 | pub fn __main() -> () { 34 | // type check the given path 35 | let f: fn() -> () = $path; 36 | f() 37 | } 38 | }; 39 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | 6 | mod dos_tests; 7 | 8 | use rust_dos::*; 9 | use crate::dos_tests::allocator_test::allocator_test; 10 | use crate::dos_tests::datetime::datetime_test; 11 | use crate::dos_tests::file::file_read_test; 12 | use crate::dos_tests::cooperative_multitasking_test::cooperative_multitasking_test; 13 | 14 | entry!(main); 15 | 16 | fn main() { 17 | allocator_test(); 18 | file_read_test(); 19 | datetime_test(); 20 | cooperative_multitasking_test(); 21 | println!("Hello, World!"); 22 | } 23 | --------------------------------------------------------------------------------