├── rust-toolchain ├── .gitignore ├── kernel ├── README.org ├── build.rs ├── tests │ ├── basic_boot.rs │ ├── heap_allocation.rs │ ├── stack_overflow.rs │ └── interrupt_wrap.rs ├── Cargo.toml └── src │ ├── serial.rs │ ├── memory │ ├── allocator.rs │ └── kernel_info.rs │ ├── lib.rs │ ├── time.rs │ ├── main.rs │ ├── vfs.rs │ ├── gdt.rs │ ├── vga_buffer.rs │ └── message.rs ├── virtio_net ├── README.org ├── Cargo.toml └── src │ └── main.rs ├── doc ├── euralios.gif ├── journal │ ├── xx-usb.org │ ├── img │ │ ├── 12-01-gpf.png │ │ ├── 16-01-arp.png │ │ ├── 19-01-PIT.png │ │ ├── 20-01-dns.png │ │ ├── 22-02-ls.png │ │ ├── 12-02-class.png │ │ ├── 13-01-open.png │ │ ├── 15-01-panic.png │ │ ├── 17-01-dhcp.png │ │ ├── 18-01-write.png │ │ ├── 18-02-read.png │ │ ├── 18-03-gopher.png │ │ ├── 18-04-broken.png │ │ ├── 21-04-gopher.png │ │ ├── 22-06-delete.png │ │ ├── 11-02-messages.pdf │ │ ├── 13-02-address.png │ │ ├── 20-02-hngopher.png │ │ ├── 22-01-welcome.png │ │ ├── 22-03-listing.png │ │ ├── 22-04-testing.png │ │ ├── 09-01-page-fault.png │ │ ├── 19-02-TSC-per-PIT.png │ │ ├── 19-03-timer-test.png │ │ ├── 21-02-user-panic.png │ │ ├── 21-03-writer-sys.png │ │ ├── 09-02-vga-listener.png │ │ ├── 14-01-rtl8139-reset.png │ │ ├── 14-02-memory-chunk.png │ │ ├── 21-01-kernel-panic.png │ │ ├── 22-05-path-testing.png │ │ ├── 27-01-basic-editor.png │ │ ├── multi_level_bitmap.pdf │ │ ├── 11-01-short-messages.pdf │ │ └── 26-01-shell-in-shell.png │ ├── xx-wifi.org │ ├── xx-shell.org │ ├── 24-directories.org │ ├── 20-dns.org │ ├── 17-tcp-stack.org │ ├── 23-keyboard.org │ ├── 25-multiple-users.org │ ├── 10-stdlib.org │ ├── 08-faster-ipc.org │ ├── 26-servers.org │ └── 16-arp.org ├── memory.org └── kernel_api.org ├── gopher ├── floodgap.png ├── Cargo.toml └── README.org ├── timing_test ├── README.org ├── Cargo.toml └── src │ └── main.rs ├── pci ├── README.org ├── Cargo.toml └── src │ ├── ports.rs │ ├── device.rs │ └── main.rs ├── rtl8139 ├── README.org └── Cargo.toml ├── .gitmodules ├── tcp ├── README.org ├── Cargo.toml └── src │ ├── dhcp.rs │ └── dns.rs ├── arp ├── Cargo.toml ├── README.org └── src │ └── main.rs ├── init ├── Cargo.toml └── README.org ├── login ├── Cargo.toml ├── README.org └── src │ └── main.rs ├── shell ├── Cargo.toml └── src │ └── main.rs ├── edit ├── Cargo.toml └── README.org ├── ramdisk ├── Cargo.toml └── src │ └── main.rs ├── .cargo └── config.toml ├── euralios_std ├── README.org ├── Cargo.toml ├── src │ ├── sys.rs │ ├── memory.rs │ ├── debug.rs │ ├── console.rs │ ├── net.rs │ ├── ports.rs │ ├── thread.rs │ ├── time.rs │ ├── env.rs │ ├── lib.rs │ └── io.rs └── tests │ └── system_test.rs ├── keyboard ├── Cargo.toml ├── README.org └── src │ └── main.rs ├── Cargo.toml ├── vga_driver ├── Cargo.toml └── README.org ├── x86_64-euralios.json ├── LICENSE └── makefile /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *~ 3 | -------------------------------------------------------------------------------- /kernel/README.org: -------------------------------------------------------------------------------- 1 | * EuraliOS kernel "Merriwig" 2 | 3 | -------------------------------------------------------------------------------- /virtio_net/README.org: -------------------------------------------------------------------------------- 1 | * Virtio network card driver 2 | 3 | -------------------------------------------------------------------------------- /doc/euralios.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/euralios.gif -------------------------------------------------------------------------------- /doc/journal/xx-usb.org: -------------------------------------------------------------------------------- 1 | * USB devices 2 | 3 | https://github.com/rust-osdev/xhci 4 | -------------------------------------------------------------------------------- /gopher/floodgap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/gopher/floodgap.png -------------------------------------------------------------------------------- /timing_test/README.org: -------------------------------------------------------------------------------- 1 | * Timing test 2 | 3 | A user-space program to test timing functions 4 | 5 | 6 | -------------------------------------------------------------------------------- /doc/journal/img/12-01-gpf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/12-01-gpf.png -------------------------------------------------------------------------------- /doc/journal/img/16-01-arp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/16-01-arp.png -------------------------------------------------------------------------------- /doc/journal/img/19-01-PIT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/19-01-PIT.png -------------------------------------------------------------------------------- /doc/journal/img/20-01-dns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/20-01-dns.png -------------------------------------------------------------------------------- /doc/journal/img/22-02-ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/22-02-ls.png -------------------------------------------------------------------------------- /doc/journal/img/12-02-class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/12-02-class.png -------------------------------------------------------------------------------- /doc/journal/img/13-01-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/13-01-open.png -------------------------------------------------------------------------------- /doc/journal/img/15-01-panic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/15-01-panic.png -------------------------------------------------------------------------------- /doc/journal/img/17-01-dhcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/17-01-dhcp.png -------------------------------------------------------------------------------- /doc/journal/img/18-01-write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/18-01-write.png -------------------------------------------------------------------------------- /doc/journal/img/18-02-read.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/18-02-read.png -------------------------------------------------------------------------------- /doc/journal/img/18-03-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/18-03-gopher.png -------------------------------------------------------------------------------- /doc/journal/img/18-04-broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/18-04-broken.png -------------------------------------------------------------------------------- /doc/journal/img/21-04-gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/21-04-gopher.png -------------------------------------------------------------------------------- /doc/journal/img/22-06-delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/22-06-delete.png -------------------------------------------------------------------------------- /doc/journal/img/11-02-messages.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/11-02-messages.pdf -------------------------------------------------------------------------------- /doc/journal/img/13-02-address.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/13-02-address.png -------------------------------------------------------------------------------- /doc/journal/img/20-02-hngopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/20-02-hngopher.png -------------------------------------------------------------------------------- /doc/journal/img/22-01-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/22-01-welcome.png -------------------------------------------------------------------------------- /doc/journal/img/22-03-listing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/22-03-listing.png -------------------------------------------------------------------------------- /doc/journal/img/22-04-testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/22-04-testing.png -------------------------------------------------------------------------------- /doc/journal/img/09-01-page-fault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/09-01-page-fault.png -------------------------------------------------------------------------------- /doc/journal/img/19-02-TSC-per-PIT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/19-02-TSC-per-PIT.png -------------------------------------------------------------------------------- /doc/journal/img/19-03-timer-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/19-03-timer-test.png -------------------------------------------------------------------------------- /doc/journal/img/21-02-user-panic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/21-02-user-panic.png -------------------------------------------------------------------------------- /doc/journal/img/21-03-writer-sys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/21-03-writer-sys.png -------------------------------------------------------------------------------- /doc/journal/img/09-02-vga-listener.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/09-02-vga-listener.png -------------------------------------------------------------------------------- /doc/journal/img/14-01-rtl8139-reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/14-01-rtl8139-reset.png -------------------------------------------------------------------------------- /doc/journal/img/14-02-memory-chunk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/14-02-memory-chunk.png -------------------------------------------------------------------------------- /doc/journal/img/21-01-kernel-panic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/21-01-kernel-panic.png -------------------------------------------------------------------------------- /doc/journal/img/22-05-path-testing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/22-05-path-testing.png -------------------------------------------------------------------------------- /doc/journal/img/27-01-basic-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/27-01-basic-editor.png -------------------------------------------------------------------------------- /doc/journal/img/multi_level_bitmap.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/multi_level_bitmap.pdf -------------------------------------------------------------------------------- /doc/journal/img/11-01-short-messages.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/11-01-short-messages.pdf -------------------------------------------------------------------------------- /doc/journal/img/26-01-shell-in-shell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bendudson/EuraliOS/HEAD/doc/journal/img/26-01-shell-in-shell.png -------------------------------------------------------------------------------- /pci/README.org: -------------------------------------------------------------------------------- 1 | * PCI bus driver 2 | 3 | This runs as a Ring 3 program with IO permissions, so it can read and 4 | write to ports. 5 | 6 | -------------------------------------------------------------------------------- /rtl8139/README.org: -------------------------------------------------------------------------------- 1 | * RTL8139 network card driver 2 | 3 | This program runs in user space and provides an interface to the 4 | RTL8139 network card. 5 | -------------------------------------------------------------------------------- /kernel/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rustc-link-arg=-Ttext-segment=5000000"); 3 | println!("cargo:rustc-link-arg=-Trodata-segment=5100000"); 4 | } 5 | -------------------------------------------------------------------------------- /doc/journal/xx-wifi.org: -------------------------------------------------------------------------------- 1 | * WiFi 2 | 3 | https://forum.osdev.org/viewtopic.php?f=1&t=32406 4 | 5 | https://wiki.osdev.org/802.11 6 | 7 | Thinkpad: Network controller: Intel Corporation Wireless 8265 / 8275 (rev 78) 8 | 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/vga"] 2 | path = external/vga 3 | url = git@github.com:bendudson/vga.git 4 | [submodule "external/pc-keyboard"] 5 | path = external/pc-keyboard 6 | url = https://github.com/bendudson/pc-keyboard 7 | -------------------------------------------------------------------------------- /tcp/README.org: -------------------------------------------------------------------------------- 1 | * TCP stack server 2 | 3 | Uses the [[https://docs.rs/smoltcp/latest/smoltcp/][smoltcp]] crate to implement a TCP stack, and enable user 4 | programs to open, read and write TCP sockets. Includes DHCP and basic 5 | DNS facilities. 6 | 7 | -------------------------------------------------------------------------------- /arp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "arp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /init/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "init" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /gopher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gopher" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /login/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "login" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /shell/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shell" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /edit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "edit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | 11 | -------------------------------------------------------------------------------- /rtl8139/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtl8139" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /timing_test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "timing_test" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /virtio_net/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtio_net" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | -------------------------------------------------------------------------------- /ramdisk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ramdisk" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | spin = "0.5.2" 11 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | build-std-features = ["compiler-builtins-mem"] 3 | build-std = ["core", "compiler_builtins", "alloc"] 4 | 5 | [build] 6 | target = "x86_64-euralios.json" 7 | 8 | [target.'cfg(target_os = "none")'] 9 | runner = "bootimage runner" 10 | rustflags = ["-C", "relocation-model=static"] 11 | -------------------------------------------------------------------------------- /euralios_std/README.org: -------------------------------------------------------------------------------- 1 | * EuraliOS standard library 2 | 3 | This library provides a subset of the Rust standard library (=std=) 4 | on top of EuraliOS-specific low level routines. 5 | 6 | 7 | ** EuraliOS functions 8 | 9 | The =euralios_std::syscalls= module contains functions which 10 | wrap kernel system calls. 11 | -------------------------------------------------------------------------------- /keyboard/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "keyboard" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | pc-keyboard = { path = "../external/pc-keyboard" } 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "kernel", 5 | "keyboard", 6 | "euralios_std", 7 | "pci", 8 | "rtl8139", 9 | "virtio_net", 10 | "arp", 11 | "tcp", 12 | "gopher", 13 | "timing_test", 14 | "vga_driver", 15 | "ramdisk", 16 | "shell", 17 | "edit", 18 | "login", 19 | "init", 20 | ] 21 | 22 | -------------------------------------------------------------------------------- /pci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pci" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | spin = "0.5.2" 11 | 12 | [dependencies.lazy_static] 13 | version = "1.0" 14 | features = ["spin_no_std"] 15 | -------------------------------------------------------------------------------- /euralios_std/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "euralios_std" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | linked_list_allocator = "0.10.2" 10 | serde_json = { version = "1.0", default-features = false, features = ["alloc"] } 11 | spin = "0.5.2" 12 | -------------------------------------------------------------------------------- /vga_driver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vga_driver" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | vga = { path = "../external/vga" } 11 | spin = "0.5.2" 12 | 13 | [dependencies.lazy_static] 14 | version = "1.0" 15 | features = ["spin_no_std"] 16 | -------------------------------------------------------------------------------- /euralios_std/src/sys.rs: -------------------------------------------------------------------------------- 1 | ///! This is mainly from the Rust std library 2 | ///! 3 | ///! 4 | 5 | pub mod path { 6 | #[inline] 7 | pub fn is_sep_byte(b: u8) -> bool { 8 | b == b'/' 9 | } 10 | 11 | #[inline] 12 | pub fn is_verbatim_sep(b: u8) -> bool { 13 | b == b'/' 14 | } 15 | 16 | pub const MAIN_SEP_STR: &str = "/"; 17 | pub const MAIN_SEP: char = '/'; 18 | } 19 | -------------------------------------------------------------------------------- /euralios_std/src/memory.rs: -------------------------------------------------------------------------------- 1 | 2 | extern crate alloc; 3 | use linked_list_allocator::LockedHeap; 4 | 5 | #[global_allocator] 6 | static ALLOCATOR: LockedHeap = LockedHeap::empty(); 7 | 8 | pub fn init(heap_start: usize, heap_size: usize) { 9 | unsafe {ALLOCATOR.lock().init(heap_start as *mut u8, heap_size);} 10 | } 11 | 12 | // Allocator error handler 13 | #[alloc_error_handler] 14 | fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { 15 | panic!("allocation error: {:?}", layout) 16 | } 17 | -------------------------------------------------------------------------------- /timing_test/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use euralios_std::{debug_println, 5 | syscalls::{self, STDIN}, 6 | time}; 7 | 8 | #[no_mangle] 9 | fn main() { 10 | loop { 11 | let _ = syscalls::receive(&STDIN); 12 | debug_println!("[timing_test] TSC: {} microseconds: {}", 13 | time::time_stamp_counter(), 14 | time::microseconds_monotonic()); 15 | syscalls::thread_yield(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /x86_64-euralios.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", 4 | "arch": "x86_64", 5 | "target-endian": "little", 6 | "target-pointer-width": "64", 7 | "target-c-int-width": "32", 8 | "os": "none", 9 | "executables": true, 10 | "linker-flavor": "ld", 11 | "linker": "ld", 12 | "panic-strategy": "abort", 13 | "disable-redzone": true, 14 | "features": "-mmx,-sse,+soft-float" 15 | } 16 | -------------------------------------------------------------------------------- /tcp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tcp" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | euralios_std = { path = "../euralios_std" } 10 | smoltcp = { version = "0.8.1", default-features = false, features = ["alloc", "medium-ethernet", "socket-tcp", "socket-udp", "socket-dhcpv4", "proto-ipv4", "proto-dhcpv4"] } 11 | spin = "0.5.2" 12 | bit_field = "0.10.0" 13 | 14 | [dependencies.lazy_static] 15 | version = "1.0" 16 | features = ["spin_no_std"] 17 | -------------------------------------------------------------------------------- /keyboard/README.org: -------------------------------------------------------------------------------- 1 | * Keyboard driver program 2 | 3 | This program runs in Ring 3 and needs IO permissions to read data from port 0x60. 4 | It uses the =await_interrupt= EuraliOS syscall to wait for an interrupt to occur; 5 | reads the scancode from port 0x60; passes the scancode to the [[https://docs.rs/pc-keyboard/latest/pc_keyboard/][pc_keyboard]] crate; 6 | and then sends the corresponding character (if any) in a message to =STDOUT=. 7 | 8 | The =init= program starts this keyboard driver, configuring it so that =keyboard='s 9 | STDOUT is connected to the =init='s STDIN. 10 | -------------------------------------------------------------------------------- /kernel/tests/basic_boot.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(custom_test_frameworks)] 4 | #![test_runner(blog_os::test_runner)] 5 | #![reexport_test_harness_main = "test_main"] 6 | 7 | use blog_os::println; 8 | use core::panic::PanicInfo; 9 | 10 | #[no_mangle] // don't mangle the name of this function 11 | pub extern "C" fn _start() -> ! { 12 | test_main(); 13 | 14 | loop {} 15 | } 16 | 17 | #[panic_handler] 18 | fn panic(info: &PanicInfo) -> ! { 19 | blog_os::test_panic_handler(info) 20 | } 21 | 22 | #[test_case] 23 | fn test_println() { 24 | println!("test_println output"); 25 | } 26 | -------------------------------------------------------------------------------- /doc/journal/xx-shell.org: -------------------------------------------------------------------------------- 1 | * Shell 2 | 3 | Some possible ways to add a shell: 4 | 1. Port [[https://github.com/redox-os/ion][Ion]] from [[https://www.redox-os.org/][Redox-OS]]. 5 | 6 | 2. Use an embedded scripting language with =no_std= 7 | implementation in Rust 8 | 9 | There are [[https://github.com/rust-unofficial/awesome-rust#scripting][lists]] of [[https://github.com/alilleybrinker/langs-in-rust][languages in Rust]] 10 | 11 | - [[https://github.com/rhaiscript/rhai][Rhai]] 12 | - [[https://github.com/rune-rs/rune][Rune]] 13 | 14 | 3. Implement something simple to start with, with just the 15 | basic functionality needed 16 | -------------------------------------------------------------------------------- /arp/README.org: -------------------------------------------------------------------------------- 1 | * Address Resolution Protocol program 2 | 3 | This is a program that demonstrates how to send and receive [[https://en.wikipedia.org/wiki/Address_Resolution_Protocol][Address 4 | Resolution Protocol (ARP)]] packets through a network card (assumed to 5 | be at =/dev/nic=), to look up hardware (MAC) addresses from IP 6 | addresses. More details are given in the [[../doc/journal/16-arp.org][journal section 16]]. 7 | 8 | *Note* This is a simple implementation that is not suitable for actual use: 9 | It assumes that it's the only process using the network card, for example. 10 | The the =tcp= program implements a much more complete TCP stack by using 11 | the smoltcp]] crate. 12 | -------------------------------------------------------------------------------- /edit/README.org: -------------------------------------------------------------------------------- 1 | * Text editor 2 | 3 | User program to edit plain ASCII text files. 4 | 5 | ** How to use 6 | 7 | Running the =/bin/edit filename= will open or create the file 8 | specified in the first argument. 9 | 10 | #+CAPTION: Text editor with line numbers 11 | #+NAME: fig-edit 12 | [[../doc/journal/img/27-01-basic-editor.png]] 13 | 14 | =Ctrl-S= saves the file, and =Ctrl-Q= quits the program. Arrow keys 15 | move the editing cursor, and =PgUp= / =PgDn= move the page up and 16 | down. 17 | 18 | ** How does it work 19 | 20 | Uses a [[https://en.wikipedia.org/wiki/Piece_table][Piece table]] to manage text changes. See [[../doc/journal/27-text-editor.org][Journal section 27]] for 21 | some details. 22 | -------------------------------------------------------------------------------- /virtio_net/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use euralios_std::{println, 5 | syscalls, 6 | message::{self, rcall, pci, MessageData}}; 7 | 8 | #[no_mangle] 9 | fn main() { 10 | println!("[virtio-net] Starting driver"); 11 | 12 | let handle = syscalls::open("/pci", message::O_READ).expect("Couldn't open pci"); 13 | 14 | // Use PCI program to look for device 15 | let (msg_type, md_address, _) = rcall(&handle, pci::FIND_DEVICE, 16 | 0x1AF4.into(), 0x1000.into(), 17 | None).unwrap(); 18 | let address = md_address.value(); 19 | if msg_type != pci::ADDRESS { 20 | println!("[virtio-net] Device not found. Exiting."); 21 | return; 22 | } 23 | println!("[virtio-net] Found at address: {:08X}", address); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /euralios_std/src/debug.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use core::fmt; 3 | 4 | struct Writer {} 5 | 6 | impl fmt::Write for Writer { 7 | fn write_str(&mut self, s: &str) -> fmt::Result { 8 | unsafe { 9 | asm!("mov rax, 2", // syscall function 10 | "syscall", 11 | in("rdi") s.as_ptr(), // First argument 12 | in("rsi") s.len(), // Second argument 13 | out("rcx") _, 14 | out("r11") _); 15 | } 16 | Ok(()) 17 | } 18 | } 19 | 20 | pub fn _print(args: fmt::Arguments) { 21 | use core::fmt::Write; 22 | Writer{}.write_fmt(args).unwrap(); 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! debug_print { 27 | ($($arg:tt)*) => ($crate::debug::_print(format_args!($($arg)*))); 28 | } 29 | 30 | #[macro_export] 31 | macro_rules! debug_println { 32 | () => ($crate::debug_print!("\n")); 33 | ($($arg:tt)*) => ($crate::debug_print!("{}\n", format_args!($($arg)*))); 34 | } 35 | -------------------------------------------------------------------------------- /login/README.org: -------------------------------------------------------------------------------- 1 | * Login process 2 | 3 | This is launched by =init=. It waits for a user to enter their login 4 | details, checks them and launches a shell for the user. 5 | 6 | This is the only part of EuraliOS that really knows about users, or 7 | distinguishes root from non-root users. User permissions are 8 | controlled by the virtual file system (VFS) they can see. When =root= 9 | logs in then =login= starts a shell that shares its VFS, and so root 10 | can modify essential parts of the system. When another user logs in 11 | they get a custom VFS that only contains some paths such as their home 12 | directory and read-only access to =bin=. 13 | 14 | The behaviour of this program is currently hard-wired, but the idea is 15 | that the user names, passwords and allowed paths would be contained in 16 | a configuration file that users would not have direct access to. To 17 | enable users to change their own passwords, login could mount a 18 | communication handle in each user's VFS that waits for a message. 19 | -------------------------------------------------------------------------------- /euralios_std/tests/system_test.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(custom_test_frameworks)] 4 | #![test_runner(crate::test_runner)] 5 | #![reexport_test_harness_main = "test_main"] 6 | 7 | use euralios_std::{print, println}; 8 | 9 | #[no_mangle] 10 | fn main() { 11 | println!("EuraliOS system test"); 12 | println!("===================="); 13 | #[cfg(test)] 14 | test_main(); 15 | } 16 | 17 | #[cfg(test)] 18 | mod tests { 19 | #[test_case] 20 | fn empty_test() { 21 | } 22 | } 23 | 24 | // Custom test framework 25 | 26 | pub trait Testable { 27 | fn run(&self) -> (); 28 | } 29 | 30 | // Implement Testable trait for all types with Fn() trait 31 | impl Testable for T 32 | where 33 | T: Fn(), 34 | { 35 | fn run(&self) { 36 | print!("{}...\t", core::any::type_name::()); 37 | self(); 38 | println!("[ok]"); 39 | } 40 | } 41 | 42 | pub fn test_runner(tests: &[&dyn Testable]) { 43 | println!("Running {} tests", tests.len()); 44 | for test in tests { 45 | test.run(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /kernel/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kernel" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [[test]] 7 | name = "stack_overflow" 8 | harness = false 9 | 10 | [dependencies] 11 | bootloader = { version = "0.9.23", features = ["map_physical_memory"]} 12 | volatile = "0.2.6" 13 | spin = "0.5.2" 14 | x86_64 = "0.14.2" 15 | uart_16550 = "0.2.0" 16 | pic8259 = "0.10.1" 17 | linked_list_allocator = "0.10.2" 18 | object = { version = "0.27.1", default-features = false, features = ["read"] } 19 | 20 | [dependencies.lazy_static] 21 | version = "1.0" 22 | features = ["spin_no_std"] 23 | 24 | [package.metadata.bootimage] 25 | run-args = ["-cpu", "Skylake-Client-v3", "-nic", "user,model=rtl8139,hostfwd=tcp::5555-:23"] 26 | #run-args = ["-netdev", "user,id=u1", "-device", "rtl8139,netdev=u1", "-object", "filter-dump,id=f1,netdev=u1,file=dump.dat"] 27 | 28 | test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", 29 | "-display", "none"] 30 | test-success-exit-code = 33 # (0x10 << 1) | 1 31 | test-timeout = 300 # (in seconds) 32 | -------------------------------------------------------------------------------- /init/README.org: -------------------------------------------------------------------------------- 1 | * Init process "Hyacinth" 2 | 3 | This is the first user (Ring 3) process created by kernel Merriwig. 4 | It is responsible for setting up all other processes, including 5 | drivers and user login consoles. 6 | 7 | The startup consists of 8 | - Start the =keyboard= process 9 | - Wait for a message from the kernel containing the video memory 10 | handle. 11 | - Start the =VGA= process and send it the video memory handle. 12 | - Start =ramdisk=, mounted on =/ramdisk=. Write some executable 13 | files to =/ramdisk/bin/=. Create directories for =root= and =user= 14 | home directories. 15 | - Start =pci=, =rtl8139= network driver and =tcp= programs. 16 | 17 | After this it enters a loop waiting for input from the keyboard. 18 | =F= keys switch consoles, redirecting keyboard to different programs 19 | and switching the active VGA buffer. 20 | 21 | * Known issues 22 | 23 | - Message sends and receives block and do not yet have any timeout. 24 | The result is that if a program is not responding when =init= 25 | tries to send a character then =init= will be suspended and the whole 26 | system will become unresponsive. The solution is to add message 27 | send & receive timeouts. 28 | -------------------------------------------------------------------------------- /kernel/src/serial.rs: -------------------------------------------------------------------------------- 1 | use uart_16550::SerialPort; 2 | use spin::Mutex; 3 | use lazy_static::lazy_static; 4 | 5 | lazy_static! { 6 | pub static ref SERIAL1: Mutex = { 7 | let mut serial_port = unsafe { SerialPort::new(0x3F8) }; 8 | serial_port.init(); 9 | Mutex::new(serial_port) 10 | }; 11 | } 12 | 13 | #[doc(hidden)] 14 | pub fn _print(args: ::core::fmt::Arguments) { 15 | use core::fmt::Write; 16 | use x86_64::instructions::interrupts; 17 | 18 | interrupts::without_interrupts(|| { 19 | SERIAL1 20 | .lock() 21 | .write_fmt(args) 22 | .expect("Printing to serial failed"); 23 | }); 24 | } 25 | 26 | /// Prints to the host through the serial interface. 27 | #[macro_export] 28 | macro_rules! serial_print { 29 | ($($arg:tt)*) => { 30 | $crate::serial::_print(format_args!($($arg)*)); 31 | }; 32 | } 33 | 34 | /// Prints to the host through the serial interface, appending a newline. 35 | #[macro_export] 36 | macro_rules! serial_println { 37 | () => ($crate::serial_print!("\n")); 38 | ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); 39 | ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( 40 | concat!($fmt, "\n"), $($arg)*)); 41 | } 42 | 43 | -------------------------------------------------------------------------------- /kernel/src/memory/allocator.rs: -------------------------------------------------------------------------------- 1 | //! Kernel heap allocator 2 | //! 3 | //! Uses the `linked_list_allocator` crate to manage a fixed size heap 4 | //! Used to store kernel data structures, including: 5 | //! - Thread objects (in Box) 6 | //! - Stacks for kernel threads 7 | 8 | use x86_64::{ 9 | structures::paging::{ 10 | mapper::MapToError, FrameAllocator, Mapper, PageTableFlags, Size4KiB, 11 | }, 12 | VirtAddr, 13 | }; 14 | 15 | // Fixed heap for the kernel 16 | pub const HEAP_START: usize = 0x_4444_4444_0000; 17 | pub const HEAP_SIZE: usize = 1024 * 1024; // 1 Mb 18 | 19 | use crate::memory; 20 | 21 | pub fn init_heap( 22 | mapper: &mut impl Mapper, 23 | frame_allocator: &mut impl FrameAllocator, 24 | ) -> Result<(), MapToError> { 25 | 26 | memory::allocate_pages_mapper( 27 | frame_allocator, 28 | mapper, 29 | VirtAddr::new(HEAP_START as u64), 30 | HEAP_SIZE as u64, 31 | PageTableFlags::PRESENT | PageTableFlags::WRITABLE)?; 32 | 33 | unsafe { 34 | ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE); 35 | } 36 | 37 | Ok(()) 38 | } 39 | 40 | use linked_list_allocator::LockedHeap; 41 | 42 | #[global_allocator] 43 | static ALLOCATOR: LockedHeap = LockedHeap::empty(); 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ben Dudson 4 | Based on original blog posts Copyright (c) 2019 Philipp Oppermann 5 | Parts adapted from MOROS, Copyright (c) 2019-2022 Vincent Ollivier 6 | Parts inspired by Redox, Copyright (c) 2017 Jeremy Soller 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /euralios_std/src/console.rs: -------------------------------------------------------------------------------- 1 | ///! Definitions and routines for handling consoles 2 | 3 | pub mod sequences { 4 | pub const F1: u64 = 0x1b_9b_31_31_7e; // ESC [ 1 1 ~ 5 | pub const F2: u64 = 0x1b_9b_31_32_7e; // ESC [ 1 2 ~ 6 | pub const F3: u64 = 0x1b_9b_31_33_7e; // ESC [ 1 3 ~ 7 | pub const F4: u64 = 0x1b_9b_31_34_7e; // ESC [ 1 4 ~ 8 | pub const F5: u64 = 0x1b_9b_31_35_7e; // ESC [ 1 5 ~ 9 | pub const F6: u64 = 0x1b_9b_31_37_7e; // ESC [ 1 7 ~ 10 | pub const F7: u64 = 0x1b_9b_31_38_7e; // ESC [ 1 8 ~ 11 | pub const F8: u64 = 0x1b_9b_31_39_7e; // ESC [ 1 9 ~ 12 | pub const F9: u64 = 0x1b_9b_32_30_7e; // ESC [ 2 0 ~ 13 | pub const F10: u64 = 0x1b_9b_32_31_7e; // ESC [ 2 1 ~ 14 | pub const F11: u64 = 0x1b_9b_32_33_7e; // ESC [ 2 3 ~ 15 | pub const F12: u64 = 0x1b_9b_32_34_7e; // ESC [ 3 4 ~ 16 | 17 | pub const PageUp: u64 = 0x1b_9b_35_7e; // ESC [ 5 ~ 18 | pub const PageDown: u64 = 0x1b_9b_36_7e; // ESC [ 6 ~ 19 | pub const Home: u64 = 0x1b_9b_48; // ESC [ H 20 | pub const End: u64 = 0x1b_9b_46; // ESC [ F 21 | 22 | // These are xterm sequences 23 | pub const ArrowUp: u64 = 0x1b_9b_41; // ESC [ A 24 | pub const ArrowDown: u64 = 0x1b_9b_42; // ESC [ B 25 | pub const ArrowRight: u64 = 0x1b_9b_43; // ESC [ C 26 | pub const ArrowLeft: u64 = 0x1b_9b_44; // ESC [ D 27 | } 28 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: all build user 3 | 4 | all: build 5 | 6 | # Build all user programs then kernel 7 | build: user 8 | cargo build --release --bin kernel 9 | 10 | # Build everything then run with QEMU 11 | run : user 12 | cargo run --release --bin kernel 13 | 14 | # List of user programs to build 15 | # Note: init includes many others so should be last 16 | user: user/pci user/rtl8139 user/virtio_net user/arp user/tcp user/gopher \ 17 | user/timing_test user/vga_driver user/ramdisk user/shell \ 18 | user/keyboard user/system_test user/login user/edit user/init 19 | 20 | user/% : FORCE 21 | cargo build --release --bin $* 22 | mkdir -p user 23 | cp target/x86_64-euralios/release/$* user/ 24 | 25 | # This builds both unit test "user/std_test" and integration test "system_test" 26 | user/system_test: FORCE 27 | cd euralios_std; cargo test --no-run 28 | @cp $(shell find target/x86_64-euralios/debug/deps/ -maxdepth 1 -name "system_test-*" -executable -print | head -n 1) $@ 29 | @strip $@ # Can't use debugging symbols anyway 30 | @cp $(shell find target/x86_64-euralios/debug/deps/ -maxdepth 1 -name "euralios_std-*" -executable -print | head -n 1) user/std_test 31 | @strip user/std_test 32 | 33 | FORCE: 34 | 35 | # Some shortcuts which build all documentation 36 | doc: FORCE 37 | cargo doc --document-private-items 38 | 39 | doc-open: 40 | cargo doc --document-private-items --open 41 | -------------------------------------------------------------------------------- /pci/src/ports.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | use lazy_static::lazy_static; 3 | use spin::Mutex; 4 | 5 | /// Provides access to the PCI address space by reading and writing to ports 6 | pub struct PciPorts {} 7 | 8 | impl PciPorts { 9 | const CONFIG_ADDRESS: u16 = 0xCF8; 10 | const CONFIG_DATA: u16 = 0xCFC; 11 | 12 | /// Write to Address and Data ports 13 | pub fn write(&mut self, address: u32, value: u32) { 14 | unsafe { 15 | asm!("out dx, eax", 16 | in("dx") Self::CONFIG_ADDRESS, 17 | in("eax") address, 18 | options(nomem, nostack)); 19 | 20 | asm!("out dx, eax", 21 | in("dx") Self::CONFIG_DATA, 22 | in("eax") value, 23 | options(nomem, nostack)); 24 | } 25 | } 26 | 27 | /// Write to Address port, read from Data port 28 | /// Note: Mutates ports values so needs mut self 29 | pub fn read(&mut self, address: u32) -> u32 { 30 | let value: u32; 31 | unsafe { 32 | asm!("out dx, eax", 33 | in("dx") Self::CONFIG_ADDRESS, 34 | in("eax") address, 35 | options(nomem, nostack)); 36 | 37 | asm!("in eax, dx", 38 | in("dx") Self::CONFIG_DATA, 39 | lateout("eax") value, 40 | options(nomem, nostack)); 41 | } 42 | value 43 | } 44 | } 45 | 46 | lazy_static! { 47 | pub static ref PORTS: Mutex = Mutex::new(PciPorts{}); 48 | } 49 | -------------------------------------------------------------------------------- /gopher/README.org: -------------------------------------------------------------------------------- 1 | #+begin_src 2 | _ 3 | | | 4 | __ _ ___ _ __ | |__ ___ _ __ 5 | / _` |/ _ \| '_ \| '_ \ / _ \ '__| 6 | | (_| | (_) | |_) | | | | __/ | 7 | \__, |\___/| .__/|_| |_|\___|_| 8 | __/ | | | 9 | |___/ |_| 10 | #+end_src 11 | 12 | This is a user program which opens [[https://en.wikipedia.org/wiki/Gopher_(protocol)][gopher]] pages. When started it loads 13 | the front page of =gopher.floodgap.com=. 14 | 15 | ** How to use 16 | 17 | Running the =gopher= program should bring up a screen like this: 18 | 19 | #+CAPTION: The Floodgap gopher server landing page 20 | #+NAME: fig-gopher 21 | [[./floodgap.png]] 22 | 23 | Type =h= or =?= to print the "help". Actually pressing any key 24 | which doesn't have a function will print this help. 25 | 26 | Navigation is with either WASD or IJKL keys: 27 | - =w= or =i= scroll up the page 28 | - =s= or =k= scroll down the page 29 | 30 | Links are numbered on the left and marked a DIR (directory) containing 31 | further gopher links, or a TXT (text) file. Other target types are not 32 | currently recognised. 33 | 34 | To follow a link type the number on the left margin. 35 | A "Link: " line should be printed at the bottom of the screen, 36 | listing the address. Press =Enter= to confirm. 37 | 38 | To go back press =a= or =j= keys, and to go forward again press =d= or 39 | =l= keys. 40 | 41 | Finally, press =q= to quit. 42 | 43 | Enjoy! 44 | 45 | ** How does it work? 46 | 47 | This program reads data using the =tcp= device: 48 | #+begin_src shell 49 | /tcp/hostname/port 50 | #+end_src 51 | -------------------------------------------------------------------------------- /euralios_std/src/net.rs: -------------------------------------------------------------------------------- 1 | //! Network related data structures and functions 2 | 3 | use core::fmt; 4 | 5 | /// Represent a Media Access Control (MAC) address 6 | /// 7 | /// Interface similar to mac_address crate 8 | /// 9 | pub struct MacAddress { 10 | octet: [u8; 6] 11 | } 12 | 13 | impl MacAddress { 14 | /// Create a new MacAddress from bytes 15 | pub fn new(octet: [u8; 6]) -> Self { 16 | MacAddress{octet} 17 | } 18 | 19 | /// Return the address as an array of bytes 20 | pub fn bytes(&self) -> [u8; 6] { 21 | self.octet 22 | } 23 | 24 | pub fn from_u64(value: u64) -> Self { 25 | MacAddress{octet:[ 26 | (value & 0xFF) as u8, 27 | ((value >> 8) & 0xFF) as u8, 28 | ((value >> 16) & 0xFF) as u8, 29 | ((value >> 24) & 0xFF) as u8, 30 | ((value >> 32) & 0xFF) as u8, 31 | ((value >> 40) & 0xFF) as u8 32 | ]} 33 | } 34 | 35 | pub fn as_u64(&self) -> u64 { 36 | (self.octet[0] as u64) | 37 | ((self.octet[1] as u64) << 8) | 38 | ((self.octet[2] as u64) << 16) | 39 | ((self.octet[3] as u64) << 24) | 40 | ((self.octet[4] as u64) << 32) | 41 | ((self.octet[5] as u64) << 40) 42 | } 43 | } 44 | 45 | impl fmt::Display for MacAddress { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | for i in 0..5 { 48 | write!(f, "{:02X}:", self.octet[i])?; 49 | } 50 | write!(f, "{:02X}", self.octet[5]) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /kernel/tests/heap_allocation.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(custom_test_frameworks)] 4 | #![test_runner(blog_os::test_runner)] 5 | #![reexport_test_harness_main = "test_main"] 6 | 7 | extern crate alloc; 8 | 9 | use bootloader::{entry_point, BootInfo}; 10 | use core::panic::PanicInfo; 11 | use blog_os::memory; 12 | 13 | entry_point!(main); 14 | 15 | fn main(boot_info: &'static BootInfo) -> ! { 16 | blog_os::init(); 17 | memory::init(boot_info); 18 | 19 | test_main(); 20 | loop {} 21 | } 22 | 23 | #[panic_handler] 24 | fn panic(info: &PanicInfo) -> ! { 25 | blog_os::test_panic_handler(info) 26 | } 27 | 28 | // Tests 29 | 30 | use alloc::boxed::Box; 31 | 32 | #[test_case] 33 | fn simple_allocation() { 34 | let heap_value_1 = Box::new(41); 35 | let heap_value_2 = Box::new(13); 36 | assert_eq!(*heap_value_1, 41); 37 | assert_eq!(*heap_value_2, 13); 38 | } 39 | 40 | use alloc::vec::Vec; 41 | 42 | #[test_case] 43 | fn large_vec() { 44 | let n = 1000; 45 | let mut vec = Vec::new(); 46 | for i in 0..n { 47 | vec.push(i); 48 | } 49 | assert_eq!(vec.iter().sum::(), (n - 1) * n / 2); 50 | } 51 | 52 | 53 | use blog_os::allocator::HEAP_SIZE; 54 | 55 | #[test_case] 56 | fn many_boxes() { 57 | for i in 0..HEAP_SIZE { 58 | let x = Box::new(i); 59 | assert_eq!(*x, i); 60 | } 61 | } 62 | 63 | #[test_case] 64 | fn many_boxes_long_lived() { 65 | let long_lived = Box::new(1); // A single long-lived allocation 66 | for i in 0..HEAP_SIZE { 67 | let x = Box::new(i); // Many short-lived allocations 68 | assert_eq!(*x, i); 69 | } 70 | assert_eq!(*long_lived, 1); // new 71 | } 72 | -------------------------------------------------------------------------------- /doc/memory.org: -------------------------------------------------------------------------------- 1 | * Memory 2 | 3 | Some useful python functions to map between table indices and 4 | addresses are: 5 | #+begin_src python 6 | def page_table_indices(vaddr): 7 | return ((vaddr >> 39) & 511, (vaddr >> 30) & 511, (vaddr >> 21) & 511, (vaddr >> 12) & 511, vaddr & 4095) 8 | 9 | def page_table_address(indices): 10 | return (indices[0] << 39) + (indices[1] << 30) + (indices[2] << 21) + (indices[3] << 12) + indices[4] 11 | #+end_src 12 | 13 | ** Kernel memory layout 14 | 15 | - Code below (0, 0, 40, 0, 0), 0x5000000 i.e 50Mb maximum 16 | - Kernel heap (136, 273, 34, 64, 0), 0x_4444_4444_0000. Set by 17 | =HEAP_START= and =HEAP_SIZE= constants in =memory/allocator.rs=. 18 | - Physical memory map 19 | 20 | ** User program memory layout 21 | 22 | - Kernel info page, (0, 0, 39, 511, 0) to (0, 0, 40, 0, 0), 0x4fff000 to 0x5000000. 23 | Read-only, contains information for fast timing functions. Set by 24 | =KERNELINFO_VIRTADDR= in =kernel/src/memory/kernel_info.rs= and =euralios_std/src/time.rs=. 25 | 26 | - Code (0, 0, 40, 0, 0) to (0, 2, 0, 0, 0), 0x5000000 to 0x80000000. Set by =USER_CODE_START= and 27 | =USER_CODE_END= constants in =process.rs=. 28 | 29 | - Stack (5,0,0,0,0) to (5,0,1,0,0), 0x28000000000 to 30 | 0x28000200000. 2Mb for all threads. Set by the 31 | =THREAD_STACK_PAGE_INDEX= constant in =memory.rs=. 32 | 33 | - Heap is (5,0,3,0,0) to (5,0,23,0,0), 0x28000600000 to 0x28002e00000, 34 | a total of 0x2800000 bytes or 40Mb. Set by =USER_HEAP_START= and 35 | =USER_HEAP_SIZE= constants in =process.rs=. 36 | 37 | - Memory chunks (5,1,0,0,0) to (6,0,0,0,0). 511 chunks, up to 1Gb 38 | each. Set by =MEMORY_CHUNK_L4_ENTRY=, =MEMORY_CHUNK_L3_FIRST= and 39 | =MEMORY_CHUNK_L3_LAST= constants in =memory.rs=. 40 | 41 | -------------------------------------------------------------------------------- /euralios_std/src/ports.rs: -------------------------------------------------------------------------------- 1 | //! Functions for input and output to I/O ports 2 | 3 | use core::arch::asm; 4 | 5 | pub fn outportb(ioaddr: u16, value: u8) { 6 | unsafe { 7 | asm!("out dx, al", 8 | in("dx") ioaddr, 9 | in("al") value, 10 | options(nomem, nostack)); 11 | } 12 | } 13 | 14 | /// Write a word (16 bits) to a port 15 | pub fn outportw(ioaddr: u16, value: u16) { 16 | unsafe { 17 | asm!("out dx, ax", 18 | in("dx") ioaddr, 19 | in("ax") value, 20 | options(nomem, nostack)); 21 | } 22 | } 23 | 24 | /// Write a double word (32 bits) to a port 25 | pub fn outportd(ioaddr: u16, value: u32) { 26 | unsafe { 27 | asm!("out dx, eax", 28 | in("dx") ioaddr, 29 | in("eax") value, 30 | options(nomem, nostack)); 31 | } 32 | } 33 | 34 | /// Read a byte from a port 35 | pub fn inportb(ioaddr: u16) -> u8 { 36 | let value: u8; 37 | unsafe { 38 | asm!("in al, dx", 39 | in("dx") ioaddr, 40 | lateout("al") value, 41 | options(nomem, nostack)); 42 | } 43 | value 44 | } 45 | 46 | /// Read a word (16 bits) from a port 47 | pub fn inportw(ioaddr: u16) -> u16 { 48 | let value: u16; 49 | unsafe { 50 | asm!("in ax, dx", 51 | in("dx") ioaddr, 52 | lateout("ax") value, 53 | options(nomem, nostack)); 54 | } 55 | value 56 | } 57 | 58 | pub fn inportd(ioaddr: u16) -> u32 { 59 | let value: u32; 60 | unsafe { 61 | asm!("in eax, dx", 62 | in("dx") ioaddr, 63 | lateout("eax") value, 64 | options(nomem, nostack)); 65 | } 66 | value 67 | } 68 | -------------------------------------------------------------------------------- /vga_driver/README.org: -------------------------------------------------------------------------------- 1 | * VGA text mode driver 2 | 3 | This program runs in user space, using the [[https://crates.io/crates/vga][vga crate]] to interface to 4 | VGA video cards in 80x25 text mode. This driver interprets a subset of 5 | the [[https://en.wikipedia.org/wiki/ANSI_escape_code][ANSI escape sequences]] and keeps track of multiple displays that it 6 | can switch between. 7 | 8 | Displays are represented by =Writer= objects, that keep track of 9 | what is displayed and can write to video memory if active. 10 | 11 | ** Startup 12 | 13 | When first starting this program expects to receive a message of type 14 | =VIDEO_MEMORY= via =stdin=. That should contain a 128k (length 15 | =0x20000=) memory region that is mapped to video memory. 16 | 17 | ** Main loop 18 | 19 | The main loop waits for messages on =stdin=. If it receives an 20 | =OPEN_READWRITE= message then it creates a new =Writer=, i.e. a 21 | new screen that can be written to. A new thread is launched 22 | to handle messages for this new =Writer= and a communication 23 | handle is returned along with the =Writer= ID. 24 | 25 | To switch between displays a =WRITE= message is sent to the main 26 | loop (=stdin= of the VGA driver) with the =Writer= ID. The 27 | current writer will be deactivated and the specified writer 28 | activated by copying its buffer into VGA memory. 29 | 30 | ** Writer handler 31 | 32 | For each =Writer= there is a thread that waits for =WRITE= messages. 33 | Those can be either single characters, or strings that may contain 34 | ANSI escape sequences. 35 | 36 | When processing inputs the =Writer= updates an internal buffer, and if 37 | active also updates the VGA memory. The reason for updating both is 38 | that reading from VGA memory is very slow, so this avoids having to do 39 | this when switching displays. 40 | -------------------------------------------------------------------------------- /kernel/tests/stack_overflow.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(abi_x86_interrupt)] 4 | 5 | use blog_os::serial_print; 6 | 7 | #[no_mangle] 8 | pub extern "C" fn _start() -> ! { 9 | serial_print!("stack_overflow::stack_overflow...\t"); 10 | 11 | blog_os::gdt::init(); 12 | init_test_idt(); 13 | 14 | // trigger a stack overflow 15 | stack_overflow(); 16 | 17 | panic!("Execution continued after stack overflow"); 18 | } 19 | 20 | #[allow(unconditional_recursion)] 21 | fn stack_overflow() { 22 | stack_overflow(); // for each recursion, the return address is pushed 23 | volatile::Volatile::new(0).read(); // prevent tail recursion optimizations 24 | } 25 | 26 | use core::panic::PanicInfo; 27 | 28 | #[panic_handler] 29 | fn panic(info: &PanicInfo) -> ! { 30 | blog_os::test_panic_handler(info) 31 | } 32 | 33 | // Custom interrupt table, replacing the double fault handler 34 | // with one which exits from QEMU 35 | 36 | use lazy_static::lazy_static; 37 | use x86_64::structures::idt::InterruptDescriptorTable; 38 | 39 | lazy_static! { 40 | static ref TEST_IDT: InterruptDescriptorTable = { 41 | let mut idt = InterruptDescriptorTable::new(); 42 | unsafe { 43 | idt.double_fault 44 | .set_handler_fn(test_double_fault_handler) 45 | .set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX); 46 | } 47 | 48 | idt 49 | }; 50 | } 51 | 52 | use blog_os::{exit_qemu, QemuExitCode, serial_println}; 53 | use x86_64::structures::idt::InterruptStackFrame; 54 | 55 | extern "x86-interrupt" fn test_double_fault_handler( 56 | _stack_frame: InterruptStackFrame, 57 | _error_code: u64, 58 | ) -> ! { 59 | serial_println!("[ok]"); 60 | exit_qemu(QemuExitCode::Success); 61 | loop {} 62 | } 63 | 64 | pub fn init_test_idt() { 65 | TEST_IDT.load(); 66 | } 67 | -------------------------------------------------------------------------------- /doc/journal/24-directories.org: -------------------------------------------------------------------------------- 1 | 2 | * Directories 3 | 4 | We'll now add the `mkdir`, `rmdir` and `cd` commands to the shell, 5 | and the message handling in the `ramdisk` program to create and 6 | delete directories. 7 | 8 | 9 | ** Opening read-only 10 | 11 | To control whether files and directories can be written we need to 12 | be able to open them read-only. 13 | 14 | 15 | The Rust =std::fs::OpenOptions= builder provides a way to control 16 | the options used when opening a file: Do we want to write the file? If the file 17 | exists do we want to create it? etc. 18 | 19 | After implementing =euralios_std::fs::OpenOptions= with the same 20 | interface, the =fs::create_dir()= function is implemented using all 21 | our path utilities as: 22 | #+begin_src rust 23 | pub fn create_dir>(path: P) -> Result<(), SyscallError> { 24 | let path: &Path = path.as_ref(); 25 | 26 | // Get the directory's parent 27 | let parent = match path.parent() { 28 | Some(parent) => parent, 29 | None => { return Err(syscalls::SYSCALL_ERROR_PARAM); } 30 | }; 31 | 32 | // Get the final part of the path 33 | let new_dir_name = match path.file_name() { 34 | Some(name) => name, 35 | None => { return Err(syscalls::SYSCALL_ERROR_PARAM); } 36 | }; 37 | 38 | // Open the parent directory for modifying 39 | let f = OpenOptions::new().write(true).open(parent)?; 40 | 41 | // Send a MKDIR message 42 | let bytes = new_dir_name.bytes(); 43 | match f.rcall(message::MKDIR, 44 | (bytes.len() as u64).into(), 45 | MemoryHandle::from_u8_slice(bytes).into()) { 46 | Err((err, _)) => Err(err), 47 | Ok((message::OK, _, _)) => Ok(()), 48 | _ => Err(syscalls::SYSCALL_ERROR_PARAM) 49 | } 50 | } 51 | #+end_src 52 | 53 | In the [[./26-multiple-users.org][next section]] we'll use directories and custom Virtual File 54 | Systems (VFS) to isolate multiple users from each other. 55 | -------------------------------------------------------------------------------- /euralios_std/src/thread.rs: -------------------------------------------------------------------------------- 1 | //////////////////////////// 2 | // Thread library 3 | // 4 | // Interface here: 5 | // https://doc.rust-lang.org/book/ch16-01-threads.html 6 | // 7 | // std implementation is here: 8 | // - spawn 9 | // https://doc.rust-lang.org/src/std/thread/mod.rs.html#646 10 | // - Thread::new 11 | // https://github.com/rust-lang/rust/blob/master/library/std/src/sys/unix/thread.rs 12 | // 13 | 14 | extern crate alloc; 15 | use alloc::boxed::Box; 16 | 17 | use crate::syscalls::{self, SyscallError}; 18 | 19 | /// Spawn a new thread with closure 20 | /// 21 | /// The spawned thread may outlive the caller, so all 22 | /// variables captured must be moved or have static lifetime. 23 | /// 24 | /// thread::spawn(move || { 25 | /// // Code which captures from environment 26 | /// }); 27 | /// 28 | pub fn spawn(f: F) -> Result<(), SyscallError> 29 | where 30 | F: FnOnce() -> (), 31 | F: Send + 'static, 32 | { 33 | launch(Box::new(f)) 34 | } 35 | 36 | /// Launch a thread by calling the low-level syscalls 37 | /// 38 | fn launch(p: Box) -> Result<(), SyscallError> 39 | { 40 | // Note: A Box is a fat pointer, 41 | // containing a pointer to heap allocated memory 42 | // along with the function pointer. To convert to 43 | // a thin pointer (memory address) we first need 44 | // to move the fat pointer onto the heap by putting 45 | // into a Box, then get a pointer to that. 46 | let p = Box::into_raw(Box::new(p)); 47 | 48 | // Get thin pointer as memory address 49 | if let Err(sys_err) = syscalls::thread_spawn(thread_start, 50 | p as *mut () as usize) { 51 | // Could not launch thread. Reconstruct Box so that 52 | // the contents can be dropped 53 | let _ = unsafe {Box::from_raw(p)}; 54 | return Err(sys_err); 55 | } 56 | 57 | // This function is called by syscalls::thread_spawn 58 | extern "C" fn thread_start(main: usize) { 59 | // Convert address back to Box containing the Box 60 | // fat pointer, then call it. 61 | unsafe {Box::from_raw(main as *mut Box)()}; 62 | } 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /tcp/src/dhcp.rs: -------------------------------------------------------------------------------- 1 | //! Dynamic Host Configuration Protocol (DHCP) 2 | //! Configure IP, DNS and gatway 3 | 4 | use smoltcp::wire::{IpCidr, Ipv4Cidr, IpAddress}; 5 | use smoltcp::socket::{Dhcpv4Event, Dhcpv4Socket}; 6 | use smoltcp::time::Instant; 7 | 8 | use euralios_std::{println, print, 9 | syscalls}; 10 | 11 | use crate::Interface; 12 | use crate::dns; 13 | 14 | /// DHCP configuration on given interface 15 | /// 16 | /// Based on 17 | pub fn configure(interface: &mut Interface) { 18 | 19 | let dhcp_socket = Dhcpv4Socket::new(); 20 | let dhcp_handle = interface.add_socket(dhcp_socket); 21 | 22 | if let Err(e) = interface.poll(Instant::from_millis(0)) { // This transmits 23 | panic!("[tcp] Network Error: {}", e); 24 | } 25 | 26 | loop { 27 | let event = interface.get_socket::(dhcp_handle).poll(); 28 | match event { 29 | None => {} 30 | Some(Dhcpv4Event::Configured(config)) => { 31 | interface.remove_socket(dhcp_handle); 32 | 33 | print!("[tcp] DHCP: IP {}", config.address); 34 | set_ipv4_addr(interface, config.address); 35 | 36 | if let Some(router) = config.router { 37 | print!(" Router {}", router); 38 | interface.routes_mut().add_default_ipv4_route(router).unwrap(); 39 | } 40 | 41 | for addr in config.dns_servers.iter() 42 | .filter(|addr| addr.is_some()).map(|addr| addr.unwrap()) { 43 | print!(" DNS {}", addr); 44 | dns::add_server(IpAddress::from(addr)); 45 | } 46 | println!(""); 47 | break; 48 | } 49 | Some(Dhcpv4Event::Deconfigured) => { 50 | } 51 | } 52 | // Wait and retry 53 | syscalls::thread_yield(); 54 | } 55 | } 56 | 57 | /// Set the IPv4 address of an interface 58 | /// 59 | /// This function from: 60 | /// 61 | fn set_ipv4_addr(iface: &mut Interface, cidr: Ipv4Cidr) { 62 | iface.update_ip_addrs(|addrs| { 63 | let dest = addrs.iter_mut().next().unwrap(); 64 | *dest = IpCidr::Ipv4(cidr); 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /kernel/tests/interrupt_wrap.rs: -------------------------------------------------------------------------------- 1 | // Test that the interrupt wrap! macro enables handler 2 | // functions to access and modify process registers. 3 | 4 | #![no_std] 5 | #![no_main] 6 | #![feature(custom_test_frameworks)] 7 | #![test_runner(blog_os::test_runner)] 8 | #![reexport_test_harness_main = "test_main"] 9 | #![feature(abi_x86_interrupt)] 10 | #![feature(naked_functions)] 11 | #![feature(asm_sym)] 12 | 13 | use core::arch::asm; 14 | use core::panic::PanicInfo; 15 | 16 | use bootloader::{entry_point, BootInfo}; 17 | 18 | use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; 19 | 20 | use lazy_static::lazy_static; 21 | 22 | use blog_os::gdt; 23 | use blog_os::interrupts::{Context, InterruptIndex, PICS}; 24 | use blog_os::interrupt_wrap; 25 | 26 | entry_point!(main); 27 | 28 | lazy_static! { 29 | static ref IDT: InterruptDescriptorTable = { 30 | let mut idt = InterruptDescriptorTable::new(); 31 | idt[InterruptIndex::Timer.as_usize()] 32 | .set_handler_fn(timer_handler_naked); 33 | idt 34 | }; 35 | } 36 | 37 | fn main(_boot_info: &'static BootInfo) -> ! { 38 | gdt::init(); 39 | 40 | IDT.load(); 41 | 42 | unsafe { PICS.lock().initialize() }; // Configure hardware interrupt controller 43 | x86_64::instructions::interrupts::enable(); // CPU starts listening for hardware interrupts 44 | 45 | test_main(); 46 | loop {} 47 | } 48 | 49 | extern "C" fn timer_handler(context: &mut Context) { 50 | context.r11 = context.rdi + 0x5321; 51 | context.rcx = 0xdeadbeef; 52 | 53 | // Tell the PIC that the interrupt has been processed 54 | unsafe { 55 | PICS.lock() 56 | .notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); 57 | } 58 | } 59 | 60 | interrupt_wrap!(timer_handler => timer_handler_naked); 61 | 62 | #[panic_handler] 63 | fn panic(info: &PanicInfo) -> ! { 64 | blog_os::test_panic_handler(info) 65 | } 66 | 67 | // Tests 68 | 69 | #[test_case] 70 | fn handler_changes_regs() { 71 | unsafe { 72 | asm!("mov r11, 0x4242", 73 | "mov rcx, 0x93", 74 | "mov rdi, 0x22" 75 | ); 76 | } 77 | 78 | // Wait for an interrupt 79 | unsafe {asm!("hlt");} 80 | 81 | // Get the register values 82 | let (r11, rdi, rcx): (i64, i64, i64); 83 | unsafe {asm!("nop", 84 | lateout("r11") r11, 85 | lateout("rcx") rcx, 86 | lateout("rdi") rdi);} 87 | 88 | assert_eq!(rdi, 0x22); 89 | assert_eq!(r11, rdi + 0x5321); 90 | assert_eq!(rcx, 0xdeadbeef); 91 | } 92 | -------------------------------------------------------------------------------- /euralios_std/src/time.rs: -------------------------------------------------------------------------------- 1 | use core::arch::asm; 2 | 3 | pub use core::time::Duration; 4 | 5 | pub fn time_stamp_counter() -> u64 { 6 | let counter: u64; 7 | unsafe{ 8 | asm!("rdtsc", 9 | "shl rdx, 32", // High bits in EDX 10 | "or rdx, rax", // Low bits in EAX 11 | out("rdx") counter, 12 | out("rax") _, // Clobbers RAX 13 | options(pure, nomem, nostack) 14 | ); 15 | } 16 | counter 17 | } 18 | 19 | /// This structure is mapped read-only into user address space 20 | pub struct KernelInfo { 21 | pub pit_ticks: u64, // Number of PIT ticks since restart 22 | pub last_tsc: u64, // TSC value at last pit_ticks update 23 | pub tsc_per_pit: u64, // Change in TSC ticks per PIT tick 24 | } 25 | 26 | /// The virtual address of the KernelInfo struct 27 | const KERNELINFO_VIRTADDR: u64 = 0x4fff000; 28 | 29 | /// Get a reference to the KernelInfo struct 30 | pub fn kernel_info() -> &'static KernelInfo { 31 | let ptr = KERNELINFO_VIRTADDR as *const KernelInfo; 32 | unsafe{&(*ptr)} 33 | } 34 | 35 | /// Monotonic count of he number of microseconds since restart 36 | /// 37 | /// Uses PIT interrupts to calibrate the TSC. Calibration calculated 38 | /// by the kernel and stored in the KernelInfo struct. 39 | /// 40 | pub fn microseconds_monotonic() -> u64 { 41 | // Calibration calculated in the kernel (kernel/src/time.rs) 42 | let info = kernel_info(); 43 | 44 | // Number of PIT ticks 45 | let pit = info.pit_ticks; 46 | // Number of TSC ticks since last PIT interrupt 47 | let tsc = time_stamp_counter() - info.last_tsc; 48 | 49 | // Number of TSC counts per PIT tick 50 | let tsc_per_pit = info.tsc_per_pit; 51 | 52 | // PIT frequency is 3_579_545 / 3 = 1_193_181.666 Hz 53 | // each PIT tick is 0.83809534452 microseconds 54 | // 878807 / (1024*1024) = 0.83809566497 55 | // 56 | // Calculate total TSC then divide to get microseconds 57 | // Note: Don't use TSC directly because jitter in tsc_per_pit would lead to 58 | // non-monotonic outputs 59 | 60 | // Note! This next expression will overflow in about 2 hours : 61 | // 2**64 / (1024 * 1024 * 2270) microseconds 62 | //((pit * tsc_per_pit + tsc) * 878807) / (1024*1024 * tsc_per_pit) 63 | 64 | const SCALED_TSC_RATE: u64 = 16; 65 | let scaled_tsc = (tsc * SCALED_TSC_RATE) / tsc_per_pit; 66 | 67 | // Factorize 878807 = 437 * 2011 68 | // This will overflow in about 142 years : 2**64 / 4096 microseconds 69 | ((((pit * SCALED_TSC_RATE + scaled_tsc) * 2011) / 4096) * 437) / (256 * SCALED_TSC_RATE) 70 | } 71 | -------------------------------------------------------------------------------- /kernel/src/memory/kernel_info.rs: -------------------------------------------------------------------------------- 1 | //! A page which is mapped read-only into every user program's address 2 | //! space. It can be used to provide time-sensitive information. 3 | //! 4 | use x86_64::{ 5 | structures::paging::{ 6 | page::Page, frame::PhysFrame, 7 | mapper::MapToError, FrameAllocator, Mapper, PageTableFlags, Size4KiB, 8 | }, 9 | VirtAddr, PhysAddr 10 | }; 11 | 12 | use core::sync::atomic::{AtomicU64, Ordering}; 13 | 14 | static FRAME_PHYSADDR: AtomicU64 = AtomicU64::new(0); 15 | 16 | /// KernelInfo virtual address in kernel address space 17 | static FRAME_VIRTADDR: AtomicU64 = AtomicU64::new(0); 18 | 19 | /// KernelInfo virtual address in user address space 20 | const KERNELINFO_VIRTADDR: u64 = 0x4fff000; 21 | 22 | pub struct KernelInfo { 23 | // These are set in time::pit_interrupt_notify() 24 | pub pit_ticks: u64, // Number of PIT ticks since restart 25 | pub last_tsc: u64, // TSC value at last pit_ticks update 26 | pub tsc_per_pit: u64, // Change in TSC ticks per PIT tick 27 | } 28 | 29 | /// Initialise a frame to hold the KernelInfo struct 30 | pub fn init( 31 | frame_allocator: &mut impl FrameAllocator, 32 | physical_memory_offset: VirtAddr 33 | ) -> Result<(), MapToError> { 34 | 35 | // Get one frame 36 | let frame = frame_allocator 37 | .allocate_frame() 38 | .ok_or(MapToError::FrameAllocationFailed)?; 39 | 40 | // This frame is already mapped. Save its physical and virtual addresses 41 | FRAME_PHYSADDR.store(frame.start_address().as_u64(), Ordering::Relaxed); 42 | FRAME_VIRTADDR.store(physical_memory_offset.as_u64() + frame.start_address().as_u64(), 43 | Ordering::Relaxed); 44 | Ok(()) 45 | } 46 | 47 | pub fn get_ref() -> &'static KernelInfo { 48 | let ptr = FRAME_VIRTADDR.load(Ordering::Relaxed) as *const KernelInfo; 49 | unsafe{&(*ptr)} 50 | } 51 | 52 | pub fn get_mut() -> &'static mut KernelInfo { 53 | let ptr = FRAME_VIRTADDR.load(Ordering::Relaxed) as *mut KernelInfo; 54 | unsafe{&mut (*ptr)} 55 | } 56 | 57 | pub fn add_to_user_table( 58 | mapper: &mut impl Mapper, 59 | frame_allocator: &mut impl FrameAllocator, 60 | ) -> Result<(), MapToError> { 61 | let page = Page::containing_address( 62 | VirtAddr::new(KERNELINFO_VIRTADDR)); 63 | let frame = PhysFrame::containing_address( 64 | PhysAddr::new(FRAME_PHYSADDR.load(Ordering::Relaxed))); 65 | 66 | unsafe { 67 | mapper.map_to(page, 68 | frame, 69 | // Page not writable 70 | PageTableFlags::PRESENT | 71 | PageTableFlags::USER_ACCESSIBLE, 72 | frame_allocator)?.flush(); 73 | } 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /doc/journal/20-dns.org: -------------------------------------------------------------------------------- 1 | * Domain Name System (DNS) 2 | 3 | So far our gopher client from [[./18-gopher.org][section 18]] is hard-wired to the host 4 | IP address 192.80.49.99 (=gopher.floodgap.com=) because 5 | we can't look up names to find other IP addresses. 6 | 7 | 8 | DNS sockets were [[https://github.com/smoltcp-rs/smoltcp/commit/da1a2b2df0eafebb7fb92c00e56e88d533daa446][recently added to smoltcp]] but are not yet in 9 | a released version 10 | 11 | 12 | #+begin_src shell 13 | 00:53:40.493500 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 52:54:00:12:34:56 (oui Unknown), length 262 14 | 00:53:40.493585 IP 10.0.2.2.bootps > 255.255.255.255.bootpc: BOOTP/DHCP, Reply, length 548 15 | 00:53:40.554433 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 52:54:00:12:34:56 (oui Unknown), length 274 16 | 00:53:40.554502 IP 10.0.2.2.bootps > 255.255.255.255.bootpc: BOOTP/DHCP, Reply, length 548 17 | #+end_src 18 | 19 | #+begin_src shell 20 | 00:53:40.670505 ARP, Request who-has 10.0.2.2 (Broadcast) tell 10.0.2.15, length 28 21 | 00:53:40.670571 ARP, Reply 10.0.2.2 is-at 52:55:0a:00:02:02 (oui Unknown), length 50 22 | #+end_src 23 | 24 | #+begin_src shell 25 | 00:53:40.728456 IP 10.0.2.15.49152 > dns.google.domain: 49153+ A? www.google.com. (32) 26 | 00:53:40.746304 IP dns.google.domain > 10.0.2.15.49152: 49153 1/0/0 A 172.217.164.100 (48) 27 | #+end_src 28 | 29 | #+CAPTION: 30 | #+NAME: fig-dns 31 | [[./img/20-01-dns.png]] 32 | 33 | ** Loose ends 34 | 35 | Some things have not been implemented but could/should be at some point: 36 | 37 | - Non-ASCII domain names are not yet handled. These should be 38 | converted into [[https://en.wikipedia.org/wiki/Punycode][Punycode]] before sending to the DNS server. 39 | - Timeouts and connection failures: Currently we'll just keep waiting 40 | forever, but there should be a mechanism to fall back to alternative 41 | DNS servers if one isn't working. 42 | 43 | ** Extending the gopher browser 44 | 45 | Now that we can connect to other servers by using their hostname 46 | rather than IP address, we can extend the =gopher= program so 47 | that it can follow links to other severs. 48 | 49 | #+CAPTION: Browsing to hngopher from floodgap.com 50 | #+NAME: fig-hngopher 51 | [[./img/20-02-hngopher.png]] 52 | 53 | In figure [[fig-hngopher]] you can see a message from =rtl8139= mixed in 54 | with the gopher page. That's because all processes are currently using 55 | =debug_println!= to write to screen. Before we develop any more 56 | programs which output to screen we need to do something about this: we 57 | need a way to separate outputs into workspaces/terminals/windows that 58 | we can switch between. We'll start doing this next, by moving the VGA 59 | driver from kernel into userspace and extending what it can do in the 60 | [[./21-vga.org][next section]]. 61 | -------------------------------------------------------------------------------- /euralios_std/src/env.rs: -------------------------------------------------------------------------------- 1 | extern crate alloc; 2 | use alloc::string::String; 3 | use core::str::{self, from_utf8, Utf8Error}; 4 | use crate::{get_args, get_env, 5 | path::PathBuf, 6 | ffi::OsString}; 7 | 8 | pub fn args() -> impl Iterator { 9 | // Convert bytes into a vector of owned strings 10 | get_args() 11 | .split(|&b| b == 0x03) 12 | .map(|arg| String::from_utf8_lossy(arg).into_owned()) 13 | } 14 | 15 | pub fn as_str() -> Result<&'static str, Utf8Error> { 16 | from_utf8(get_env()) 17 | } 18 | 19 | pub struct Vars { 20 | envs: String, 21 | pos: usize 22 | } 23 | 24 | impl Iterator for Vars { 25 | type Item = (String, String); 26 | 27 | fn next(&mut self) -> Option<(String, String)> { 28 | if self.pos >= self.envs.len() { 29 | return None; 30 | } 31 | let remaining = &self.envs[self.pos..]; 32 | 33 | // Records split using 0x03 bytes 34 | if let Some(pair) = remaining.split('\u{03}').next() { 35 | self.pos += pair.len() + 1; 36 | // Split pair by '=' 37 | let mut key_value = pair.split('='); 38 | Some((String::from(key_value.next()?), 39 | String::from(key_value.next()?))) 40 | } else { 41 | None 42 | } 43 | } 44 | } 45 | 46 | /// Returns an iterator over the (key, value) pairs 47 | pub fn vars() -> Vars { 48 | if let Ok(envs) = as_str() { 49 | Vars { 50 | envs: String::from(envs), 51 | pos: 0 52 | } 53 | } else { 54 | Vars { 55 | envs: String::new(), 56 | pos: 0 57 | } 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub enum VarError { 63 | NotPresent, 64 | NotUnicode, 65 | } 66 | 67 | pub fn var(key: &str) -> Result { 68 | let envs = as_str().map_err(|_| VarError::NotPresent)?; 69 | 70 | if let Some(pos) = envs.find(key) { 71 | if pos + key.len() == envs.len() { 72 | return Err(VarError::NotPresent); 73 | } 74 | let env_bytes = envs.as_bytes(); 75 | 76 | // Check that the next character is '=' 77 | if env_bytes[pos + key.len()] != b'=' { 78 | return Err(VarError::NotPresent); 79 | } 80 | 81 | // Value terminated by 0x03 82 | let value_bytes = &env_bytes[(pos + key.len() + 1)..]; 83 | let mut value_len = 0; 84 | for b in value_bytes { 85 | if *b == 0x03 { 86 | break; 87 | } 88 | value_len += 1; 89 | } 90 | return Ok(String::from( 91 | unsafe { 92 | str::from_utf8_unchecked(&value_bytes[..value_len]) 93 | })); 94 | } 95 | Err(VarError::NotPresent) 96 | } 97 | 98 | pub fn current_dir() -> Result { 99 | let pwd = var("PWD")?; 100 | Ok(PathBuf::from( 101 | OsString::from(pwd))) 102 | } 103 | -------------------------------------------------------------------------------- /kernel/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(test, no_main)] 3 | #![feature(custom_test_frameworks)] 4 | #![test_runner(crate::test_runner)] 5 | #![reexport_test_harness_main = "test_main"] 6 | #![feature(abi_x86_interrupt)] 7 | #![feature(alloc_error_handler)] 8 | #![feature(naked_functions)] 9 | #![feature(asm_sym)] 10 | #![feature(asm_const)] 11 | 12 | pub mod serial; 13 | pub mod vga_buffer; 14 | pub mod interrupts; 15 | pub mod gdt; 16 | pub mod memory; 17 | pub mod syscalls; 18 | pub mod process; 19 | pub mod rendezvous; 20 | pub mod message; 21 | pub mod vfs; 22 | pub mod time; 23 | 24 | extern crate alloc; // Memory allocation in stdlib 25 | 26 | use core::panic::PanicInfo; 27 | 28 | // Initialisation 29 | pub fn init() { 30 | gdt::init(); 31 | interrupts::init_idt(); 32 | unsafe { interrupts::PICS.lock().initialize() }; // Configure hardware interrupt controller 33 | x86_64::instructions::interrupts::enable(); // CPU starts listening for hardware interrupts 34 | } 35 | 36 | /// Energy-efficient endless loop 37 | pub fn hlt_loop() -> ! { 38 | loop { 39 | x86_64::instructions::hlt(); 40 | } 41 | } 42 | 43 | // Custom test framework 44 | 45 | pub trait Testable { 46 | fn run(&self) -> (); 47 | } 48 | 49 | // Implement Testable trait for all types with Fn() trait 50 | impl Testable for T 51 | where 52 | T: Fn(), 53 | { 54 | fn run(&self) { 55 | serial_print!("{}...\t", core::any::type_name::()); 56 | self(); 57 | serial_println!("[ok]"); 58 | } 59 | } 60 | 61 | pub fn test_runner(tests: &[&dyn Testable]) { 62 | serial_println!("Running {} tests", tests.len()); 63 | for test in tests { 64 | test.run(); 65 | } 66 | exit_qemu(QemuExitCode::Success); 67 | } 68 | 69 | pub fn test_panic_handler(info: &PanicInfo) -> ! { 70 | serial_println!("[failed]\n"); 71 | serial_println!("Error: {}\n", info); 72 | exit_qemu(QemuExitCode::Failed); 73 | hlt_loop(); 74 | } 75 | 76 | /// Entry point for `cargo test` 77 | 78 | #[cfg(test)] 79 | use bootloader::{entry_point, BootInfo}; 80 | 81 | #[cfg(test)] 82 | entry_point!(test_kernel_main); 83 | 84 | #[cfg(test)] 85 | fn test_kernel_main(_boot_info: &'static BootInfo) -> ! { 86 | init(); 87 | test_main(); 88 | hlt_loop(); 89 | } 90 | 91 | // our panic handler in test mode 92 | #[cfg(test)] 93 | #[panic_handler] 94 | fn panic(info: &PanicInfo) -> ! { 95 | test_panic_handler(info) 96 | } 97 | 98 | // To exit QEMU 99 | 100 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 101 | #[repr(u32)] 102 | pub enum QemuExitCode { 103 | Success = 0x10, 104 | Failed = 0x11, 105 | } 106 | 107 | pub fn exit_qemu(exit_code: QemuExitCode) { 108 | use x86_64::instructions::port::Port; 109 | 110 | unsafe { 111 | let mut port = Port::new(0xf4); 112 | port.write(exit_code as u32); 113 | } 114 | } 115 | 116 | // Allocator error handler 117 | #[alloc_error_handler] 118 | fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { 119 | panic!("allocation error: {:?}", layout) 120 | } 121 | -------------------------------------------------------------------------------- /keyboard/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use euralios_std::{debug_print, 5 | console::sequences, 6 | syscalls::{self, STDOUT}, 7 | message, ports}; 8 | use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1, KeyCode}; 9 | 10 | #[no_mangle] 11 | fn main() { 12 | let mut keyboard: Keyboard = Keyboard::new(HandleControl::MapLettersToUnicode); 13 | 14 | loop { 15 | // Wait for an interrupt to occur 16 | syscalls::await_interrupt(); 17 | 18 | let scancode: u8 = ports::inportb(0x60); 19 | if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { 20 | if let Some(key) = keyboard.process_keyevent(key_event) { 21 | let chars_be: u64 = match key { 22 | DecodedKey::Unicode(character) => { 23 | character as u64 // A single character 24 | }, 25 | DecodedKey::RawKey(key) => { 26 | match key { 27 | // These escape sequences follow the VT convention 28 | KeyCode::F1 => sequences::F1, 29 | KeyCode::F2 => sequences::F2, 30 | KeyCode::F3 => sequences::F3, 31 | KeyCode::F4 => sequences::F4, 32 | KeyCode::F5 => sequences::F5, 33 | KeyCode::F6 => sequences::F6, 34 | KeyCode::F7 => sequences::F7, 35 | KeyCode::F8 => sequences::F8, 36 | KeyCode::F9 => sequences::F9, 37 | KeyCode::F10 => sequences::F10, 38 | KeyCode::F11 => sequences::F11, 39 | KeyCode::F12 => sequences::F12, 40 | 41 | KeyCode::PageUp => sequences::PageUp, 42 | KeyCode::PageDown => sequences::PageDown, 43 | KeyCode::Home => sequences::Home, 44 | KeyCode::End => sequences::End, 45 | 46 | KeyCode::ArrowUp => sequences::ArrowUp, 47 | KeyCode::ArrowDown => sequences::ArrowDown, 48 | KeyCode::ArrowRight => sequences::ArrowRight, 49 | KeyCode::ArrowLeft => sequences::ArrowLeft, 50 | _ => { 51 | debug_print!("{:?}", key); 52 | continue; 53 | } 54 | } 55 | } 56 | }; 57 | // Send the character(s) in a short message 58 | if let Err((err, _msg)) = syscalls::send(&STDOUT, 59 | message::Message::Short( 60 | message::CHAR, 61 | chars_be, 0)) { 62 | // Failed to send. Probably not much to be done except panic. 63 | panic!("[keyboard] Send: {}", err); 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /kernel/src/time.rs: -------------------------------------------------------------------------------- 1 | 2 | use core::arch::asm; 3 | use core::sync::atomic::{AtomicU64, Ordering}; 4 | 5 | use crate::memory; 6 | 7 | /// The Programmable Interrupt Timer frequency divider 8 | const PIT_TICKS_PER_INTERRUPT: u64 = 65536; 9 | 10 | /// Cumulative number of PIT ticks since start 11 | static PIT_TICKS: AtomicU64 = AtomicU64::new(0); 12 | 13 | /// Previous value of Time Stamp Counter 14 | static LAST_TSC: AtomicU64 = AtomicU64::new(0); 15 | 16 | static TSC_PER_PIT: AtomicU64 = AtomicU64::new(0); 17 | 18 | /// Read the processor's Time Stamp Counter 19 | /// uses RDTSC 20 | /// 21 | fn time_stamp_counter() -> u64 { 22 | let counter: u64; 23 | unsafe{ 24 | asm!("rdtsc", 25 | "shl rdx, 32", // High bits in EDX 26 | "or rdx, rax", // Low bits in EAX 27 | out("rdx") counter, 28 | out("rax") _, // Clobbers RAX 29 | options(pure, nomem, nostack) 30 | ); 31 | } 32 | counter 33 | } 34 | 35 | /// This function is called by the timer interrupt handler 36 | pub fn pit_interrupt_notify() { 37 | // Increment the number of PIT ticks 38 | PIT_TICKS.fetch_add(PIT_TICKS_PER_INTERRUPT, Ordering::Relaxed); 39 | 40 | // Get the change in TSC from last time, and update moving average of 41 | // TSC ticks per PIT tick. 42 | let new_tsc = time_stamp_counter(); 43 | let last_tsc = LAST_TSC.swap(new_tsc, Ordering::Relaxed); 44 | let new_tsc_per_pit = (new_tsc - last_tsc) / PIT_TICKS_PER_INTERRUPT; 45 | let ma_tsc_per_pit = (new_tsc_per_pit + TSC_PER_PIT.load(Ordering::Relaxed)) / 2; 46 | TSC_PER_PIT.store(ma_tsc_per_pit, Ordering::Relaxed); 47 | 48 | // Store in user-accessible KernelInfo page 49 | let info = memory::kernel_info::get_mut(); 50 | info.pit_ticks = PIT_TICKS.load(Ordering::Relaxed); 51 | info.last_tsc = new_tsc; 52 | info.tsc_per_pit = ma_tsc_per_pit; 53 | } 54 | 55 | /// Monotonic count of he number of microseconds since restart 56 | /// 57 | /// Uses PIT interrupts to calibrate the TSC 58 | /// 59 | pub fn microseconds_monotonic() -> u64 { 60 | // Number of PIT ticks 61 | let pit = PIT_TICKS.load(Ordering::Relaxed); 62 | // Number of TSC ticks since last PIT interrupt 63 | let tsc = time_stamp_counter() - LAST_TSC.load(Ordering::Relaxed); 64 | 65 | // Number of TSC counts per PIT tick 66 | let tsc_per_pit = TSC_PER_PIT.load(Ordering::Relaxed); 67 | 68 | // PIT frequency is 3_579_545 / 3 = 1_193_181.666 Hz 69 | // each PIT tick is 0.83809534452 microseconds 70 | // 878807 / (1024*1024) = 0.83809566497 71 | // 72 | // Calculate total TSC then divide to get microseconds 73 | // Note: Don't use TSC directly because jitter in tsc_per_pit would lead to 74 | // non-monotonic outputs 75 | 76 | // Note! This next expression will overflow in about 2 hours : 77 | // 2**64 / (1024 * 1024 * 2270) microseconds 78 | //((pit * tsc_per_pit + tsc) * 878807) / (1024*1024 * tsc_per_pit) 79 | 80 | const SCALED_TSC_RATE: u64 = 16; 81 | let scaled_tsc = (tsc * SCALED_TSC_RATE) / tsc_per_pit; 82 | 83 | // Factorize 878807 = 437 * 2011 84 | // This will overflow in about 142 years : 2**64 / 4096 microseconds 85 | ((((pit * SCALED_TSC_RATE + scaled_tsc) * 2011) / 4096) * 437) / (256 * SCALED_TSC_RATE) 86 | } 87 | -------------------------------------------------------------------------------- /doc/kernel_api.org: -------------------------------------------------------------------------------- 1 | * Kernel API 2 | 3 | 4 | 5 | | Syscall | RAX 0-7 | RAX 8-15 | RAX 16-31 | RAX 32-63 | RDI | RSI | RDX | Description | 6 | |-----------------+---------+----------+-----------+-----------+---------+--------------+---------+-----------------------------------------------| 7 | | fork_thread | 0 | | | | | | | Creates a new thread | 8 | | exit_thread | 1 | | | | | | | Terminates the calling thread | 9 | | debug_write | 2 | | | | ptr | len | | Prints direct to screen. Will be removed | 10 | | receive | 3 | | | | | | | Wait for a message | 11 | | send | 4 | | | | | | | Send a message | 12 | | send_receive | 5 | | | | | | | Send and wait for reply from receiving thread | 13 | | open | 6 | | | | ptr | len | | Open a mounted filesystem | 14 | | malloc | 7 | | | | | | | Allocate a chunk of memory pages | 15 | | free | 8 | | | | | | | Deallocate a chunk of pages | 16 | | yield | 9 | | | | | | | Puts current thread back into the scheduler | 17 | | new_rendezvous | 10 | | | | | | | Creates a new pair of Rendezvous handles | 18 | | copy_rendezvous | 11 | | | | | | | Copies a Rendezvous handle | 19 | | exec | 12 | flags | param_len | bin_len | bin_ptr | stdin/stdout | vfs_ptr | Create a new process | 20 | | mount | 13 | | | | | | | Mount a Rendezvous into the process' VFS | 21 | | list_mounts | 14 | | | | | | | List all mounted paths in the process' VFS | 22 | | umount | 15 | | | | ptr | len | | Remove a Rendezvous from the VFS | 23 | | close | 16 | | | | | | | Close a Rendezvous handle | 24 | | await_interrupt | 17 | | | | | | | Wait until a hardware interrupt occurs | 25 | 26 | ** Thread and process management 27 | 28 | New processes are created with =exec= 29 | 30 | Processes can create new threads with the =fork_thread= system call 31 | 32 | Threads can exit with the =exit_thread= syscall. Unlike Linux (for 33 | example) there is no "main" thread: All threads are treated the same, 34 | and the process stops when the last thread exits. 35 | 36 | -------------------------------------------------------------------------------- /euralios_std/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![cfg_attr(test, no_main)] 3 | #![feature(alloc_error_handler)] 4 | #![feature(custom_test_frameworks)] 5 | #![test_runner(crate::test_runner)] 6 | #![reexport_test_harness_main = "test_main"] 7 | 8 | use core::{slice, arch::asm}; 9 | 10 | /// Re-export core modules 11 | pub use core::fmt; 12 | pub use core::str; 13 | pub use core::iter; 14 | 15 | pub mod console; 16 | pub mod debug; 17 | pub mod env; 18 | pub mod ffi; 19 | pub mod fs; 20 | pub mod io; 21 | pub mod memory; 22 | pub mod message; // EuraliOS-only 23 | pub mod net; 24 | pub mod path; 25 | pub mod ports; 26 | pub mod syscalls; // EuraliOS-only 27 | pub mod thread; 28 | pub mod time; 29 | pub mod sys; 30 | pub mod server; // EuraliOS-only 31 | 32 | use core::panic::PanicInfo; 33 | #[panic_handler] 34 | pub fn panic(info: &PanicInfo) -> ! { 35 | debug_println!("User panic: {}", info); 36 | syscalls::thread_exit(); 37 | } 38 | 39 | // User program entry point 40 | #[cfg(not(test))] 41 | extern { 42 | fn main() -> (); 43 | } 44 | 45 | // The arguments and environment string slices are set in _start and 46 | // then constant. They point to regions of the user stack that were 47 | // filled by the kernel. 48 | static mut ARGS_SLICE: &[u8] = b""; 49 | static mut ENV_SLICE: &[u8] = b""; 50 | 51 | pub fn get_args() -> &'static [u8] { 52 | // Note: Only reading from static mut once program starts 53 | return unsafe{ ARGS_SLICE }; 54 | } 55 | 56 | pub fn get_env() -> &'static [u8] { 57 | return unsafe{ ENV_SLICE }; 58 | } 59 | 60 | #[no_mangle] 61 | pub unsafe extern "sysv64" fn _start() -> ! { 62 | // Information passed from the operating system 63 | let heap_start: usize; 64 | let heap_size: usize; 65 | let args_address: usize; 66 | let env_address: usize; 67 | asm!("", 68 | lateout("rax") heap_start, 69 | lateout("rcx") heap_size, 70 | lateout("rdx") args_address, 71 | lateout("rdi") env_address, 72 | options(pure, nomem, nostack) 73 | ); 74 | memory::init(heap_start, heap_size); 75 | 76 | if args_address != 0 { 77 | // Command-line arguments 78 | 79 | ARGS_SLICE = unsafe{ 80 | let length = *(args_address as *mut i32) as usize; 81 | slice::from_raw_parts((args_address + 4) as *const u8, length) 82 | }; 83 | } 84 | 85 | if env_address != 0 { 86 | // Environment 87 | 88 | ENV_SLICE = unsafe{ 89 | let length = *(env_address as *mut i32) as usize; 90 | slice::from_raw_parts((env_address + 4) as *const u8, length) 91 | }; 92 | } 93 | 94 | // Call the user program 95 | #[cfg(not(test))] 96 | main(); 97 | 98 | #[cfg(test)] 99 | test_main(); 100 | 101 | syscalls::thread_exit(); 102 | } 103 | 104 | // Custom test framework 105 | 106 | pub trait Testable { 107 | fn run(&self) -> (); 108 | } 109 | 110 | // Implement Testable trait for all types with Fn() trait 111 | impl Testable for T 112 | where 113 | T: Fn(), 114 | { 115 | fn run(&self) { 116 | print!("{}...\t", core::any::type_name::()); 117 | self(); 118 | println!("[ok]"); 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | pub fn test_runner(tests: &[&dyn Testable]) { 124 | println!("Running {} tests", tests.len()); 125 | for test in tests { 126 | test.run(); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /login/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | use alloc::string::String; 6 | use alloc::vec::Vec; 7 | 8 | use euralios_std::{fs::{File, OpenOptions}, 9 | io, 10 | message, 11 | path::Path, 12 | print, println, 13 | syscalls::{self, SyscallError, VFS}}; 14 | 15 | 16 | fn exec_path(path: &Path, vfs: VFS) -> Result<(), SyscallError> { 17 | // Read binary from file 18 | let bin = { 19 | let mut bin: Vec = Vec::new(); 20 | let mut file = File::open(path)?; 21 | file.read_to_end(&mut bin)?; 22 | bin 23 | }; 24 | 25 | // Create a communication handle for the input 26 | let (exe_input, exe_input2) = syscalls::new_rendezvous()?; 27 | 28 | syscalls::exec( 29 | &bin, 30 | 0, // Permission flags 31 | exe_input2, 32 | syscalls::STDOUT.clone(), 33 | vfs, 34 | Vec::new(), 35 | "")?; 36 | 37 | loop { 38 | // Wait for keyboard input 39 | match syscalls::receive(&syscalls::STDIN) { 40 | Ok(syscalls::Message::Short( 41 | message::CHAR, ch, _)) => { 42 | // Received a character 43 | if let Err((err, _)) = syscalls::send(&exe_input, 44 | syscalls::Message::Short( 45 | message::CHAR, ch, 0)) { 46 | println!("Received error: {}", err); 47 | return Ok(()); 48 | } 49 | }, 50 | _ => { 51 | // Ignore 52 | } 53 | } 54 | } 55 | } 56 | 57 | #[no_mangle] 58 | fn main() { 59 | 60 | let stdin = io::stdin(); 61 | let mut username = String::new(); 62 | 63 | println!("Welcome to EuraliOS! 64 | 65 | [F1] switches to system console 66 | [F2..F5] user consoles 67 | 68 | "); 69 | 70 | loop { 71 | print!("login: "); 72 | 73 | username.clear(); 74 | stdin.read_line(&mut username); 75 | 76 | // Here we could ask for a password, compare to hash stored 77 | // in a password file that users can't directly access. 78 | 79 | // For now just hard-wire some users: 80 | let vfs = match username.trim() { 81 | "root" => VFS::shared(), // Root sees everything 82 | "user" => { 83 | // Open bin directory read-only 84 | let bin = OpenOptions::new().open("/ramdisk/bin").unwrap(); 85 | // User's home directory read-write 86 | let home = OpenOptions::new().write(true).open("/ramdisk/user").unwrap(); 87 | // TCP stack read-write 88 | let tcp = OpenOptions::new().write(true).open("/tcp").unwrap(); 89 | VFS::new() 90 | .mount(bin.to_CommHandle(), "/bin") 91 | .mount(home.to_CommHandle(), "/ramdisk") 92 | .mount(tcp.to_CommHandle(), "/tcp") 93 | }, 94 | _ => { 95 | println!("Unknown login. Try 'root' or 'user'..."); 96 | continue; 97 | } 98 | }; 99 | 100 | if let Err(err) = exec_path(Path::new("/ramdisk/bin/shell"), vfs) { 101 | println!("Error executing shell: {}", err); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /kernel/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(custom_test_frameworks)] 4 | #![test_runner(blog_os::test_runner)] 5 | #![reexport_test_harness_main = "test_main"] 6 | 7 | use core::panic::PanicInfo; 8 | use kernel::println; 9 | use bootloader::{BootInfo, entry_point}; 10 | extern crate alloc; 11 | use alloc::{vec::Vec, sync::Arc}; 12 | use spin::RwLock; 13 | 14 | use kernel::memory; 15 | use kernel::syscalls; 16 | use kernel::process; 17 | use kernel::rendezvous::Rendezvous; 18 | use kernel::vfs; 19 | use kernel::message::{self, Message}; 20 | 21 | entry_point!(kernel_entry); 22 | 23 | /// Main kernel thread entry point 24 | /// This is the first process added to the scheduler 25 | /// which is started once basic kernel functions have 26 | /// been initialised in kernel_entry 27 | fn kernel_thread_main() { 28 | // User-space init process 29 | let null = Arc::new(RwLock::new(Rendezvous::Empty)); 30 | let init_screen = Arc::new(RwLock::new(Rendezvous::Empty)); 31 | let init_thread = process::new_user_thread( 32 | include_bytes!("../../user/init"), 33 | process::Params{ 34 | handles: Vec::from([ 35 | // Null input 36 | null, 37 | // Output used to pass data 38 | init_screen.clone() 39 | ]), 40 | io_privileges: true, 41 | mounts: vfs::VFS::new(), // Create a Virtual File System 42 | args: Vec::new(), 43 | envs: Vec::new(), 44 | }).unwrap(); 45 | 46 | // Allocate a memory chunk mapping video memory 47 | let (virtaddr, _) = process::special_memory_chunk( 48 | &init_thread, 49 | 32, // Pages, 128k. 0xC0000 - 0xA0000 50 | 0xA0000).unwrap(); 51 | 52 | // Remove chunk from table so it can be sent 53 | let (physaddr, _) = init_thread.take_memory_chunk(virtaddr).unwrap(); 54 | 55 | // Send a message to init process containing the chunk. 56 | // When received the chunk will be mapped into address space 57 | init_screen.write().send(None, Message::Long( 58 | message::VIDEO_MEMORY, 59 | (0xC0000 - 0xA0000).into(), 60 | physaddr.into() 61 | )); 62 | 63 | process::schedule_thread(init_thread); 64 | 65 | kernel::hlt_loop(); 66 | } 67 | 68 | /// Function called by the bootloader 69 | /// via _start entry point declared in entry_point! above 70 | /// 71 | /// Inputs 72 | /// BootInfo Bootloader memory mapping information 73 | /// 74 | fn kernel_entry(boot_info: &'static BootInfo) -> ! { 75 | kernel::init(); 76 | 77 | // Set up memory and kernel heap with allocator 78 | memory::init(boot_info); 79 | 80 | // Set up system calls 81 | syscalls::init(); 82 | 83 | #[cfg(test)] 84 | test_main(); 85 | 86 | // Launch the main kernel thread 87 | // which will be scheduled and take over from here 88 | process::new_kernel_thread(kernel_thread_main, Vec::new()); 89 | 90 | kernel::hlt_loop(); 91 | } 92 | 93 | /// This function is called on panic. 94 | #[cfg(not(test))] // If not in QEMU test mode 95 | #[panic_handler] 96 | fn panic(info: &PanicInfo) -> ! { 97 | println!("Kernel panic: {}", info); 98 | kernel::hlt_loop(); 99 | } 100 | 101 | #[cfg(test)] 102 | #[panic_handler] 103 | fn panic(info: &PanicInfo) -> ! { 104 | kernel::test_panic_handler(info) 105 | } 106 | 107 | #[test_case] 108 | fn trivial_assertion() { 109 | assert_eq!(1, 1); 110 | } 111 | -------------------------------------------------------------------------------- /kernel/src/vfs.rs: -------------------------------------------------------------------------------- 1 | //! Virtual File System 2 | 3 | use spin::RwLock; 4 | use alloc::{vec::Vec, sync::Arc}; 5 | use alloc::string::String; 6 | use core::convert::From; 7 | 8 | use crate::rendezvous::Rendezvous; 9 | 10 | /// Virtual File System type 11 | #[derive(Clone)] 12 | pub struct VFS(Arc>)>>>); 13 | 14 | impl From>)>> for VFS { 15 | fn from( 16 | mounts: Vec<(String, Arc>)> 17 | ) -> Self { 18 | VFS(Arc::new(RwLock::new(mounts))) 19 | } 20 | } 21 | 22 | impl VFS { 23 | /// Create an empty set of mount points 24 | pub fn new() -> Self { 25 | VFS(Arc::new(RwLock::new(Vec::new()))) 26 | } 27 | 28 | /// Add a mount point to the VFS 29 | /// 30 | /// The path will have leading and trailing whitespace removed, 31 | /// and all trailing '/' characters removed. 32 | /// 33 | /// It is expected, though not technically required, for the 34 | /// path to start with '/' 35 | pub fn mount(&mut self, 36 | path: &str, 37 | rendezvous: Arc>) { 38 | let path = path.trim().trim_end_matches('/'); 39 | self.0.write().push((String::from(path), rendezvous)); 40 | } 41 | 42 | /// Remove a mount point from the VFS 43 | pub fn umount(&mut self, 44 | path: &str) -> Result<(),()> { 45 | let mut mounts = self.0.write(); 46 | if let Some(index) = mounts.iter().position( 47 | |mount_path| mount_path.0 == path) { 48 | // Found an index 49 | _ = mounts.swap_remove(index); 50 | return Ok(()); 51 | } 52 | Err(()) 53 | } 54 | 55 | /// Open a path, returning a handle to read/write and the 56 | /// length of the string matched. 57 | pub fn open(&self, 58 | path: &str) -> Option<(Arc>, usize)> { 59 | let mounts = self.0.read(); 60 | let mut found: Option<(usize, usize)> = None; 61 | for (i, mount_path) in mounts.iter().enumerate() { 62 | if path.starts_with(&mount_path.0) { 63 | let len = mount_path.0.len(); 64 | if path.len() > len { 65 | // The path should be a subdirectory 66 | // - the next character in path should be "/" 67 | // Note: str.len() is bytes, not characters 68 | if path.as_bytes()[len] != b'/' { 69 | // Not this path 70 | continue; 71 | } 72 | } 73 | // Choose the longest match 74 | if let Some((_, maxlen)) = found { 75 | if len > maxlen { 76 | found = Some((i, len)); 77 | } 78 | } else { 79 | found = Some((i, len)); 80 | } 81 | } 82 | } 83 | if let Some((ind, match_len)) = found { 84 | return Some((mounts[ind].1.clone(), match_len)); 85 | } 86 | None 87 | } 88 | 89 | /// Return a list of mount points as a JSON string 90 | pub fn to_json(&self) -> String { 91 | let mounts = self.0.read(); 92 | 93 | let mut s = String::new(); 94 | s.push('['); 95 | for mount_path in mounts.iter() { 96 | s.push('"'); 97 | s.push_str(&mount_path.0); 98 | s.push('"'); 99 | s.push(','); 100 | } 101 | s.push(']'); 102 | s 103 | } 104 | 105 | /// Make a copy of the internal state 106 | pub fn copy(&self) -> Self { 107 | VFS(Arc::new(RwLock::new(self.0.read().clone()))) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /arp/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use euralios_std::{debug_println, 5 | syscalls, 6 | net::MacAddress, 7 | message::{self, rcall, nic}}; 8 | 9 | #[no_mangle] 10 | fn main() { 11 | debug_println!("[arp] Starting"); 12 | 13 | // Open link to the network interface driver 14 | let handle = syscalls::open("/dev/nic", message::O_READ).expect("Couldn't open /dev/nic"); 15 | 16 | // Get the hardware MAC address 17 | let (_, ret, _) = rcall(&handle, nic::GET_MAC_ADDRESS, 18 | 0.into(), 0.into(), 19 | Some(message::nic::MAC_ADDRESS)).unwrap(); 20 | 21 | let mac_address = MacAddress::from_u64(ret.value()); 22 | debug_println!("[arp] MAC address: {}", mac_address); 23 | let mac = mac_address.bytes(); 24 | 25 | // Ethernet frame format 26 | // containing an ARP packet 27 | // https://wiki.osdev.org/Address_Resolution_Protocol 28 | let frame = [ 29 | // Ethernet frame header 30 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Destination MAC address 31 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], // Source address 32 | 0x08, 0x06, // Ethernet protocol type: Ipv4 = 0x0800, Arp = 0x0806, Ipv6 = 0x86DD 33 | // ARP packet 34 | 0, 1, // u16 Hardware type (Ethernet is 0x1) 35 | 8, 0, // u16 Protocol type (IP is 0x0800) 36 | 6, // u8 hlen, Hardware address length (Ethernet = 6) 37 | 4, // u8 plen, Protocol address length (IPv4 = 4) 38 | 0, 1, // u16 ARP Operation Code (ARP request = 0x0001) 39 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], // Source hardware address - hlen bytes 40 | 10, 0, 2, 15, // Source protocol address - plen bytes 41 | 0, 0, 0, 0, 0, 0, // Destination hardware address 42 | 10, 0, 2, 2 // Destination protocol address 43 | ]; 44 | 45 | // Copy packet into a memory chunk that can be sent 46 | let mem_handle = syscalls::MemoryHandle::from_u8_slice(&frame); 47 | 48 | syscalls::send(&handle, 49 | message::Message::Long( 50 | message::WRITE, 51 | (frame.len() as u64).into(), 52 | mem_handle.into())); 53 | 54 | // Wait for a packet 55 | loop { 56 | match rcall(&handle, message::READ, 57 | 0.into(), 0.into(), 58 | None).unwrap() { 59 | (message::DATA, md_length, md_handle) => { 60 | // Get the memory handle 61 | let handle = md_handle.memory(); 62 | 63 | // Get the ethernet frame via a &[u8] slice 64 | let frame = handle.as_slice::(md_length.value() as usize); 65 | let from_mac = MacAddress::new(frame[0..6].try_into().unwrap()); 66 | let to_mac = MacAddress::new(frame[6..12].try_into().unwrap()); 67 | debug_println!("Ethernet frame: to {} from {} type {:02x}{:02x}", 68 | from_mac, to_mac, frame[12], frame[13]); 69 | 70 | // ARP packet 71 | let arp = &frame[14..]; 72 | 73 | debug_println!("ARP packet: hw {:02x}{:02x} protocol {:02x}{:02x} hlen {:02x} plen {:02x} op {:02x}{:02x}", 74 | arp[0], arp[1], arp[2], arp[3], arp[4], arp[5], arp[6], arp[7]); 75 | debug_println!(" source {} / {}.{}.{}.{}", 76 | MacAddress::new(arp[8..14].try_into().unwrap()), arp[14], arp[15], arp[16], arp[17]); 77 | debug_println!(" target {} / {}.{}.{}.{}", 78 | MacAddress::new(arp[18..24].try_into().unwrap()), arp[24], arp[25], arp[26], arp[27]); 79 | break; 80 | } 81 | _ => { 82 | // Wait and retry 83 | syscalls::thread_yield(); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /euralios_std/src/io.rs: -------------------------------------------------------------------------------- 1 | //! Input/Output 2 | 3 | use core::fmt; 4 | 5 | extern crate alloc; 6 | use alloc::string::String; 7 | 8 | use crate::{syscalls::{self, CommHandle, SyscallError, STDIN, STDOUT}, 9 | message::{self, rcall}}; 10 | 11 | struct Writer<'a> { 12 | handle: &'a CommHandle 13 | } 14 | 15 | impl fmt::Write for Writer<'_> { 16 | fn write_str(&mut self, s: &str) -> fmt::Result { 17 | if s.len() == 0 { 18 | return Ok(()); 19 | } 20 | _ = rcall(self.handle, 21 | message::WRITE, 22 | (s.len() as u64).into(), 23 | syscalls::MemoryHandle::from_u8_slice(s.as_ref()).into(), 24 | None); 25 | Ok(()) 26 | } 27 | } 28 | 29 | pub fn _print(handle: &CommHandle, args: fmt::Arguments) { 30 | use core::fmt::Write; 31 | Writer{handle}.write_fmt(args).unwrap(); 32 | } 33 | 34 | #[macro_export] 35 | macro_rules! print { 36 | ($($arg:tt)*) => ($crate::io::_print( 37 | &$crate::syscalls::STDOUT, format_args!($($arg)*))); 38 | } 39 | 40 | #[macro_export] 41 | macro_rules! println { 42 | () => ($crate::print!("\n")); 43 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); 44 | } 45 | 46 | #[macro_export] 47 | macro_rules! fprint { 48 | ($handle:expr, $($arg:tt)*) => ($crate::io::_print( 49 | $handle, format_args!($($arg)*))); 50 | } 51 | 52 | #[macro_export] 53 | macro_rules! fprintln { 54 | ($handle:expr) => ($crate::fprint!($handle, "\n")); 55 | ($handle:expr, $($arg:tt)*) => ($crate::fprint!($handle, "{}\n", format_args!($($arg)*))); 56 | } 57 | 58 | //////////////////////////////////////////// 59 | // 60 | 61 | pub struct Stdin {} 62 | 63 | /// Constructs a new handle to the standard input of the current process. 64 | /// 65 | /// Intended to have the same interface as the Rust std::io 66 | /// 67 | pub fn stdin() -> Stdin { 68 | Stdin{} 69 | } 70 | 71 | impl Stdin { 72 | /// reads a line of input, appending it to the specified buffer. 73 | /// 74 | /// API from : 75 | /// 76 | /// Read all bytes until a newline (the 0xA byte) is reached, and 77 | /// append them to the provided buffer. You do not need to clear 78 | /// the buffer before appending. 79 | /// 80 | /// This function will read bytes from the underlying stream until 81 | /// the newline delimiter (the 0xA byte) or EOF is found. Once 82 | /// found, all bytes up to, and including, the delimiter (if 83 | /// found) will be appended to buf. 84 | /// 85 | /// If successful, this function will return the total number of bytes read. 86 | /// 87 | /// If this function returns Ok(0), the stream has reached EOF. 88 | /// 89 | /// This function is blocking and should be used carefully: it is 90 | /// possible for an attacker to continuously send bytes without 91 | /// ever sending a newline or EOF. 92 | /// 93 | pub fn read_line(&self, buf: &mut String) -> Result { 94 | let mut length = 0; 95 | loop { 96 | match syscalls::receive(&STDIN) { 97 | Ok(syscalls::Message::Short( 98 | message::CHAR, ch, _)) => { 99 | // Received a character 100 | if ch == 0x8 { 101 | // Backspace 102 | if let Some(_) = buf.pop() { 103 | // If there is a character to remove 104 | // Erase by overwriting with a space 105 | print!("\u{08} \u{08}"); 106 | } 107 | } else { 108 | // Echo character to stdout 109 | _ = syscalls::send(&STDOUT, syscalls::Message::Short( 110 | message::CHAR, ch, 0)); // Not really bothered if it fails 111 | if let Some(utf_ch) = char::from_u32(ch as u32) { 112 | // If it's a UTF char then append to buffer 113 | buf.push(utf_ch); 114 | length += 1; 115 | if ch == 0xA { 116 | return Ok(length); 117 | } 118 | } 119 | } 120 | } 121 | _ => { 122 | // Ignore 123 | } 124 | } 125 | } 126 | } 127 | } 128 | 129 | -------------------------------------------------------------------------------- /doc/journal/17-tcp-stack.org: -------------------------------------------------------------------------------- 1 | * TCP network stack 2 | 3 | In the [[./16-arp.org][last section]] we implemented a simple Address Resolution 4 | Protocol (ARP) program, which used the network driver to get hardware 5 | addresses from IP addresses. That is one part of a network stack, but 6 | developing a robust and reasonably complete network stack is a huge 7 | undertaking of its own. Fortunately someone has already done this in 8 | =no_std= Rust, so we'll use the [[https://docs.rs/smoltcp/latest/smoltcp/][smoltcp]] crate to add a network stack 9 | to EuraliOS. 10 | 11 | We'll create a new program =tcp= 12 | #+begin_src bash 13 | cargo new tcp 14 | #+end_src 15 | 16 | The traits to implement a physical layer which [[https://docs.rs/smoltcp/latest/smoltcp/][smoltcp]] can use are 17 | [[https://github.com/smoltcp-rs/smoltcp/blob/master/src/phy/mod.rs][defined here]]. A =smoltcp::phy::Device= needs to implement 18 | =capabilities()=, =receive()= and =transmit()= functions. Those should 19 | return objects which implement =smoltcp::phy::RxToken= and 20 | =smoltcp::phy::TxToken= traits. =RxToken= represents received data 21 | which can be obtained with a =consume()= method; =TxToken='s 22 | =consume()= function takes data and sends it. 23 | 24 | The TCP program will communicate with the network card driver, so the 25 | only thing the =Device= needs to contain is a communication handle: 26 | #+begin_src rust 27 | struct EthernetDevice { 28 | handle: Arc 29 | } 30 | 31 | impl EthernetDevice { 32 | fn new(handle: syscalls::CommHandle) -> Self { 33 | EthernetDevice{handle:Arc::new(handle)} 34 | } 35 | } 36 | #+end_src 37 | We wrap the handle in an =Arc= because we're going to need multiple 38 | references to it in the tokens. We can initialise it by first opening 39 | a handle for the NIC and passing in the handle: 40 | #+begin_src rust 41 | let handle = syscalls::open("/dev/nic").expect("Couldn't open /dev/nic"); 42 | let device = EthernetDevice::new(handle); 43 | #+end_src 44 | 45 | The =Device= trait is 46 | #+begin_src rust 47 | impl<'a> smoltcp::phy::Device<'a> for EthernetDevice { 48 | type RxToken = RxToken; 49 | type TxToken = TxToken; 50 | 51 | fn capabilities(&self) -> DeviceCapabilities { 52 | ... 53 | } 54 | 55 | fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { 56 | ... 57 | } 58 | 59 | fn transmit(&'a mut self) -> Option { 60 | ... 61 | } 62 | #+end_src 63 | The capabilities method should tell TCP something about the 64 | capabilities of the specific network card being used. For now we'll 65 | just hard-wire some safe defaults for the maximum packet size, and 66 | maximum number of packets in a "burst": 67 | #+begin_src rust 68 | fn capabilities(&self) -> DeviceCapabilities { 69 | let mut caps = DeviceCapabilities::default(); 70 | caps.max_transmission_unit = 1500; 71 | caps.max_burst_size = Some(1); 72 | caps 73 | } 74 | #+end_src 75 | 76 | ** Transmitting data 77 | 78 | To transmit data the caller uses =transmit()= to get a =TxToken=, and then 79 | the =consume()= method on the =TxToken=. Since we don't know how much data 80 | will be sent, all we can do in =transmit()= is copy the communication handle: 81 | #+begin_src rust 82 | fn transmit(&'a mut self) -> Option { 83 | Some(TxToken{handle: self.handle.clone()}) 84 | } 85 | #+end_src 86 | where =TxToken= is just: 87 | #+begin_src rust 88 | struct TxToken { 89 | handle: Arc 90 | } 91 | #+end_src 92 | 93 | The actual communication is performed when the =consume()= method is 94 | called. That is given the length (in =u8= chars) and a function to be 95 | called to fill the buffer: 96 | #+begin_src rust 97 | impl smoltcp::phy::TxToken for TxToken { 98 | fn consume(mut self, 99 | _timestamp: Instant, 100 | length: usize, f: F 101 | ) -> smoltcp::Result where F: FnOnce(&mut [u8]) -> smoltcp::Result { 102 | // Allocate memory buffer 103 | let (mut buffer, _) = syscalls::malloc(length as u64, 0).unwrap(); 104 | 105 | // Call function to fill buffer 106 | let res = f(buffer.as_mut_slice::(length)); 107 | 108 | if res.is_ok() { 109 | // Transmit, sending buffer to NIC driver 110 | syscalls::send( 111 | self.handle.as_ref(), 112 | message::Message::Long( 113 | message::WRITE, 114 | (length as u64).into(), 115 | buffer.into())); 116 | } 117 | res 118 | } 119 | } 120 | #+end_src 121 | 122 | ** Dynamic Host Configuration Protocol (DHCP) 123 | 124 | The DHCP protocol is a standard way to configure devices on a local 125 | network. It provides a way to discover the network gateway, DNS 126 | server, and be assigned an IP address. 127 | 128 | #+CAPTION: Using smoltcp to configure IP settings with DHCP 129 | #+NAME: fig-dhcp 130 | [[./img/17-01-dhcp.png]] 131 | 132 | In the [[./18-gopher.org][next section]] we'll try out TCP by writing a simple [[https://en.wikipedia.org/wiki/Gopher_(protocol)][Gopher]] 133 | protocol browser. 134 | -------------------------------------------------------------------------------- /doc/journal/23-keyboard.org: -------------------------------------------------------------------------------- 1 | * Interrupts and a better keyboard 2 | 3 | 4 | ** Awaiting an interrupt 5 | 6 | When an interrupt occurs we need a way to be able to transfer 7 | execution to a user program. One option is to use a Rendezvous 8 | system: User programs open a Rendezvous handle and wait for 9 | a message to be sent when an interrupt occurs. A problem with 10 | this is that multiple devices can share the same interrupt. 11 | 12 | 13 | #+begin_src rust 14 | lazy_static! { 15 | static ref INTERRUPT_WAITING: Arc>>> = 16 | Arc::new(RwLock::new(Vec::new())); 17 | } 18 | 19 | /// Store a thread, to be scheduled when an interrupt occurs 20 | pub fn await_interrupt(thread: Box) { 21 | INTERRUPT_WAITING.write().push(thread); 22 | } 23 | #+end_src 24 | 25 | Then when an interrupt occurs we schedule any threads that are waiting: 26 | #+begin_src rust 27 | extern "C" fn keyboard_handler_inner( 28 | context_addr: usize 29 | ) -> usize { 30 | // Schedule threads if waiting 31 | for thread in INTERRUPT_WAITING.write().drain(..) { 32 | process::schedule_thread(thread); 33 | } 34 | ... 35 | } 36 | #+end_src 37 | 38 | In =syscalls.rs= we create a new system call: 39 | #+begin_src rust 40 | pub const SYSCALL_AWAIT_INTERRUPT: u64 = 17; 41 | #+end_src 42 | dispatch it: 43 | #+begin_src rust 44 | match syscall_id & SYSCALL_MASK { 45 | ... 46 | SYSCALL_AWAIT_INTERRUPT => sys_await_interrupt(context_ptr, arg1), 47 | ... 48 | } 49 | #+end_src 50 | The actual implementation is quite simple: We take the current thread, 51 | pass it to =interrupts::await_interrupt= to store, and launch the next 52 | thread from the scheduler. 53 | 54 | In the user library =euralios_std::syscalls= the =await_interrupt= function 55 | is just: 56 | #+begin_src rust 57 | pub fn await_interrupt() { 58 | unsafe { 59 | asm!("syscall", 60 | // RAX contains syscall 61 | in("rax") SYSCALL_AWAIT_INTERRUPT, 62 | out("rcx") _, 63 | out("r11") _); 64 | } 65 | } 66 | #+end_src 67 | Soon we will want to add an argument to specify which interrupt to 68 | wait for, and add some permissions so that only some user programs can 69 | call this function. 70 | 71 | ** Moving keyboard handler to userspace 72 | 73 | If we move all the keyboard handling into a userspace (Ring 3) driver, 74 | all the kernel has to do is provide an interrupt handler which 75 | wakes up threads when an interrupt occurs. The keyboard handler 76 | can schedule all waiting threads and run one of them: 77 | #+begin_src rust 78 | extern "C" fn keyboard_handler_inner( 79 | context_addr: usize 80 | )-> usize { 81 | 82 | // Change to a new thread if there is one waiting 83 | if !INTERRUPT_WAITING.read().is_empty() { 84 | 85 | // Schedule waiting threads 86 | for thread in INTERRUPT_WAITING.write().drain(..) { 87 | // Note: This adds to the front of the queue 88 | process::schedule_thread(thread); 89 | } 90 | 91 | // Switch to one of the scheduled threads 92 | process::schedule_next(context_addr) 93 | } else { 94 | // Return to interrupted thread 95 | context_addr 96 | } 97 | } 98 | #+end_src 99 | 100 | When a thread wants to wait for an interrupt it calls =await_interrupt=: 101 | #+begin_src rust 102 | /// Store a thread, to be scheduled when an interrupt occurs 103 | pub fn await_interrupt(thread: Box) { 104 | INTERRUPT_WAITING.write().push(thread); 105 | 106 | unsafe { 107 | PICS.lock() 108 | .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); 109 | } 110 | } 111 | #+end_src 112 | 113 | Note that we notify end of interrupt when =await_interrupt= is called, 114 | rather than at the end of =keyboard_handler_inner=; I tried the other 115 | way and found occasional problems with the interrupt handler getting 116 | stuck, for reasons I don't understand (yet). 117 | 118 | ** Mapping special keys 119 | 120 | So far short messages from the keyboard or to the VGA console have 121 | just encoded a single character. It can however contain two 64-bit 122 | numbers or 16 bytes. 123 | 124 | Function keys F1-F12 http://aperiodic.net/phil/archives/Geekery/term-function-keys.html 125 | 126 | [[https://en.wikipedia.org/wiki/ANSI_escape_code#CSIsection][Control Sequence Introducer]] =ESC [= consists of two bytes, =1b9b=, and 127 | the character sequence =ESC [ 1 1 ~= is 0x1b_9b_31_31_7e in big-endian 128 | bytes. 129 | 130 | ** Function keys to switch consoles 131 | 132 | Use F-keys to change consoles, rather than TAB and ESC. 133 | 134 | ** Special keys in gopher program 135 | 136 | Add arrow keys, home. 137 | 138 | ** Entering unicode 139 | 140 | These are some notes for later, because we don't have a way to display 141 | unicode characters. 142 | 143 | US international scientific keyboard: https://michaelgoerz.net/notes/the-us-international-scientific-keyboard-layout.html 144 | 145 | LaTeX to unicode mode: Entering `\` creates a minibuffer, user enters 146 | LaTeX symbol followed by space/tab/enter, and it is converted to a 147 | unicode character. 148 | 149 | https://github.com/JuliaEditorSupport/julia-vim#latex-to-unicode-substitutions 150 | -------------------------------------------------------------------------------- /kernel/src/gdt.rs: -------------------------------------------------------------------------------- 1 | //! Manages the Global Descriptor Table (GDT) and Task State Segment (TSS) 2 | 3 | use x86_64::VirtAddr; 4 | use x86_64::structures::tss::TaskStateSegment; 5 | 6 | use spin::Mutex; 7 | use lazy_static::lazy_static; 8 | 9 | /// Fixed kernel stack which is the same for all processes. Index 0 10 | /// should only be used for interrupts which won't switch contexts 11 | pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; 12 | pub const PAGE_FAULT_IST_INDEX: u16 = 0; 13 | pub const GENERAL_PROTECTION_FAULT_IST_INDEX: u16 = 0; 14 | 15 | /// Used by timer interrupt and syscall to set kernel stack 16 | /// 17 | /// Note: Syscalls must offset the stack location because 18 | /// otherwise syscalls could not be interrupted. 19 | pub const TIMER_INTERRUPT_INDEX: u16 = 1; 20 | pub const KEYBOARD_INTERRUPT_INDEX: u16 = 1; 21 | 22 | /// Use an interrupt stack table entry as temporary storage 23 | /// for the user stack during a syscall. 24 | pub const SYSCALL_TEMP_INDEX: u16 = 2; 25 | 26 | 27 | lazy_static! { 28 | /// The Task State Segment (TSS) 29 | /// 30 | /// In x86-64 mode this contains: 31 | /// - The stack pointer addresses for each privilege level. 32 | /// - Pointer Addresses for the Interrupt Stack Table 33 | /// - Offset Address of the IO permission bitmap. 34 | /// 35 | /// The TSS is static but also mutable so we can change the stack pointers 36 | /// during task switching. To protect access to the TSS a spinlock is used. 37 | /// 38 | /// Notes: 39 | /// - There can be up to 7 IST entries per CPU 40 | /// https://www.kernel.org/doc/Documentation/x86/kernel-stacks 41 | /// 42 | static ref TSS: Mutex = { 43 | let mut tss = TaskStateSegment::new(); 44 | tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { 45 | const STACK_SIZE: usize = 4096 * 5; 46 | static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; 47 | 48 | let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); 49 | let stack_end = stack_start + STACK_SIZE; 50 | stack_end 51 | }; 52 | 53 | // Set initial timer interrupt index. This will be modified to set 54 | // the kernel stack for each thread. Those stacks will contain 55 | // the thread registers and flags. 56 | tss.interrupt_stack_table[TIMER_INTERRUPT_INDEX as usize] = 57 | tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize]; 58 | 59 | Mutex::new(tss) 60 | }; 61 | } 62 | 63 | /// Function to extract a reference with static lifetime 64 | /// 65 | /// Mutex lock returns a MutexGuard, which dereferences to a reference with 66 | /// a lifetime tied to the mutex lock. This function casts the reference 67 | /// to have 'static lifetime which Descriptor::tss_segment expects. 68 | /// 69 | /// Note: Should not be used except in setting the TSS in the GDT 70 | /// 71 | unsafe fn tss_reference() -> &'static TaskStateSegment { 72 | let tss_ptr = &*TSS.lock() as *const TaskStateSegment; 73 | & *tss_ptr 74 | } 75 | 76 | pub fn tss_address() -> u64 { 77 | let tss_ptr = &*TSS.lock() as *const TaskStateSegment; 78 | tss_ptr as u64 79 | } 80 | 81 | /// Set the interrupt stack table entry to a given virtual address 82 | /// 83 | /// This is called to set the kernel stack of the current process 84 | pub fn set_interrupt_stack_table(index: usize, stack_end: VirtAddr) { 85 | TSS.lock().interrupt_stack_table[index] = stack_end; 86 | } 87 | 88 | use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor, SegmentSelector}; 89 | 90 | lazy_static! { 91 | /// Set up the Global Descriptor Table the first time this is accessed 92 | /// This adapted from MOROS https://github.com/vinc/moros/blob/trunk/src/sys/gdt.rs#L37 93 | static ref GDT: (GlobalDescriptorTable, Selectors) = { 94 | let mut gdt = GlobalDescriptorTable::new(); 95 | 96 | let tss = unsafe {tss_reference()}; 97 | 98 | // Ring 0 segments for the kernel 99 | let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); 100 | let data_selector = gdt.add_entry(Descriptor::kernel_data_segment()); 101 | let tss_selector = gdt.add_entry(Descriptor::tss_segment(tss)); 102 | // Ring 3 data and code segments for user code 103 | let user_data_selector = gdt.add_entry(Descriptor::user_data_segment()); 104 | let user_code_selector = gdt.add_entry(Descriptor::user_code_segment()); 105 | (gdt, Selectors { code_selector, data_selector, tss_selector, 106 | user_code_selector, user_data_selector}) 107 | }; 108 | } 109 | 110 | struct Selectors { 111 | code_selector: SegmentSelector, 112 | data_selector: SegmentSelector, 113 | tss_selector: SegmentSelector, 114 | user_data_selector: SegmentSelector, 115 | user_code_selector: SegmentSelector 116 | } 117 | 118 | pub fn init() { 119 | use x86_64::instructions::segmentation::{Segment, CS, DS}; 120 | use x86_64::instructions::tables::load_tss; 121 | 122 | GDT.0.load(); 123 | unsafe { 124 | CS::set_reg(GDT.1.code_selector); 125 | DS::set_reg(GDT.1.data_selector); 126 | load_tss(GDT.1.tss_selector); 127 | } 128 | } 129 | 130 | pub fn get_kernel_segments() -> (SegmentSelector, SegmentSelector) { 131 | (GDT.1.code_selector, GDT.1.data_selector) 132 | } 133 | 134 | pub fn get_user_segments() -> (SegmentSelector, SegmentSelector) { 135 | (GDT.1.user_code_selector, GDT.1.user_data_selector) 136 | } 137 | -------------------------------------------------------------------------------- /kernel/src/vga_buffer.rs: -------------------------------------------------------------------------------- 1 | 2 | #[allow(dead_code)] 3 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 4 | #[repr(u8)] 5 | pub enum Color { 6 | Black = 0, 7 | Blue = 1, 8 | Green = 2, 9 | Cyan = 3, 10 | Red = 4, 11 | Magenta = 5, 12 | Brown = 6, 13 | LightGray = 7, 14 | DarkGray = 8, 15 | LightBlue = 9, 16 | LightGreen = 10, 17 | LightCyan = 11, 18 | LightRed = 12, 19 | Pink = 13, 20 | Yellow = 14, 21 | White = 15, 22 | } 23 | 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 25 | #[repr(transparent)] 26 | struct ColorCode(u8); 27 | 28 | impl ColorCode { 29 | fn new(foreground: Color, background: Color) -> ColorCode { 30 | ColorCode((background as u8) << 4 | (foreground as u8)) 31 | } 32 | } 33 | 34 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 35 | #[repr(C)] 36 | struct ScreenChar { 37 | ascii_character: u8, 38 | color_code: ColorCode, 39 | } 40 | 41 | const BUFFER_HEIGHT: usize = 25; 42 | const BUFFER_WIDTH: usize = 80; 43 | 44 | use volatile::Volatile; 45 | 46 | #[repr(transparent)] 47 | struct Buffer { 48 | chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], 49 | } 50 | 51 | pub struct Writer { 52 | column_position: usize, 53 | color_code: ColorCode, 54 | buffer: &'static mut Buffer, 55 | } 56 | 57 | impl Writer { 58 | pub fn write_byte(&mut self, byte: u8) { 59 | match byte { 60 | b'\n' => self.new_line(), 61 | byte => { 62 | if self.column_position >= BUFFER_WIDTH { 63 | self.new_line(); 64 | } 65 | 66 | let row = BUFFER_HEIGHT - 1; 67 | let col = self.column_position; 68 | 69 | let color_code = self.color_code; 70 | self.buffer.chars[row][col].write(ScreenChar { 71 | ascii_character: byte, 72 | color_code, 73 | }); 74 | self.column_position += 1; 75 | } 76 | } 77 | } 78 | 79 | fn new_line(&mut self) { 80 | for row in 1..BUFFER_HEIGHT { 81 | for col in 0..BUFFER_WIDTH { 82 | let character = self.buffer.chars[row][col].read(); 83 | self.buffer.chars[row - 1][col].write(character); 84 | } 85 | } 86 | self.clear_row(BUFFER_HEIGHT - 1); 87 | self.column_position = 0; 88 | } 89 | 90 | fn clear_row(&mut self, row: usize) { 91 | let blank = ScreenChar { 92 | ascii_character: b' ', 93 | color_code: self.color_code, 94 | }; 95 | for col in 0..BUFFER_WIDTH { 96 | self.buffer.chars[row][col].write(blank); 97 | } 98 | } 99 | 100 | pub fn write_string(&mut self, s: &str) { 101 | for byte in s.bytes() { 102 | match byte { 103 | // printable ASCII byte or newline 104 | 0x20..=0x7e | b'\n' => self.write_byte(byte), 105 | // not part of printable ASCII range 106 | _ => self.write_byte(0xfe), 107 | } 108 | 109 | } 110 | } 111 | } 112 | 113 | use core::fmt; 114 | 115 | impl fmt::Write for Writer { 116 | fn write_str(&mut self, s: &str) -> fmt::Result { 117 | self.write_string(s); 118 | Ok(()) 119 | } 120 | } 121 | 122 | use lazy_static::lazy_static; 123 | use spin::Mutex; 124 | 125 | lazy_static! { 126 | pub static ref WRITER: Mutex = Mutex::new(Writer { 127 | column_position: 0, 128 | color_code: ColorCode::new(Color::Yellow, Color::Black), 129 | buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, 130 | }); 131 | } 132 | 133 | #[macro_export] 134 | macro_rules! print { 135 | ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); 136 | } 137 | 138 | #[macro_export] 139 | macro_rules! println { 140 | () => ($crate::print!("\n")); 141 | ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); 142 | } 143 | 144 | /// Prints the given formatted string to the VGA text buffer 145 | /// through the global `WRITER` instance. 146 | #[doc(hidden)] 147 | pub fn _print(args: fmt::Arguments) { 148 | use core::fmt::Write; 149 | use x86_64::instructions::interrupts; 150 | 151 | interrupts::without_interrupts(|| { 152 | WRITER.lock().write_fmt(args).unwrap(); 153 | }); 154 | } 155 | 156 | ////////////////////////////////////////////////////// 157 | 158 | // verify that println works without panicking 159 | #[test_case] 160 | fn test_println_simple() { 161 | println!("test_println_simple output"); 162 | } 163 | 164 | // ensure that no panic occurs even if many lines are printed 165 | // and lines are shifted off the screen 166 | #[test_case] 167 | fn test_println_many() { 168 | for _ in 0..200 { 169 | println!("test_println_many output"); 170 | } 171 | } 172 | 173 | // verify that the printed lines really appear on the screen 174 | #[test_case] 175 | fn test_println_output() { 176 | use core::fmt::Write; 177 | use x86_64::instructions::interrupts; 178 | 179 | let s = "Some test string that fits on a single line"; 180 | interrupts::without_interrupts(|| { 181 | let mut writer = WRITER.lock(); 182 | writeln!(writer, "\n{}", s).expect("writeln failed"); 183 | for (i, c) in s.chars().enumerate() { 184 | let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); 185 | assert_eq!(char::from(screen_char.ascii_character), c); 186 | } 187 | }); 188 | } 189 | -------------------------------------------------------------------------------- /pci/src/device.rs: -------------------------------------------------------------------------------- 1 | 2 | use core::fmt; 3 | 4 | use crate::ports::PORTS; 5 | 6 | #[derive(Clone, Copy)] 7 | pub struct PciLocation { 8 | pub bus: u16, 9 | pub slot: u16, 10 | pub function: u16 11 | } 12 | 13 | impl fmt::Display for PciLocation { 14 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 15 | write!(f, "{:04X}:{:02X}:{:02X}", 16 | self.bus, self.slot, self.function) 17 | } 18 | } 19 | 20 | impl PciLocation { 21 | pub fn from_address(address: u32) -> PciLocation { 22 | PciLocation{ 23 | function:((address >> 8) & 0b111) as u16, 24 | slot:((address >> 11) & 0b1_1111) as u16, 25 | bus:((address >> 16) & 0xFF) as u16 26 | } 27 | } 28 | 29 | /// Return PCI bus address 30 | pub fn address(&self) -> u32 { 31 | 0x8000_0000 32 | | ((self.bus as u32) << 16) 33 | | ((self.slot as u32) << 11) 34 | | ((self.function as u32) << 8) 35 | } 36 | 37 | pub fn read_register(&self, register: u8) -> u32 { 38 | let addr = self.address() 39 | | ((register as u32) << 2); 40 | 41 | PORTS.lock().read(addr) 42 | } 43 | 44 | pub fn write_register(&self, register: u8, value: u32) { 45 | let addr = self.address() 46 | | ((register as u32) << 2); 47 | 48 | PORTS.lock().write(addr, value); 49 | } 50 | 51 | /// Return the Device which is at this PCI bus location 52 | /// May return None if there is no device 53 | pub fn get_device(&self) -> Option { 54 | let reg_0 = self.read_register(0); 55 | if reg_0 == 0xFFFF_FFFF { 56 | return None // No device 57 | } 58 | 59 | let vendor_id = (reg_0 & 0xFFFF) as u16; 60 | let device_id = (reg_0 >> 16) as u16; 61 | 62 | let reg_2 = self.read_register(2); 63 | 64 | let revision_id = (reg_2 & 0xFF) as u8; 65 | let prog_if = ((reg_2 >> 8) & 0xFF) as u8; 66 | let subclass = ((reg_2 >> 16) & 0xFF) as u8; 67 | let class = ((reg_2 >> 24) & 0xFF) as u8; 68 | 69 | let reg_3 = self.read_register(3); 70 | 71 | let header_type = ((reg_3 >> 16) & 0xFF) as u8; 72 | 73 | let subsystem_id = if header_type == 0 { 74 | let reg_B = self.read_register(0xB); 75 | ((reg_B >> 16) & 0xFFFF) as u16 76 | } else { 0 }; 77 | 78 | Some(Device { 79 | location: self.clone(), 80 | vendor_id, 81 | device_id, 82 | class, 83 | subclass, 84 | prog_if, 85 | revision_id, 86 | header_type, 87 | subsystem_id 88 | }) 89 | } 90 | } 91 | 92 | /// Information about a PCI device 93 | pub struct Device { 94 | pub location: PciLocation, 95 | pub vendor_id: u16, // Identifies the manufacturer of the device 96 | pub device_id: u16, // Identifies the particular device. Valid IDs are allocated by the vendor 97 | pub class: u8, // The type of function the device performs 98 | pub subclass: u8, // The specific function the device performs 99 | pub prog_if: u8, // register-level programming interface, if any 100 | pub revision_id: u8, // revision identifier. Valid IDs are allocated by the vendor 101 | pub header_type: u8, 102 | pub subsystem_id: u16 103 | } 104 | 105 | impl Device { 106 | pub fn class_str(&self) -> &'static str { 107 | match self.class { 108 | 0x0 => match self.subclass { 109 | 0 => "Non-VGA-Compatible Unclassified Device", 110 | 1 => "VGA-Compatible Unclassified Device", 111 | _ => "Unknown", 112 | }, 113 | 0x1 => match self.subclass { 114 | 0x0 => "SCSI Bus Controller", 115 | 0x1 => "IDE Controller", 116 | 0x2 => "Floppy Disk Controller", 117 | 0x3 => "IPI Bus Controller", 118 | 0x4 => "RAID Controller", 119 | 0x5 => "ATA Controller", 120 | 0x6 => "Serial ATA Controller", 121 | 0x7 => "Serial Attached SCSI Controller", 122 | 0x8 => "Non-Volatile Memory Controller", 123 | _ => "Mass Storage Controller" 124 | } 125 | 0x2 => match self.subclass { 126 | 0x0 => "Ethernet Controller", 127 | 0x1 => "Token Ring Controller", 128 | 0x2 => "FDDI Controller", 129 | 0x3 => "ATM Controller", 130 | 0x4 => "ISDN Controller", 131 | 0x5 => "WorldFip Controller", 132 | 0x6 => "PICMG 2.14 Multi Computing Controller", 133 | 0x7 => "Infiniband Controller", 134 | 0x8 => "Fabric Controller", 135 | _ => "Network Controller" 136 | } 137 | 0x3 => match self.subclass { 138 | 0x0 => "VGA Compatible Controller", 139 | 0x1 => "XGA Controller", 140 | 0x2 => "3D Controller (Not VGA-Compatible)", 141 | _ => "Display Controller" 142 | } 143 | 0x4 => match self.subclass { 144 | 0x0 => "Multimedia Video Controller", 145 | 0x1 => "Multimedia Audio Controller", 146 | 0x2 => "Computer Telephony Device", 147 | 0x3 => "Audio Device", 148 | _ => "Multimedia Controller" 149 | } 150 | 0x5 => match self.subclass { 151 | 0x0 => "RAM Controller", 152 | 0x1 => "Flash Controller", 153 | _ => "Memory Controller" 154 | } 155 | 0x6 => match self.subclass { 156 | 0x0 => "Host Bridge", 157 | 0x1 => "ISA Bridge", 158 | 0x2 => "EISA Bridge", 159 | 0x3 => "MCA Bridge", 160 | 0x4 => "PCI-to-PCI Bridge", 161 | 0x5 => "PCMCIA Bridge", 162 | 0x6 => "NuBus Bridge", 163 | 0x7 => "CardBus Bridge", 164 | 0x8 => "RACEway Bridge", 165 | 0x9 => "PCI-to-PCI Bridge", 166 | 0xA => "InfiniBand-to-PCI Host Bridge", 167 | _ => "Bridge" 168 | } 169 | _ => "Unknown" 170 | } 171 | } 172 | } 173 | 174 | impl fmt::Display for Device { 175 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 176 | write!(f, "{} [{:04X}:{:04X}] {}", 177 | self.location, self.vendor_id, self.device_id, self.class_str()) 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /ramdisk/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | use alloc::collections::btree_map::BTreeMap; 6 | use alloc::{string::String, sync::Arc, vec::Vec}; 7 | use alloc::format; 8 | use core::{str, cmp}; 9 | 10 | use spin::RwLock; 11 | 12 | use euralios_std::{println, 13 | server::{FileLike, DirLike, handle_directory}, 14 | message, 15 | syscalls::{self, STDIN}, 16 | sys::path::MAIN_SEP_STR}; 17 | 18 | /// Represents a file as a bag of bytes 19 | struct File { 20 | data: Vec 21 | } 22 | 23 | impl File { 24 | fn new() -> Self { 25 | File{data: Vec::new()} 26 | } 27 | } 28 | 29 | impl FileLike for File { 30 | fn len(&self) -> usize { 31 | self.data.len() 32 | } 33 | fn read(&self, start: usize, buffer: &mut [u8]) -> Result { 34 | let end = cmp::min(start + buffer.len(), self.data.len()); 35 | if start >= end { 36 | return Err(syscalls::SYSCALL_ERROR_NO_DATA); 37 | } 38 | let size = end - start; 39 | println!("[ramdisk] Reading {} bytes", size); 40 | buffer[..size].copy_from_slice(&self.data[start..end]); 41 | Ok(size) 42 | } 43 | fn write(&mut self, start: usize, buffer: &[u8]) -> Result { 44 | println!("[ramdisk] Writing {} bytes", buffer.len()); 45 | self.data.extend_from_slice(buffer); 46 | Ok(buffer.len()) 47 | } 48 | fn clear(&mut self) -> Result<(), syscalls::SyscallError> { 49 | self.data.clear(); 50 | Ok(()) 51 | } 52 | } 53 | 54 | /// A tree structure of directories containing File objects 55 | /// 56 | /// All subdirectories and files are wrapped in Arc> because: 57 | /// - Multiple processes may hold handles to the same directory or 58 | /// file 59 | /// - Hard links where multiple files point to the same data 60 | pub struct Directory { 61 | subdirs: BTreeMap>>, 62 | files: BTreeMap>> 63 | } 64 | 65 | impl Directory { 66 | fn new() -> Self { 67 | Self { 68 | subdirs: BTreeMap::new(), 69 | files: BTreeMap::new() 70 | } 71 | } 72 | } 73 | 74 | impl DirLike for Directory { 75 | /// Lookup and return shared reference to a directory 76 | fn get_dir(&self, name: &str) -> Result>, syscalls::SyscallError> { 77 | if self.subdirs.contains_key(name) { 78 | Ok(self.subdirs[name].clone()) 79 | } else { 80 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 81 | } 82 | } 83 | /// Lookup and return shared reference to a file 84 | fn get_file(&self, name: &str) -> Result>, syscalls::SyscallError> { 85 | if self.files.contains_key(name) { 86 | Ok(self.files[name].clone()) 87 | } else { 88 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 89 | } 90 | } 91 | 92 | fn query(&self) -> String { 93 | // Make a list of files separated with commas. 94 | // Each is a dictionary with a "name" key 95 | let file_list = { 96 | let mut s = String::new(); 97 | let mut it = self.files.keys().peekable(); 98 | while let Some(name) = it.next() { 99 | s.reserve(name.len() + 13); 100 | s.push_str("{\"name\":\""); 101 | s.push_str(name); 102 | s.push_str("\"}"); 103 | if it.peek().is_some() { 104 | s.push_str(", "); 105 | } 106 | } 107 | s 108 | }; 109 | 110 | // Make a list of directories 111 | let subdir_list = { 112 | let mut s = String::new(); 113 | let mut it = self.subdirs.keys().peekable(); 114 | while let Some(name) = it.next() { 115 | s.reserve(name.len() + 13); 116 | s.push_str("{\"name\":\""); 117 | s.push_str(name); 118 | s.push_str("\"}"); 119 | if it.peek().is_some() { 120 | s.push_str(", "); 121 | } 122 | } 123 | s 124 | }; 125 | 126 | // Combine into a String and return 127 | format!("{{ 128 | \"short\": \"Ramdisk directory\", 129 | \"messages\": [{{\"name\": \"open\", 130 | \"tag\": {open_tag}}}, 131 | {{\"name\": \"query\", 132 | \"tag\": {query_tag}}}], 133 | \"subdirs\": [{subdir_list}], 134 | \"files\": [{file_list}]}}", 135 | open_tag = message::OPEN, 136 | query_tag = message::QUERY, 137 | file_list = file_list, 138 | subdir_list = subdir_list) 139 | } 140 | 141 | /// Make a sub-directory 142 | fn make_dir(&mut self, path: &str) -> Result>, syscalls::SyscallError> { 143 | println!("[ramdisk] Making directory {}", path); 144 | if path.contains(MAIN_SEP_STR) { 145 | // Cannot contain separator 146 | return Err(syscalls::SYSCALL_ERROR_PARAM); 147 | } 148 | if self.subdirs.contains_key(path) { 149 | // Already exists 150 | return Err(syscalls::SYSCALL_ERROR_EXISTS); 151 | } 152 | let new_dir = Arc::new(RwLock::new(Directory::new())); 153 | self.subdirs.insert(String::from(path), new_dir.clone()); 154 | Ok(new_dir) 155 | } 156 | /// Create a new file, returning a shared reference 157 | fn make_file(&mut self, name: &str) -> Result>, syscalls::SyscallError> { 158 | println!("[ramdisk] Making file {}", name); 159 | let new_file = Arc::new(RwLock::new(File::new())); 160 | self.files.insert(String::from(name), new_file.clone()); 161 | Ok(new_file) 162 | } 163 | /// Delete a file 164 | fn remove_file(&mut self, path: &str) -> Result>, syscalls::SyscallError> { 165 | let path = path.trim_start_matches('/'); 166 | println!("[ramdisk] Removing file {}", path); 167 | 168 | if let Some(file) = self.files.remove(path) { 169 | Ok(file) 170 | } else { 171 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 172 | } 173 | } 174 | } 175 | 176 | #[no_mangle] 177 | fn main() { 178 | println!("[ramdisk] Starting ramdisk"); 179 | 180 | let fs = Directory::new(); 181 | 182 | handle_directory( 183 | Arc::new(RwLock::new(fs)), 184 | STDIN.clone(), 185 | true, 186 | |message| { 187 | println!("[ramdisk] Received unexpected message {:?}", message); 188 | }); 189 | } 190 | -------------------------------------------------------------------------------- /doc/journal/25-multiple-users.org: -------------------------------------------------------------------------------- 1 | * Multiple users 2 | 3 | Now that we have [[./25-directories][directories]], we can combine them with the ability to 4 | customise the Virtual File System (VFS) of programs to create a 5 | multi-user system. Programs can only communicate with drivers etc. if 6 | they have a communication handle, so users can be given only limited 7 | access to the system drivers etc. 8 | 9 | ** Specifying a subprocess VFS 10 | 11 | New processes are created with the =exec= syscall. The =param= 12 | argument (pointer in the =rdx= register) has not been used yet but was 13 | a placeholder for some way to specify the virtual file system of the 14 | new process. 15 | 16 | The default is that the new process shares a VFS with its parent. Any 17 | new mount points added by one process will be seen by the 18 | other. Another option is to start with a copy of the parent VFS, 19 | removing or adding mount points to customise. Finally, we could start 20 | with an empty VFS and add paths to it. 21 | 22 | To be consistent with other messages we could try using JSON format to specify 23 | the VFS paths. That would mean adding a quite complex parser to the kernel, but 24 | what we need to do is quite simple. Instead the kernel reads a custom 25 | string format: 26 | 27 | - First byte specifies the VFS to start with: (S)hared with parent, 28 | (C)opy of parent's VFS, (N)ew VFS. 29 | 30 | - Following clauses modify the VFS: 31 | - Remove path: A '-' byte followed by a path terminated with a ':' 32 | e.g. =-/path:= This unmounts a path from the VFS so that the 33 | new process can't see it. 34 | - Mount communication handle: An 'm' followed by the handle number 35 | (in ASCII), a non-numerical delimiter, and a path terminating in 36 | ':' e.g. =m/path/to/mount:= The communication handle is removed 37 | from calling process. [Note: If the =exec= command fails after 38 | this is processed, the handle may be lost/closed]. 39 | 40 | Other kinds of modifications could be added but this is sufficient to 41 | enable the caller to customise the VFS of the new process. 42 | 43 | ** Standard library support 44 | 45 | In =euralios_std::syscalls= we can define a builder to create 46 | the VFS parameter string 47 | #+begin_src rust 48 | pub struct VFS { 49 | s: String 50 | } 51 | #+end_src 52 | That string is going to be the parameter string sent to the 53 | =sys_exec= syscall. We then create three constructors: 54 | =shared()=, =copy()= and =new()=. For example 55 | #+begin_src rust 56 | impl VFS { 57 | /// Copy current VFS for new process 58 | pub fn copy() -> Self { 59 | VFS{s: String::from("C")} 60 | } 61 | ... 62 | } 63 | #+end_src 64 | Modifications to this VFS then append strings 65 | #+begin_src rust 66 | impl VFS { 67 | /// Add a communication handle as a path in the VFS 68 | pub fn mount(mut self, mut handle: CommHandle, path: &str) -> Self { 69 | // String containing handle number 70 | let handle_s = unsafe{handle.take().to_string()}; 71 | 72 | self.s.push('m'); // Code for "mount" 73 | self.s.push_str(&handle_s); 74 | self.s.push('|'); // Terminates handle 75 | self.s.push_str(path); 76 | self.s.push(':'); // Terminates path 77 | self 78 | } 79 | 80 | /// Remove a path from the VFS 81 | pub fn remove(mut self, path: &str) -> Self { 82 | self.s.push('-'); 83 | self.s.push_str(path); 84 | self.s.push(':'); // Terminates path 85 | self 86 | } 87 | } 88 | #+end_src 89 | so that in =init= we can start a shell process with 90 | #+begin_src rust 91 | // Start the process 92 | syscalls::exec( 93 | include_bytes!("../../user/shell"), 94 | 0, 95 | input2, 96 | console.output.clone(), 97 | VFS::copy().remove("/pci").remove("/dev/nic")).expect("[init] Couldn't start user program"); 98 | #+end_src 99 | which sends a parameter string "C-/pci:-/dev/nic:" to the =sys_exec= 100 | system call. Running the =mount= command in the shell now just lists 101 | =["/ramdisk","/tcp",]= because both =/pci= and =/dev/nic= have been removed. 102 | 103 | ** Running a shell inside a shell 104 | 105 | In =init= we can make a directory to put all the system executables 106 | into, and add the =shell=: 107 | #+begin_src rust 108 | fs::create_dir("/ramdisk/bin"); 109 | if let Ok(mut file) = File::create("/ramdisk/bin/shell") { 110 | file.write(include_bytes!("../../user/shell")); 111 | } 112 | ... 113 | #+end_src 114 | 115 | #+CAPTION: Running a shell inside a shell 116 | #+NAME: fig-shell-in-shell 117 | [[./img/26-01-shell-in-shell.png]] 118 | 119 | ** Login process 120 | 121 | Using this VFS customisation method we can create processes that are 122 | in their own sandbox, and control the resources they and any of their 123 | child processes can access. To isolate users from each other we can 124 | now run shells with a different VFS, depending on which user logs in. 125 | 126 | We'll change =init= so that rather than launching =shell= on each 127 | VGA console, instead it launches a new process =login=. The login process 128 | will enter a loop waiting for a user to log in: 129 | 130 | #+begin_src rust 131 | let stdin = io::stdin(); 132 | let mut username = String::new(); 133 | 134 | loop { 135 | print!("login: "); 136 | 137 | username.clear(); 138 | stdin.read_line(&mut username); 139 | 140 | ... 141 | } 142 | #+end_src 143 | 144 | Eventually we should have a password file, that most users can't 145 | directly access, but for now we can just hard-wire some user names 146 | and not bother with passwords. The VFS of the shell that is launched 147 | depends on whether the login is for 'root' or 'user': 148 | #+begin_src rust 149 | let vfs = match username.trim() { 150 | "root" => VFS::shared(), // Root sees everything 151 | "user" => { 152 | // Open bin directory read-only 153 | let bin = OpenOptions::new().open("/ramdisk/bin").unwrap(); 154 | // User's home directory read-write 155 | let home = OpenOptions::new().write(true).open("/ramdisk/user").unwrap(); 156 | // TCP stack read-write 157 | let tcp = OpenOptions::new().write(true).open("/tcp").unwrap(); 158 | VFS::new() 159 | .mount(bin.to_CommHandle(), "/bin") 160 | .mount(home.to_CommHandle(), "/ramdisk") 161 | .mount(tcp.to_CommHandle(), "/tcp") 162 | }, 163 | _ => { 164 | println!("Unknown login. Try 'root' or 'user'..."); 165 | continue; 166 | } 167 | }; 168 | #+end_src 169 | 170 | Here if "root" logs in then the shell shares the VFS with the =login= 171 | process; If "user" logs in then they get a new VFS with only some 172 | paths mounted: 173 | - The =/ramdisk/bin= directory is opened read-only, the file converted to a communication 174 | handle, and mounted in the user VFS as =/bin=. The user can therefore read (and execute) 175 | these files, but can't modify or delete them. 176 | - A directory in the ramdisk, =/ramdisk/user= is opened read-write, and mounted as =/ramdisk= 177 | in the user's VFS. They can read and write to that directory, but can't access any other part 178 | of the ramdisk. 179 | - The =/tcp= directory is mounted in the user VFS as =/tcp=. This allows the user to open 180 | connections and run e.g. the =gopher= client. They can't however access the =/dev/nic= 181 | driver or =/pci= process directly. 182 | -------------------------------------------------------------------------------- /shell/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | extern crate alloc; 5 | use alloc::string::String; 6 | use alloc::vec::Vec; 7 | use core::str; 8 | 9 | use euralios_std::{path::{Path, PathBuf}, 10 | fs::{self, File}, 11 | io, 12 | message, 13 | print, println, 14 | syscalls::{self, SyscallError, VFS}}; 15 | 16 | fn exec_path(current_directory: &Path, path: &Path, args: Vec<&str>) -> Result<(), SyscallError> { 17 | // Read binary from file 18 | let bin = { 19 | let mut bin: Vec = Vec::new(); 20 | let mut file = File::open(path)?; 21 | file.read_to_end(&mut bin)?; 22 | bin 23 | }; 24 | 25 | // Create a communication handle for the input 26 | let (exe_input, exe_input2) = syscalls::new_rendezvous()?; 27 | 28 | // Make an environment with the working directory 29 | let mut env_string = String::from("PWD="); 30 | env_string.push_str(current_directory 31 | .as_os_str().to_str().unwrap()); 32 | 33 | syscalls::exec( 34 | &bin, 35 | 0, // Permission flags 36 | exe_input2, 37 | syscalls::STDOUT.clone(), 38 | VFS::shared(), 39 | args, 40 | &env_string 41 | )?; 42 | 43 | loop { 44 | // Wait for keyboard input 45 | match syscalls::receive(&syscalls::STDIN) { 46 | Ok(syscalls::Message::Short( 47 | message::CHAR, ch, _)) => { 48 | // Received a character 49 | if let Err((err, _)) = syscalls::send(&exe_input, 50 | syscalls::Message::Short( 51 | message::CHAR, ch, 0)) { 52 | println!("Received error: {}", err); 53 | return Ok(()); 54 | } 55 | }, 56 | _ => { 57 | // Ignore 58 | } 59 | } 60 | } 61 | } 62 | 63 | fn help() { 64 | println!( 65 | "EuraliOS shell help 66 | 67 | * Console windows 68 | F1 System processes 69 | F2..F5 User consoles 70 | 71 | * Built-in commands: 72 | ls [] List directory 73 | cd Change directory 74 | pwd Print working directory 75 | rm Delete a file 76 | mount List mounted filesystems 77 | umount Un-mount a filesystem 78 | mkdir Make a directory 79 | exit Exit shell 80 | " 81 | ); 82 | } 83 | 84 | /// List a directory 85 | fn ls(current_directory: &Path, args: &[&str]) { 86 | if args.len() > 1 { 87 | println!("Usage: ls [path]"); 88 | return; 89 | } 90 | 91 | let option_rd = if args.len() == 0 { 92 | fs::read_dir(current_directory) 93 | } else { 94 | let path = fs::canonicalize(PathBuf::from(current_directory).join(args[0])).unwrap(); 95 | fs::read_dir(path) 96 | }; 97 | 98 | if let Ok(rd) = option_rd { 99 | for entry in rd { 100 | if let Ok(obj) = entry { 101 | if obj.metadata().unwrap().is_dir() { 102 | println!("\x1b[34m{}\x1b[m", obj.file_name()); 103 | } else { 104 | println!("{}", obj.file_name()); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | 111 | /// Unmount a path 112 | fn umount(args: &[&str]) { 113 | if args.len() != 1 { 114 | println!("Usage: umount "); 115 | return; 116 | } 117 | let path = args.first().unwrap(); // We know it has one element 118 | if let Err(err) = syscalls::umount(path) { 119 | println!("umount error: {}", err); 120 | } 121 | } 122 | 123 | /// Delete a file 124 | fn rm(current_directory: &Path, args: &[&str]) { 125 | if args.len() != 1 { 126 | println!("Usage: rm "); 127 | return; 128 | } 129 | let file = args.first().unwrap(); 130 | 131 | let path = fs::canonicalize(PathBuf::from(current_directory).join(file)).unwrap(); 132 | if let Err(err) = fs::remove_file(path) { 133 | // Failed 134 | println!("rm: cannot remove {}: {}", file, err); 135 | } 136 | } 137 | 138 | /// Make a directory 139 | fn mkdir(current_directory: &Path, args: &[&str]) { 140 | if args.len() != 1 { 141 | println!("Usage: mkdir "); 142 | return; 143 | } 144 | let arg = args.first().unwrap(); 145 | let path = fs::canonicalize(PathBuf::from(current_directory).join(arg)).unwrap(); 146 | if let Err(err) = fs::create_dir(path) { 147 | // Failed 148 | println!("mkdir: cannot create {}: {:?}", arg, err); 149 | } 150 | } 151 | 152 | #[no_mangle] 153 | fn main() { 154 | println!("Type help [Enter] to see the shell help page."); 155 | 156 | let stdin = io::stdin(); 157 | let mut line_buffer = String::new(); 158 | 159 | // Current Working Directory 160 | let mut current_directory = PathBuf::from("/ramdisk"); 161 | 162 | loop { 163 | // prompt 164 | print!("$ "); 165 | 166 | // Read a line of input 167 | stdin.read_line(&mut line_buffer); 168 | 169 | let args: Vec<_> = line_buffer.split_whitespace().collect(); 170 | if let Some(command) = args.first() { 171 | match *command { 172 | // Built-in shell commands 173 | // 174 | // Help 175 | "help" | "?" => help(), 176 | // List directory 177 | "ls" => ls(¤t_directory, &args[1..]), 178 | // Print working directory 179 | "pwd" => println!("{:?}", current_directory), 180 | // Change directory 181 | "cd" => { 182 | if args.len() != 2 { 183 | println!("Usage: cd "); 184 | continue; 185 | } 186 | current_directory.push(args[1]); 187 | current_directory = fs::canonicalize(current_directory).unwrap(); 188 | }, 189 | "mount" => { 190 | match syscalls::list_mounts() { 191 | Ok((handle, len)) => { 192 | let u8_slice = handle.as_slice::(len as usize); 193 | if let Ok(s) = str::from_utf8(u8_slice) { 194 | println!("{}", s); 195 | } else { 196 | println!("mount: syscall utf8 error"); 197 | } 198 | } 199 | Err(err) => { 200 | println!("mount: error {}", err); 201 | } 202 | } 203 | }, 204 | "umount" => umount(&args[1..]), 205 | "rm" => rm(¤t_directory, &args[1..]), 206 | "mkdir" => mkdir(¤t_directory, &args[1..]), 207 | "exit" => return, 208 | cmd => { 209 | let path = fs::canonicalize(current_directory.join(cmd)).unwrap(); 210 | 211 | if let Err(err) = exec_path(¤t_directory, &path, args) { 212 | println!("Couldn't open '{:?}': {}", path, err); 213 | } 214 | } 215 | } 216 | } 217 | 218 | line_buffer.clear(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /doc/journal/10-stdlib.org: -------------------------------------------------------------------------------- 1 | * Standard library 2 | 3 | The user code is now getting large enough that it would be useful 4 | to split it into a library. That will also make creating new 5 | user programs easier. 6 | 7 | This is not going to be the full Rust standard library (far from it!) 8 | but we'll try to have the same API as =std= where possible. 9 | 10 | ** Adding a library 11 | 12 | In the workspace root directory edit the =Cargo.toml= file: 13 | #+begin_src toml 14 | [workspace] 15 | members = [ 16 | "kernel", 17 | "hello", 18 | "euralios_std" # New 19 | ] 20 | #+end_src 21 | then run =cargo= to create the =euralios_std= crate: 22 | #+begin_src sh 23 | cargo new euralios_std --lib 24 | #+end_src 25 | 26 | Our user program =hello= is going to depend on this library, 27 | so in =hello/Cargo.toml=: 28 | #+begin_src toml 29 | [dependencies] 30 | linked_list_allocator = "0.9.0" 31 | euralios_std = { path = "../euralios_std" } # New 32 | #+end_src 33 | 34 | ** Moving syscalls into library 35 | 36 | In the library we can now start adding some functions. 37 | In =euralios_std/src/lib.rs=: 38 | #+begin_src rust 39 | #![no_std] 40 | #![no_main] 41 | 42 | pub mod syscalls; 43 | #+end_src 44 | 45 | and in a new file =euralios_std/src/syscalls.rs= we can start by 46 | moving the =thread_exit()=, =receive()= and =send()= functions out of 47 | =hello/src/main.rs=: 48 | #+begin_src rust 49 | use core::arch::asm; 50 | 51 | pub fn thread_exit() -> ! { 52 | unsafe { 53 | asm!("mov rax, 1", // exit_current_thread 54 | "syscall", 55 | out("rcx") _, // syscall clobber 56 | out("r11") _); // syscall clobber 57 | } 58 | loop{} 59 | } 60 | #+end_src 61 | Note that the =rcx= and =r11= registers are now marked as clobbered, 62 | because the =syscall= instruction modifies these registers. The code 63 | worked before because fortunately the rust compiler/LLVM backend 64 | didn't use those registers. Without these statements I ran into 65 | strange page faults when more data than could fit in one register was 66 | returned from a function containing a =syscall= instruction, caused by 67 | overwriting these registers without telling the compiler. 68 | 69 | #+begin_src rust 70 | pub fn receive(handle: u64) -> Result { 71 | let mut err: u64; 72 | let value: u64; 73 | unsafe { 74 | asm!("mov rax, 3", // sys_receive 75 | "syscall", 76 | in(rdi) handle, 77 | lateout("rax") err, 78 | lateout("rdi") value, 79 | out("rcx") _, 80 | out("r11") _); 81 | } 82 | if err == 0 { 83 | return Ok(value); 84 | } 85 | Err(err) 86 | } 87 | 88 | pub fn send(handle: u64, value: u64) -> Result<(), u64> { 89 | let err: u64; 90 | unsafe { 91 | asm!("mov rax, 4", // sys_send 92 | "syscall", 93 | in("rdi") handle, 94 | in("rsi") value, 95 | lateout("rax") err, 96 | out("rcx") _, 97 | out("r11") _); 98 | } 99 | if err == 0 { 100 | return Ok(()); 101 | } 102 | Err(err) 103 | } 104 | #+end_src 105 | 106 | so the =hello= program =_start()= function can become: 107 | 108 | 109 | #+begin_src rust 110 | use euralios_std::syscalls; // New 111 | 112 | #[no_mangle] 113 | pub unsafe extern "sysv64" fn _start() -> ! { 114 | // Information passed from the operating system 115 | let heap_start: usize; 116 | let heap_size: usize; 117 | asm!("", 118 | lateout("rax") heap_start, 119 | lateout("rcx") heap_size, 120 | options(pure, nomem, nostack) 121 | ); 122 | println!("Heap start {:#016X}, size: {} bytes ({} Mb)", heap_start, heap_size, heap_size / (1024 * 1024)); 123 | 124 | ALLOCATOR.lock().init(heap_start, heap_size); 125 | 126 | loop{ 127 | let value = syscalls::receive(0).unwrap(); 128 | let ch = char::from_u32(value as u32).unwrap(); 129 | println!("Received: {} => {}", value, ch); 130 | if ch == 'x' { 131 | println!("Exiting"); 132 | break; 133 | } 134 | syscalls::send(1, value).unwrap(); 135 | } 136 | 137 | syscalls::thread_exit(); 138 | } 139 | #+end_src 140 | 141 | ** Debug output 142 | 143 | #+begin_src rust 144 | use core::arch::asm; 145 | use core::format_args; 146 | use core::fmt; 147 | 148 | struct Writer {} 149 | 150 | impl fmt::Write for Writer { 151 | fn write_str(&mut self, s: &str) -> fmt::Result { 152 | unsafe { 153 | asm!("mov rax, 2", // syscall function 154 | "syscall", 155 | in("rdi") s.as_ptr(), // First argument 156 | in("rsi") s.len(), // Second argument 157 | out("rcx") _, 158 | out("r11") _); 159 | } 160 | Ok(()) 161 | } 162 | } 163 | 164 | pub fn _print(args: fmt::Arguments) { 165 | use core::fmt::Write; 166 | Writer{}.write_fmt(args).unwrap(); 167 | } 168 | 169 | #[macro_export] 170 | macro_rules! debug_print { 171 | ($($arg:tt)*) => ($crate::debug::_print(format_args!($($arg)*))); 172 | } 173 | 174 | #[macro_export] 175 | macro_rules! debug_println { 176 | () => ($crate::debug_print!("\n")); 177 | ($($arg:tt)*) => ($crate::debug_print!("{}\n", format_args!($($arg)*))); 178 | } 179 | #+end_src 180 | 181 | ** Memory 182 | 183 | Remove =linked_list_allocator= dependency from =hello/Cargo.toml=, 184 | and add to =euralios_std/Cargo.toml= 185 | 186 | In =memory.rs= 187 | #+begin_src rust 188 | extern crate alloc; 189 | use linked_list_allocator::LockedHeap; 190 | 191 | use crate::debug_println; 192 | 193 | #[global_allocator] 194 | static ALLOCATOR: LockedHeap = LockedHeap::empty(); 195 | 196 | pub fn init(heap_start: usize, heap_size: usize) { 197 | debug_println!("Heap start {:#016X}, size: {} bytes ({} Mb)", heap_start, heap_size, heap_size / (1024 * 1024)); 198 | unsafe {ALLOCATOR.lock().init(heap_start, heap_size);} 199 | } 200 | 201 | // Allocator error handler 202 | #[alloc_error_handler] 203 | fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { 204 | panic!("allocation error: {:?}", layout) 205 | } 206 | #+end_src 207 | 208 | ** User entry point 209 | 210 | In =lib.rs=: 211 | #+begin_src rust 212 | // User program entry point 213 | extern { 214 | fn main() -> (); 215 | } 216 | 217 | #[no_mangle] 218 | pub unsafe extern "sysv64" fn _start() -> ! { 219 | // Information passed from the operating system 220 | let heap_start: usize; 221 | let heap_size: usize; 222 | asm!("", 223 | lateout("rax") heap_start, 224 | lateout("rcx") heap_size, 225 | options(pure, nomem, nostack) 226 | ); 227 | memory::init(heap_start, heap_size); 228 | 229 | main(); // New 230 | 231 | syscalls::thread_exit(); 232 | } 233 | #+end_src 234 | 235 | ** Final user program 236 | 237 | The =hello= program now consists of: 238 | #+begin_src rust 239 | #![no_std] 240 | #![no_main] 241 | 242 | use euralios_std::{debug_println, syscalls}; 243 | 244 | #[no_mangle] 245 | fn main() { 246 | loop{ 247 | let value = syscalls::receive(0).unwrap(); 248 | let ch = char::from_u32(value as u32).unwrap(); 249 | debug_println!("Received: {} => {}", value, ch); 250 | if ch == 'x' { 251 | debug_println!("Exiting"); 252 | break; 253 | } 254 | syscalls::send(1, value).unwrap(); 255 | } 256 | } 257 | #+end_src 258 | -------------------------------------------------------------------------------- /doc/journal/08-faster-ipc.org: -------------------------------------------------------------------------------- 1 | * Faster IPC 2 | 3 | In the [[file:07-ipc.org][last section]] a method to communicate between threads 4 | was implemented (in =rendezvous.rs=), and used to add a 5 | =read= syscall so that user programs can get keyboard input. 6 | 7 | The way this works is: When a key is pressed whichever thread is 8 | running (thread A) is interrupted, and the keyboard handler calls 9 | =send= on the rendezvous. In most cases there is already a thread 10 | waiting (thread B), so =send= returns the =Box= corresponding 11 | to thread B, which the keyboard handler passes to 12 | =process:schedule_thread=. The keyboard handler then returns to 13 | thread A, and a little time later the timer interrupt stops thread A 14 | and switches context to thread B. 15 | 16 | What's happened to thread B is curious: It called the kernel with a 17 | =syscall=, but returns from that call with an =iret= from the timer 18 | interrupt handler. This shows that our sysret and interrupt handlers 19 | treat contexts consistently, and that we can use =iret= to restart any 20 | thread. Note that the other way around doesn't work: We can't 21 | interrupt a thread and then return to it with a [[https://www.felixcloutier.com/x86/sysret][sysret]] instruction 22 | because =sysret= uses the =rcx= and =r11= registers. The interrupted 23 | thread would, as far as it is concerned, suddenly have two of its 24 | registers unexpectedly modified. 25 | 26 | ** Speeding up sys_receive 27 | 28 | The easier part to speed up is removing the =hlt= instruction in the 29 | =sys_receive= function (=syscalls.rs=). If the calling thread blocks 30 | (waiting for a message) then all we need to do is find the next 31 | thread, and then jump into the middle of the =interrupt_wrap!= macro 32 | (=interrupts.rs=), as if we had returned from the timer interrupt. 33 | 34 | We can do that by just copying the assembly starting =pop r15= and 35 | ending =iret=, in a new function =launch_thread()= in =interrupts.rs=: 36 | #+begin_src rust 37 | pub fn launch_thread(context_addr: usize) -> ! { 38 | unsafe { 39 | asm!("mov rsp, rdi", // Set the stack to the Context address 40 | 41 | "pop r15", 42 | "pop r14", 43 | "pop r13", 44 | 45 | "pop r12", 46 | "pop r11", 47 | "pop r10", 48 | "pop r9", 49 | 50 | "pop r8", 51 | "pop rbp", 52 | "pop rsi", 53 | "pop rdi", 54 | 55 | "pop rdx", 56 | "pop rcx", 57 | "pop rbx", 58 | "pop rax", 59 | 60 | "sti", // Enable interrupts 61 | "iretq",// Interrupt return 62 | in("rdi") context_addr, 63 | options(noreturn)); 64 | } 65 | } 66 | #+end_src 67 | This takes the address of the Context as input, sets the stack pointer 68 | (=rsi=) to that address, pops the registers and then returns via 69 | =iret=. That =iret= restores the instruction pointer, RFLAGS, CS and 70 | SS registers because the Context contains an exception frame even if 71 | it was created in a =syscall=. 72 | 73 | Now in =syscalls.rs= the =sys_receive()= function can be 74 | modified, to first get the next thread from the scheduler, 75 | and then call =launch_thread= to run it: 76 | #+begin_src rust 77 | use crate::interrupts; // New 78 | fn sys_receive(context_ptr: *mut Context, handle: u64) { 79 | ... 80 | if !returning { 81 | let new_context_addr = 82 | process::schedule_next(context_ptr as usize); 83 | interrupts::launch_thread(new_context_addr); 84 | } 85 | } 86 | #+end_src 87 | That's it! The =schedule_next()= function already handles changing page tables, 88 | the kernel stack in the TSS, and the =CURRENT_THREAD=. 89 | 90 | ... or almost it. One thing to watch out for is that the 91 | =launch_thread= process never returns. Even though it's marked as 92 | no-return (=!= return type), the compiler doesn't insert code to drop 93 | variables in the current scope. That means that the Rendezvous handle 94 | =rdv= which is an =Arc>= is not dropped, the Arc 95 | [[https://doc.rust-lang.org/std/sync/struct.Arc.html#method.strong_count][strong count]] is not decreased but increases every time this 96 | =launch_thread= path through =sys_receive= is followed. This will lead 97 | to a memory leak as the Rendezvous will not be free'd when all 98 | processes holding it exit. To fix this we can do: 99 | #+begin_src rust 100 | use core::mem::drop; 101 | 102 | ... 103 | if !returning { 104 | drop(rdv); // new 105 | let new_context_addr = 106 | process::schedule_next(context_ptr as usize); 107 | interrupts::launch_thread(new_context_addr); 108 | } 109 | #+end_src 110 | The [[https://doc.rust-lang.org/core/mem/fn.drop.html][implementation of drop]] is quite neat: It's just 111 | #+begin_src rust 112 | pub fn drop(_x: T) { } 113 | #+end_src 114 | so the ownership of =rdv= is transferred to this function 115 | and then dropped. 116 | 117 | ** Speeding up keyboard_interrupt_handler 118 | 119 | The keyboard interrupt handler uses the =x86-interrupt= calling 120 | convention, which saves us some work but doesn't capture a context in 121 | the same way as our timer interrupt or syscall handlers. To switch to 122 | the thread which receives the keyboard message, rather than returning 123 | to the interrupted thread, we need to get that context. 124 | 125 | Fortunately we already have a macro that will do this for us: 126 | =interrupt_wrap!=. In =interrupts.rs= replace: 127 | #+begin_src rust 128 | extern "x86-interrupt" fn keyboard_interrupt_handler( 129 | _stack_frame: InterruptStackFrame) 130 | #+end_src 131 | with 132 | #+begin_src rust 133 | interrupt_wrap!(keyboard_handler_inner => keyboard_interrupt_handler); 134 | 135 | extern "C" fn keyboard_handler_inner(context_addr: usize) 136 | -> usize { 137 | ... 138 | 0 // New 139 | } 140 | #+end_src 141 | and everything should still work as before. The handler returns 0 142 | so the stack isn't modified and it returns to the original thread. 143 | 144 | Then we can change the end of this function to decide whether to 145 | return to the interrupted thread, or schedule another: 146 | #+begin_src rust 147 | ... 148 | let next_context = if returning {context_addr} else { 149 | // Schedule a different thread to run 150 | process::schedule_next(context_addr) 151 | }; 152 | 153 | unsafe { 154 | PICS.lock() 155 | .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); 156 | } 157 | next_context 158 | } 159 | #+end_src 160 | 161 | This should work if you type at a reasonable speed, but if you mash 162 | the keyboard you'll find a page fault with error code USER_MODE | 163 | INSTRUCTION_FETCH. By adding print statements you can see that this is 164 | happening because of this sequence of events: 165 | 166 | - Keyboard interrupts thread 1, it's context is written to the 167 | keyboard interrupt handler stack (GDT index 0). Control is passed to 168 | thread 2 which was waiting. 169 | - Thread 2 is interrupted before it can call sys_receive again. It's 170 | context is written to the keyboard handler stack, overwriting thread 171 | 1's context. 172 | - Soon thereafter thread 1 is run again. Unfortunately its context 173 | still points to the keyboard interrupt stack, which has been 174 | overwritten by thread 2's context, so now has the wrong instruction 175 | pointer. 176 | 177 | The fix is quite simple: In =gdt.rs= change =KEYBOARD_INTERRUPT_INDEX= 178 | from 0 to 1, so it's the same as the timer interrupt index and is 179 | unique to each thread. We need to remember to use the stack at GDT 180 | index 1 for any interrupt where we might switch contexts. The page 181 | fault handler is ok (for now) because it either returns to the same 182 | thread (e.g on-demand paging), or the thread will be stopped and not 183 | restarted. 184 | 185 | We now have a user space program that can quite efficiently receive 186 | input from the keyboard via messaging. In the [[file:09-message-sending.org][next section]] we'll enable 187 | user programs to send messages to write to the screen. 188 | 189 | -------------------------------------------------------------------------------- /pci/src/main.rs: -------------------------------------------------------------------------------- 1 | //! EuraliOS PCI bus driver 2 | //! 3 | //! Code initially based on 4 | //! - MOROS : 5 | //! - Theseus : 6 | //! 7 | //! Reference: 8 | //! - OSdev: 9 | 10 | #![no_std] 11 | #![no_main] 12 | 13 | use core::str; 14 | 15 | use euralios_std::{println, syscalls, message::pci, 16 | message::{self, Message}, 17 | server::{FileLike, DirLike, handle_directory}, 18 | syscalls::STDIN}; 19 | 20 | extern crate alloc; 21 | use alloc::collections::btree_map::BTreeMap; 22 | use alloc::{string::String, sync::Arc}; 23 | use alloc::format; 24 | use spin::RwLock; 25 | 26 | mod ports; 27 | mod device; 28 | use device::{PciLocation, Device}; 29 | 30 | impl DirLike for Device { 31 | fn get_dir(&self, _name: &str) -> Result>, syscalls::SyscallError> { 32 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 33 | } 34 | fn get_file(&self, _name: &str) -> Result>, syscalls::SyscallError> { 35 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 36 | } 37 | fn query(&self) -> String { 38 | format!("{{ 39 | \"name\": \"{vendor_id:04X}_{device_id:04X}\", 40 | \"description\": \"{self}\", 41 | \"address\": \"0x{address:0X}\", 42 | \"vendor_id\": \"0x{vendor_id:04X}\", 43 | \"device_id\": \"0x{device_id:04X}\", 44 | \"class\": {class}, 45 | \"subclass\": {subclass}, 46 | \"subsystem_id\": {subsystem_id}, 47 | \"subdirs\": [], 48 | \"files\": []}}", 49 | address = self.location.address() as u64, 50 | vendor_id = self.vendor_id, 51 | device_id = self.device_id, 52 | class = self.class, 53 | subclass = self.subclass, 54 | subsystem_id = self.subsystem_id) 55 | } 56 | } 57 | 58 | struct DeviceCollection { 59 | devices: BTreeMap>> 60 | } 61 | 62 | impl DeviceCollection { 63 | fn new() -> Self { 64 | Self{devices: BTreeMap::new()} 65 | } 66 | 67 | fn insert(&mut self, device: Device) { 68 | self.devices.insert(format!("{:04X}_{:04X}", 69 | device.vendor_id, device.device_id), 70 | Arc::new(RwLock::new(device))); 71 | } 72 | 73 | fn find(&self, vendor_id: u16, device_id:u16) -> Option { 74 | println!("[pci] Finding device [{:04X}:{:04X}]", 75 | vendor_id, device_id); 76 | 77 | if let Some((_key, device)) = self.devices.iter().find( 78 | |&(_key, d)| { 79 | let d = d.read(); 80 | d.vendor_id == vendor_id && 81 | d.device_id == device_id}) { 82 | Some(device.read().location) 83 | } else { 84 | None 85 | } 86 | } 87 | } 88 | 89 | impl DirLike for DeviceCollection { 90 | /// Each subdirectory is a PCI Device 91 | fn get_dir(&self, name: &str) -> Result>, syscalls::SyscallError> { 92 | match self.devices.get(name) { 93 | Some(device) => Ok(device.clone()), 94 | None => Err(syscalls::SYSCALL_ERROR_NOTFOUND) 95 | } 96 | } 97 | /// No files; always returns not found error 98 | fn get_file(&self, _name: &str) -> Result>, syscalls::SyscallError> { 99 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 100 | } 101 | fn query(&self) -> String { 102 | // Make a list of devices 103 | let device_list = { 104 | let mut s = String::new(); 105 | let mut it = self.devices.iter().peekable(); 106 | while let Some((_name, device)) = it.next() { 107 | s.push_str(&device.read().query()); 108 | 109 | if it.peek().is_some() { 110 | s.push_str(", "); 111 | } 112 | } 113 | s 114 | }; 115 | 116 | format!("{{ 117 | \"short\": \"PCI\", 118 | \"description\": \"PCI bus devices\", 119 | \"messages\": [{{\"name\": \"find_device\", 120 | \"tag\": {find_device_tag}}}, 121 | {{\"name\": \"read_bar\", 122 | \"tag\": {read_bar_tag}}}, 123 | {{\"name\": \"query\", 124 | \"tag\": {query_tag}}}], 125 | \"subdirs\": [{device_list}], 126 | \"files\": []}}", 127 | find_device_tag = pci::FIND_DEVICE, 128 | read_bar_tag = pci::READ_BAR, 129 | query_tag = message::QUERY, 130 | device_list = device_list) 131 | } 132 | } 133 | 134 | #[no_mangle] 135 | fn main() { 136 | 137 | let mut devices = DeviceCollection::new(); 138 | 139 | // Brute force check of all PCI slots 140 | for bus in 0..256 { 141 | for slot in 0..32 { 142 | if let Some(device) = ( 143 | PciLocation{bus, 144 | slot, 145 | function:0}).get_device() { 146 | println!("[pci] Device {}", device); 147 | devices.insert(device); 148 | } 149 | } 150 | } 151 | 152 | let devices = Arc::new(RwLock::new(devices)); 153 | 154 | handle_directory( 155 | devices.clone(), 156 | STDIN.clone(), 157 | true, // Read-write 158 | |msg| { 159 | // Messages not handled 160 | match msg { 161 | // Find a device with given vendor and device ID 162 | // and return a message to the same Rendezvous 163 | Message::Short( 164 | pci::FIND_DEVICE, vendor, device) => { 165 | 166 | if let Some(location) = devices.read().find((vendor & 0xFFFF) as u16, 167 | (device & 0xFFFF) as u16) { 168 | syscalls::send(&STDIN, 169 | syscalls::Message::Short( 170 | pci::ADDRESS, 171 | location.address() as u64, 172 | 0)); 173 | } else { 174 | // Not found 175 | syscalls::send(&STDIN, 176 | syscalls::Message::Short( 177 | pci::NOTFOUND, 178 | 0xFFFF_FFFF_FFFF_FFFF, 0)); 179 | } 180 | } 181 | 182 | // Read Base Address Register 183 | Message::Short( 184 | pci::READ_BAR, address, bar_id) => { 185 | 186 | if address > 0xFFFF_FFFF || bar_id > 5 { 187 | // Out of range 188 | syscalls::send(&STDIN, 189 | syscalls::Message::Short( 190 | pci::NOTFOUND, 191 | 0xFFFF_FFFF_FFFF_FFFF, 0)); 192 | return; 193 | } 194 | 195 | let bar_value = 196 | PciLocation::from_address(address as u32) 197 | .read_register(4 + bar_id as u8); 198 | 199 | syscalls::send(&STDIN, 200 | syscalls::Message::Short( 201 | pci::BAR, 202 | bar_value as u64, bar_id)); 203 | } 204 | // Enable bus mastering, allowing a device to use DMA 205 | // https://github.com/vinc/moros/blob/trunk/src/sys/pci.rs#L74 206 | Message::Short( 207 | pci::ENABLE_BUS_MASTERING, address, _) => { 208 | let location = PciLocation::from_address(address as u32); 209 | 210 | // Read the command register (1), 211 | location.write_register(1, 212 | location.read_register(1) | (1 << 2)); 213 | } 214 | 215 | message => { 216 | println!("[pci] Received unexpected message {:?}", message); 217 | } 218 | } 219 | }); 220 | } 221 | -------------------------------------------------------------------------------- /tcp/src/dns.rs: -------------------------------------------------------------------------------- 1 | //! Domain Name System (DNS) 2 | //! 3 | //! This code was adapted from the MOROS operating system 4 | //! 5 | //! Copyright (c) 2019-2022 Vincent Ollivier () 6 | //! 7 | //! See RFC 1035 for implementation details 8 | 9 | extern crate alloc; 10 | use alloc::collections::BTreeMap; 11 | use alloc::string::String; 12 | use alloc::vec; 13 | use alloc::vec::Vec; 14 | use bit_field::BitField; 15 | use smoltcp::wire::{IpAddress, IpEndpoint, Ipv4Address}; 16 | use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer}; 17 | use smoltcp::time::Instant; 18 | 19 | use spin::RwLock; 20 | use lazy_static::lazy_static; 21 | 22 | use euralios_std::{time, syscalls, println}; 23 | 24 | use crate::INTERFACE; 25 | 26 | #[repr(u16)] 27 | enum QueryType { 28 | A = 1, 29 | // NS = 2, 30 | // MD = 3, 31 | // MF = 4, 32 | // CNAME = 5, 33 | // SOA = 6, 34 | // MX = 15, 35 | // TXT = 16, 36 | } 37 | 38 | #[repr(u16)] 39 | enum QueryClass { 40 | IN = 1, 41 | } 42 | 43 | #[derive(Debug)] 44 | #[repr(u16)] 45 | pub enum ResponseCode { 46 | NoError = 0, 47 | FormatError = 1, 48 | ServerFailure = 2, 49 | NameError = 3, 50 | NotImplemented = 4, 51 | Refused = 5, 52 | 53 | UnknownError, 54 | NetworkError, 55 | } 56 | 57 | struct Message { 58 | datagram: Vec, 59 | } 60 | 61 | const FLAG_RD: u16 = 0x0100; // Recursion desired 62 | 63 | impl Message { 64 | fn from(datagram: &[u8]) -> Self { 65 | Self { 66 | datagram: Vec::from(datagram), 67 | } 68 | } 69 | 70 | fn query(qname: &str, qtype: QueryType, qclass: QueryClass) -> Self { 71 | let mut datagram = Vec::new(); 72 | 73 | let id = crate::ephemeral_port_number(); // A random semi-unique number 74 | for b in id.to_be_bytes().iter() { 75 | datagram.push(*b); // Transaction ID 76 | } 77 | for b in FLAG_RD.to_be_bytes().iter() { 78 | datagram.push(*b); // Flags 79 | } 80 | for b in (1 as u16).to_be_bytes().iter() { 81 | datagram.push(*b); // Questions 82 | } 83 | for _ in 0..6 { 84 | datagram.push(0); // Answer + Authority + Additional 85 | } 86 | for label in qname.split('.') { 87 | datagram.push(label.len() as u8); // QNAME label length 88 | for b in label.bytes() { 89 | datagram.push(b); // QNAME label bytes 90 | } 91 | } 92 | datagram.push(0); // Root null label 93 | for b in (qtype as u16).to_be_bytes().iter() { 94 | datagram.push(*b); // QTYPE 95 | } 96 | for b in (qclass as u16).to_be_bytes().iter() { 97 | datagram.push(*b); // QCLASS 98 | } 99 | 100 | Self { datagram } 101 | } 102 | 103 | fn id(&self) -> u16 { 104 | u16::from_be_bytes(self.datagram[0..2].try_into().unwrap()) 105 | } 106 | 107 | fn header(&self) -> u16 { 108 | u16::from_be_bytes(self.datagram[2..4].try_into().unwrap()) 109 | } 110 | 111 | fn is_response(&self) -> bool { 112 | self.header().get_bit(15) 113 | } 114 | 115 | fn rcode(&self) -> ResponseCode { 116 | match self.header().get_bits(11..15) { 117 | 0 => ResponseCode::NoError, 118 | 1 => ResponseCode::FormatError, 119 | 2 => ResponseCode::ServerFailure, 120 | 3 => ResponseCode::NameError, 121 | 4 => ResponseCode::NotImplemented, 122 | 5 => ResponseCode::Refused, 123 | _ => ResponseCode::UnknownError, 124 | } 125 | } 126 | } 127 | 128 | /// Port number to connect to on DNS server 129 | const DNS_PORT: u16 = 53; 130 | 131 | lazy_static! { 132 | /// A vector of DNS servers. Currently only the last pushed is used 133 | /// Start with the Google DNS server as fallback 134 | static ref SERVERS: RwLock> = RwLock::new(vec![IpAddress::v4(8, 8, 8, 8)]); 135 | 136 | /// Cache of host name to IP address lookup 137 | /// NOTE: These entries should also have an expiry time 138 | static ref CACHE: RwLock> = RwLock::new(BTreeMap::new()); 139 | } 140 | 141 | /// Add a DNS server which can be used to resolve hostnames 142 | pub fn add_server(address: IpAddress) { 143 | SERVERS.write().push(address); 144 | } 145 | 146 | /// Find the IP address of a given host name 147 | /// 148 | /// Uses CACHE to store previous lookups, and uses the DNS server last 149 | /// added to SERVERS. 150 | pub fn resolve(name: &str) -> Result { 151 | // Check the cache 152 | { 153 | let cache = CACHE.read(); 154 | if let Some(addr) = cache.get(name) { 155 | // Here could check expiry time 156 | return Ok(addr.clone()); 157 | } 158 | } 159 | 160 | // Get the IP address of a DNS server 161 | let dns_address = { 162 | let servers = SERVERS.read(); 163 | match servers.last() { 164 | Some(addr) => addr.clone(), 165 | None => {return Err(ResponseCode::NotImplemented);} 166 | } 167 | }; 168 | 169 | let server = IpEndpoint::new(dns_address, DNS_PORT); 170 | 171 | // Get a local port for the connection 172 | let local_port = crate::ephemeral_port_number(); 173 | let client = IpEndpoint::new(IpAddress::Unspecified, local_port); 174 | 175 | let query = Message::query(name, QueryType::A, QueryClass::IN); 176 | 177 | // Add the UDP socket to the network interface 178 | let udp_handle = { 179 | let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 2048]); 180 | let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; 2048]); 181 | let udp_socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer); 182 | 183 | let mut some_interface = INTERFACE.write(); 184 | let interface = (*some_interface).as_mut().unwrap(); 185 | interface.add_socket(udp_socket) 186 | }; 187 | 188 | #[derive(Debug)] 189 | enum State { Bind, Query, Response } 190 | let mut state = State::Bind; 191 | 192 | // Don't keep a reference to INTERFACE because this thread 193 | // is interleaved with threads servicing other requests. 194 | loop { 195 | { 196 | // Get a lock on the INTERFACE 197 | let mut some_interface = INTERFACE.write(); 198 | let interface = (*some_interface).as_mut().unwrap(); 199 | 200 | if let Err(e) = interface.poll(Instant::from_micros(time::microseconds_monotonic() as i64)) { 201 | println!("Network Error: {}", e); 202 | return Err(ResponseCode::UnknownError); 203 | } 204 | 205 | let socket = interface.get_socket::(udp_handle); 206 | 207 | state = match state { 208 | State::Bind if !socket.is_open() => { 209 | socket.bind(client).unwrap(); 210 | State::Query 211 | } 212 | State::Query if socket.can_send() => { 213 | socket.send_slice(&query.datagram, server).expect("cannot send"); 214 | State::Response 215 | } 216 | State::Response if socket.can_recv() => { 217 | let (data, _) = socket.recv().expect("cannot receive"); 218 | let message = Message::from(data); 219 | if message.id() == query.id() && message.is_response() { 220 | interface.remove_socket(udp_handle); 221 | return match message.rcode() { 222 | ResponseCode::NoError => { 223 | // TODO: Parse the datagram instead of 224 | // extracting the last 4 bytes. 225 | //let rdata = message.answer().rdata(); 226 | let n = message.datagram.len(); 227 | let rdata = &message.datagram[(n - 4)..]; 228 | 229 | let addr = IpAddress::from(Ipv4Address::from_bytes(rdata)); 230 | // Put into the cache 231 | CACHE.write().insert(String::from(name), addr.clone()); 232 | 233 | Ok(addr) 234 | } 235 | rcode => { 236 | Err(rcode) 237 | } 238 | } 239 | } 240 | state 241 | } 242 | _ => state 243 | }; 244 | } // release lock on INTERFACE 245 | 246 | // Wait, try again later 247 | syscalls::thread_yield(); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /kernel/src/message.rs: -------------------------------------------------------------------------------- 1 | //! Message type in EuraliOS "Merriwig" kernel 2 | 3 | use alloc::sync::Arc; 4 | use spin::RwLock; 5 | use x86_64::{VirtAddr, PhysAddr}; 6 | 7 | use crate::process::Thread; 8 | use crate::rendezvous::Rendezvous; 9 | use crate::syscalls; 10 | 11 | /// Messages can transmit values, communication handles, 12 | /// or memory chunks. In the kernel these are represented 13 | /// as u64 values, Rendezvous objects, and physical memory 14 | /// addresses. 15 | pub enum MessageData { 16 | Value(u64), 17 | Rendezvous(Arc>), 18 | Memory(PhysAddr) 19 | } 20 | 21 | impl From for MessageData { 22 | fn from (value: u64) -> Self { 23 | MessageData::Value(value) 24 | } 25 | } 26 | impl From>> for MessageData { 27 | fn from (rv: Arc>) -> Self { 28 | MessageData::Rendezvous(rv) 29 | } 30 | } 31 | impl From for MessageData { 32 | fn from (physaddr: PhysAddr) -> Self { 33 | MessageData::Memory(physaddr) 34 | } 35 | } 36 | 37 | /// Messages can be either Short or Long 38 | /// 39 | /// Short messages just contain values 40 | /// Long messages can contain memory or communication handles. 41 | pub enum Message { 42 | Short(u64, u64, u64), 43 | Long(u64, MessageData, MessageData), 44 | } 45 | 46 | // Syscall message control bits 47 | // 48 | // The only constraint is that syscalls reserve the first 8 bits for 49 | // the syscall number. The other 56 bits can be used as part of the 50 | // message. 51 | const MESSAGE_LONG: u64 = 1 << 8; 52 | const MESSAGE_DATA2_RDV: u64 = 1 << 9; 53 | const MESSAGE_DATA2_MEM: u64 = 2 << 9; 54 | const MESSAGE_DATA2_ERR: u64 = 3 << 9; 55 | 56 | const MESSAGE_DATA2_TYPE: u64 = 57 | MESSAGE_DATA2_RDV | MESSAGE_DATA2_MEM | MESSAGE_DATA2_ERR; // Bit mask 58 | 59 | const MESSAGE_DATA3_RDV: u64 = 1 << 11; 60 | const MESSAGE_DATA3_MEM: u64 = 2 << 11; 61 | const MESSAGE_DATA3_ERR: u64 = 3 << 11; 62 | 63 | const MESSAGE_DATA3_TYPE: u64 = 64 | MESSAGE_DATA3_RDV | MESSAGE_DATA3_MEM | MESSAGE_DATA3_ERR; // Bit mask 65 | 66 | // General message types 67 | pub const READ: u64 = 1; // Short(READ, offset, length 68 | pub const WRITE: u64 = 2; // Long(WRITE, length, handle) 69 | pub const DATA: u64 = 2; // Same as write 70 | pub const CHAR: u64 = 3; 71 | pub const JSON: u64 = 4; // Information in JSON format 72 | pub const VIDEO_MEMORY: u64 = 5; // Specific memory handle for video memory 73 | pub const COMM_HANDLE: u64 = 6; // A communication handle 74 | 75 | impl Message { 76 | /// Convert a Message into values which will be returned to user 77 | /// code by the receive or send_receive syscalls. 78 | /// 79 | /// Includes moving Rendezvous and memory chunks into the 80 | /// receiving process' handles and page tables, calling 81 | /// give_rendezvous() and give_memory_chunk() methods. 82 | /// 83 | /// Note: No error return type because errors are indicated to the 84 | /// receiver in the values. 85 | pub fn to_values( 86 | &self, 87 | thread: &Thread 88 | ) -> (u64, u64, u64, u64) { 89 | match self { 90 | Message::Short(data1, data2, data3) => (0, *data1, *data2, *data3), 91 | Message::Long(data1, data2, data3) => { 92 | let mut ctrl: u64 = 0; // No error 93 | 94 | let value2 = match data2 { 95 | MessageData::Value(value) => *value, 96 | MessageData::Rendezvous(rdv) => { 97 | ctrl |= MESSAGE_DATA2_RDV | MESSAGE_LONG; 98 | thread.give_rendezvous(rdv.clone()) as u64 99 | } 100 | MessageData::Memory(physaddr) => { 101 | match thread.give_memory_chunk(*physaddr) { 102 | Ok(virtaddr) => { 103 | ctrl |= MESSAGE_DATA2_MEM | MESSAGE_LONG; 104 | virtaddr.as_u64() 105 | } 106 | Err(error_code) => { 107 | ctrl |= MESSAGE_DATA2_ERR | MESSAGE_LONG; 108 | error_code as u64 109 | } 110 | } 111 | } 112 | }; 113 | 114 | let value3 = match data3 { 115 | MessageData::Value(value) => *value, 116 | MessageData::Rendezvous(rdv) => { 117 | ctrl |= MESSAGE_DATA3_RDV | MESSAGE_LONG; 118 | thread.give_rendezvous(rdv.clone()) as u64 119 | } 120 | MessageData::Memory(physaddr) => { 121 | match thread.give_memory_chunk(*physaddr) { 122 | Ok(virtaddr) => { 123 | ctrl |= MESSAGE_DATA3_MEM | MESSAGE_LONG; 124 | virtaddr.as_u64() 125 | } 126 | Err(error_code) => { 127 | ctrl |= MESSAGE_DATA3_ERR | MESSAGE_LONG; 128 | error_code as u64 129 | } 130 | } 131 | } 132 | }; 133 | (ctrl, *data1, value2, value3) 134 | } 135 | } 136 | } 137 | 138 | /// Take data passed via syscall and convert to 139 | /// a kernel Message object. 140 | /// 141 | /// If the user passed rendezvous or memory handles then these are 142 | /// removed from the process using take_rendezvous() and 143 | /// take_memory_chunk() methods, and stored in the Message. 144 | /// 145 | pub fn from_values( 146 | thread: &mut Thread, 147 | syscall_id: u64, 148 | data1: u64, 149 | data2: u64, 150 | data3: u64) -> Result { 151 | 152 | if syscall_id & MESSAGE_LONG == 0 { 153 | Ok(Message::Short(data1, 154 | data2, 155 | data3)) 156 | } else { 157 | // Long message 158 | let message = Message::Long( 159 | data1, 160 | match syscall_id & MESSAGE_DATA2_TYPE { 161 | MESSAGE_DATA2_RDV => { 162 | // Moving or copying a handle 163 | // First copy, then drop if message is valid 164 | if let Some(rdv) = thread.rendezvous(data2) { 165 | MessageData::Rendezvous(rdv) 166 | } else { 167 | // Invalid handle 168 | return Err(syscalls::SYSCALL_ERROR_INVALID_HANDLE); 169 | } 170 | } 171 | MESSAGE_DATA2_MEM => { 172 | // Memory handle 173 | let (physaddr, _level) = thread.memory_chunk( 174 | VirtAddr::new(data2))?; 175 | MessageData::Memory(physaddr) 176 | } 177 | _ => MessageData::Value(data2) 178 | }, 179 | match syscall_id & MESSAGE_DATA3_TYPE { 180 | MESSAGE_DATA3_RDV => { 181 | if let Some(rdv) = thread.rendezvous(data3) { 182 | MessageData::Rendezvous(rdv) 183 | } else { 184 | // Invalid handle. 185 | // If we moved data2 we would have to put it back here 186 | return Err(syscalls::SYSCALL_ERROR_INVALID_HANDLE); 187 | } 188 | } 189 | MESSAGE_DATA3_MEM => { 190 | // Memory handle 191 | let (physaddr, _level) = thread.memory_chunk( 192 | VirtAddr::new(data3))?; 193 | MessageData::Memory(physaddr) 194 | } 195 | _ => MessageData::Value(data3) 196 | }); 197 | // Message is valid => Remove handles being moved 198 | match syscall_id & MESSAGE_DATA2_TYPE { 199 | MESSAGE_DATA2_RDV => { 200 | let _ = thread.take_rendezvous(data2); 201 | } 202 | MESSAGE_DATA2_MEM => { 203 | let _ = thread.take_memory_chunk(VirtAddr::new(data2)); 204 | } 205 | _ => {} 206 | } 207 | match syscall_id & MESSAGE_DATA3_TYPE { 208 | MESSAGE_DATA3_RDV => { 209 | let _ = thread.take_rendezvous(data3); 210 | } 211 | MESSAGE_DATA3_MEM => { 212 | let _ = thread.take_memory_chunk(VirtAddr::new(data3)); 213 | } 214 | _ => {} 215 | } 216 | Ok(message) 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /doc/journal/26-servers.org: -------------------------------------------------------------------------------- 1 | * Servers 2 | 3 | Since the EuraliOS file system is the main way to provide and control 4 | access to resources, we're going to need plenty of user-space servers 5 | to handle messages as the [[./22-ramdisk.org][RAMdisk]] server does. To avoid repeating 6 | code, we'll generalise the RAMdisk code and turn the message handling 7 | code into a standard library module. It's also a good excuse to learn 8 | about Rust traits. 9 | 10 | ** FileLike trait 11 | 12 | The functions to handle messages for files currently allows reading 13 | and writing data, and writing is simplified to just appending. A third 14 | function is to truncate (empty) a file in the =open= function. The 15 | interface to something that is like a file is therefore defined (in 16 | =euralios_std::server=) as 17 | #+begin_src rust 18 | pub trait FileLike { 19 | /// Number of bytes in the file 20 | fn len(&self) -> usize; 21 | /// Read data starting at a given offset, storing in pre-allocated slice 22 | fn read(&self, start: usize, buffer: &mut [u8]) -> Result; 23 | /// Write data starting at given offset 24 | fn write(&mut self, start: usize, buffer: &[u8]) -> Result; 25 | /// Delete contents 26 | fn clear(&mut self) -> Result<(), syscalls::SyscallError>; 27 | } 28 | #+end_src 29 | The =read= function takes a pre-allocated mutable slice as input, so 30 | that the caller can control memory allocation. In our case we are allocating 31 | memory chunks for sending in messages. 32 | 33 | Not all files may implement all of these functions, so =read=, =write= 34 | and =clear= have default implementations that return 35 | =Err(syscalls::SYSCALL_ERROR_NOT_IMPLEMENTED)=. Errors should be 36 | handled by the standard library code, and returned as an =ERROR= 37 | message to the caller. The =len= function in most cases is simple, but 38 | less clear for streams where the length isn't known. Perhaps that 39 | should also return a =Result= (or =Option=) to indicate where length 40 | isn't known. The =clear= function should probably also be generalised 41 | at some point to =truncate(length:usize)= but that isn't needed yet. 42 | 43 | ** DirLike trait 44 | 45 | Directories need to look up files and subdirectories, and list their 46 | contents. Both files and directories could implement a common trait 47 | and be treated the same by directories, and my C++ instinct was to 48 | create more complicated types and use inheritance. Rust, or perhaps my 49 | lack of experience with it, seems to discourage this, with all my 50 | attempts at adding abstraction leading to more code and more 51 | complicated code. On balance I think a simpler approach is better for now, 52 | so files and directories are looked up separately: 53 | #+begin_src rust 54 | pub trait DirLike { 55 | /// Lookup and return shared reference to a directory 56 | fn get_dir(&self, name: &str) -> Result>, syscalls::SyscallError>; 57 | /// Lookup and return shared reference to a file 58 | fn get_file(&self, name: &str) -> Result>, syscalls::SyscallError>; 59 | /// Return a JSON string describing the directory and its contents 60 | fn query(&self) -> String; 61 | #+end_src 62 | 63 | ** PCI devices as a filesystem 64 | 65 | The =pci= program handles messages and behaves a bit like a file 66 | server. We can simplify it by removing most of the message handling 67 | code for pci-specific functions like reading device BAR registers, and 68 | replacing them with file read/writes. 69 | 70 | The root directory of the =pci= server represents a collection of devices: 71 | #+begin_src rust 72 | struct DeviceCollection { 73 | devices: BTreeMap>> 74 | } 75 | #+end_src 76 | 77 | #+begin_src rust 78 | impl DirLike for DeviceCollection { 79 | /// Each subdirectory is a PCI Device 80 | fn get_dir(&self, name: &str) -> Result>, syscalls::SyscallError> { 81 | match self.devices.get(name) { 82 | Some(device) => Ok(device.clone()), 83 | None => Err(syscalls::SYSCALL_ERROR_NOTFOUND) 84 | } 85 | } 86 | /// No files; always returns not found error 87 | fn get_file(&self, name: &str) -> Result>, syscalls::SyscallError> { 88 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 89 | } 90 | fn query(&self) -> String { 91 | // Create JSON description of devices 92 | } 93 | } 94 | #+end_src 95 | 96 | For this to work the =Device= struct should also implement the =DirLike= trait: 97 | #+begin_src rust 98 | impl DirLike for Device { 99 | fn get_dir(&self, name: &str) -> Result>, syscalls::SyscallError> { 100 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 101 | } 102 | fn get_file(&self, name: &str) -> Result>, syscalls::SyscallError> { 103 | Err(syscalls::SYSCALL_ERROR_NOTFOUND) 104 | } 105 | fn query(&self) -> String { 106 | // Put device information into JSON string 107 | } 108 | } 109 | #+end_src 110 | We don't yet have any files under each PCI device directory, but we 111 | can add those later to provide ways to access and configure devices. 112 | 113 | ** Multi-threaded ports 114 | 115 | Since =pci= is now multi-threaded, we need to make sure that the 116 | different threads can't interfere with each other. Unfortunately all 117 | access to PCI data involves writing to the =CONFIG_ADDRESS= port 118 | =0xCF8= and then reading or writing port =CONFIG_DATA=, =0xCFC=. 119 | 120 | To control access we will use a [[https://docs.rs/spin/0.5.2/spin/struct.Mutex.html][mutex]] and the [[https://docs.rs/lazy_static/latest/lazy_static/][lazy_static]] crate that 121 | was used in parts of the kernel. 122 | 123 | Putting the functions to read and write data to the PCI configuration 124 | address and data ports into a =struct= implementation: 125 | #+begin_src rust 126 | struct PciPorts {} 127 | 128 | impl PciPorts { 129 | const CONFIG_ADDRESS: u16 = 0xCF8; 130 | const CONFIG_DATA: u16 = 0xCFC; 131 | /// Write to Address and Data ports 132 | fn write(&mut self, address: u32, value: u32) { 133 | unsafe { 134 | asm!("out dx, eax", 135 | in("dx") Self::CONFIG_ADDRESS, 136 | in("eax") address, 137 | options(nomem, nostack)); 138 | 139 | asm!("out dx, eax", 140 | in("dx") Self::CONFIG_DATA, 141 | in("eax") value, 142 | options(nomem, nostack)); 143 | } 144 | } 145 | 146 | /// Write to Address port, read from Data port 147 | /// Note: Mutates ports values so needs mut self 148 | fn read(&mut self, address: u32) -> u32 { 149 | let value: u32; 150 | unsafe { 151 | asm!("out dx, eax", 152 | in("dx") Self::CONFIG_ADDRESS, 153 | in("eax") address, 154 | options(nomem, nostack)); 155 | 156 | asm!("in eax, dx", 157 | in("dx") Self::CONFIG_DATA, 158 | lateout("eax") value, 159 | options(nomem, nostack)); 160 | } 161 | value 162 | } 163 | } 164 | #+end_src 165 | we can then put one of these behind a mutex: 166 | #+begin_src rust 167 | lazy_static! { 168 | static ref PORTS: Mutex = Mutex::new(PciPorts{}); 169 | } 170 | #+end_src 171 | There's nothing to prevent us from modifying the ports in another part 172 | of the code but if all access is through this then we should avoid 173 | many problems. Race conditions can still occur however if a caller 174 | releases the mutex lock between reading and writing values to device 175 | registers e.g. to set or clear a bit. 176 | 177 | ** Appendix: Locking and =if let= clauses 178 | 179 | In the =open= function there was a chain of =else if= clauses, including one like: 180 | #+begin_src rust 181 | } else if let Ok(file) = dir.read().get_file(key) { // <- Locked 182 | // Opening an existing file 183 | } else if path_iter.peek().is_some() { 184 | // Missing a directory 185 | } else if (flags & message::O_CREATE) == message::O_CREATE { 186 | // Create a file 187 | let new_file = dir.write().make_file(key)?; // <- Hangs 188 | } 189 | #+end_src 190 | 191 | This code locked up because the lock created by =dir.read()= was not released 192 | by the time =dir.write()= is called. A solution is to define an intermediate 193 | variable: 194 | #+begin_src rust 195 | } else { 196 | let result_file = dir.read().get_file(key); // <- Locks and releases 197 | if let Ok(file) = result_file { 198 | // Opening an existing file 199 | } else if path_iter.peek().is_some() { 200 | // Missing a directory 201 | } else if (flags & message::O_CREATE) == message::O_CREATE { 202 | // Create a file 203 | let new_file = dir.write().make_file(key)?; 204 | } 205 | } 206 | #+end_src 207 | -------------------------------------------------------------------------------- /doc/journal/16-arp.org: -------------------------------------------------------------------------------- 1 | * Address Resolution Protocol (ARP) 2 | 3 | The most basic level of IP networking is the [[https://en.wikipedia.org/wiki/Link_layer][link layer]], i.e ethernet 4 | cards with physical harware addresses. The protocol used to discover 5 | the hardware (MAC) address associated with an IP address on the local 6 | network is the [[https://en.wikipedia.org/wiki/Address_Resolution_Protocol][Address Resolution Protocol (ARP)]]. 7 | 8 | ** Getting the MAC address 9 | 10 | We can open a connection to the =rtl8139= driver with 11 | #+begin_src rust 12 | let handle = syscalls::open("/dev/nic").expect("Couldn't open /dev/nic"); 13 | #+end_src 14 | 15 | We need to get the hardware (MAC) address of the card, so for now we'll create 16 | a new message type =nic:GET_MAC_ADDRESS= to request the address, and =nic:MAC_ADDRESS= 17 | for the return message. Then in =arp= we can get the MAC address with 18 | #+begin_src rust 19 | let (_, ret, _) = rcall(&handle, nic::GET_MAC_ADDRESS, 20 | 0.into(), 0.into(), 21 | Some(message::nic::MAC_ADDRESS)).unwrap(); 22 | 23 | let mac_address = MacAddress::from_u64(ret.value()); 24 | let mac = mac_address.bytes(); 25 | #+end_src 26 | which sends a =Short= message, and converts the returned value into a 27 | =MacAddress= object. The separate bytes are used to create the data 28 | for the ethernet frame and ARP packet. 29 | 30 | ** Sending ARP requests 31 | 32 | To send messages over the network the network card driver receives 33 | =WRITE= messages, and copies the data from a memory chunk into one of 34 | the transmit buffers. The network card expects that data to start 35 | with an ethernet frame, consisting of: 36 | - Destination MAC address (6 bytes) 37 | - Source MAC address (6 bytes) 38 | - Ethernet protocol type (2 bytes). IPv4 is 0x0800; ARP is 0x0806; 39 | IPv6 is 0x86DD. 40 | 41 | After this should come the packet data. If the ethernet protocol is 42 | ARP then it should consist of: 43 | - Hardware type (2 bytes), always 0x0001 for ethernet 44 | - Protocol type (2 bytes), 0x0800 for IP protocol 45 | - Hardware address length (1 byte). 6 for ethernet MAC address 46 | - Protocol address length (1 byte). 4 for IPv4 47 | - ARP Operation Code (2 bytes). 0x0001 for request, 0x0002 for reply 48 | - Source hardware address 49 | - Source protocol address 50 | - Destination hardware address. All zeros because we don't know what it is. 51 | - Destination protocol address. 52 | The smoltcp code to handle [[https://docs.rs/smoltcp/latest/src/smoltcp/wire/arp.rs.html][arp packets]] and [[https://github.com/smoltcp-rs/smoltcp/blob/master/src/wire/ethernet.rs#L89][ethernet frames]] is a useful place 53 | to look to figure this out. 54 | 55 | [[https://wiki.qemu.org/Documentation/Networking][QEMU's network stack]] assigns guests IPs starting 10.0.2.15, so we can 56 | use that as our "source protocol address". The network gateway is at 57 | IP address 10.0.2.2 but we don't know its hardware address. We can 58 | send an ARP request, asking for a response from the computer with IP 59 | address 10.0.2.2, by setting that as the destination protocol address 60 | and leaving the destination hardware address as all zeros in the ARP 61 | packet. In the ethernet frame we'll set the destination to 62 | =ff:ff:ff:ff:ff:ff= because this is the broadcast address. The data to 63 | be sent to the =rtl8139= driver and loaded into the transmission 64 | buffer is therefore: 65 | #+begin_src rust 66 | let frame = [ 67 | // Ethernet frame header 68 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Destination MAC address (Broadcast) 69 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], // Source address 70 | 0x08, 0x06, // Ethernet protocol type (ARP = 0x0806) 71 | 72 | // ARP packet 73 | 0, 1, // u16 Hardware type (Ethernet = 0x1) 74 | 8, 0, // u16 Protocol type (IP = 0x0800) 75 | 6, // u8 hlen, Hardware address length (Ethernet = 6) 76 | 4, // u8 plen, Protocol address length (IPv4 = 4) 77 | 0, 1, // u16 ARP Operation Code (Request = 0x0001) 78 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], // Source hardware address - hlen bytes 79 | 10, 0, 2, 15, // Source protocol address - plen bytes 80 | 0, 0, 0, 0, 0, 0, // Destination hardware address (unknown) 81 | 10, 0, 2, 2 // Destination protocol address 82 | ]; 83 | #+end_src 84 | 85 | To send this data to the network driver we need to put it into a message. 86 | We copy it into a newly allocated chunk of memory: 87 | #+begin_src rust 88 | let mem_handle = syscalls::MemoryHandle::from_u8_slice(&frame); 89 | #+end_src 90 | and send it to the driver: 91 | #+begin_src rust 92 | syscalls::send(&handle, 93 | message::Message::Long( 94 | message::WRITE, 95 | (frame.len() as u64).into(), 96 | mem_handle.into())); 97 | #+end_src 98 | 99 | ** Intercepting packet data 100 | 101 | To check that the rtl8139 driver and our ARP code is sending and 102 | receiving packets correctly, we can use [[https://wiki.qemu.org/Documentation/Networking][QEMU's networking]] to capture 103 | all packets. This is done by changing the arguments to QEMU in 104 | =kernel/Config.toml=, telling QEMU to use =filter-dump= to save 105 | network packets to a file =dump.dat=: 106 | #+begin_src toml 107 | run-args = ["-netdev", "user,id=u1", "-device", "rtl8139,netdev=u1", "-object", "filter-dump,id=f1,netdev=u1,file=dump.dat"] 108 | #+end_src 109 | This will save network traffic in [[https://wiki.wireshark.org/Development/LibpcapFileFormat][libpcap format]], a standard format 110 | which can be read by tools like [[https://www.tcpdump.org/][tcpdump]] and [[https://www.wireshark.org/][wireshark]]. We won't need 111 | fancy features so just use tcpdump. 112 | 113 | #+begin_src bash 114 | $ tcpdump -r dump.dat 115 | reading from file dump.dat, link-type EN10MB (Ethernet), snapshot length 65536 116 | 07:38:15.457337 ARP, Request who-has 10.0.2.2 tell 10.0.2.15, length 28 117 | 07:38:15.457414 ARP, Reply 10.0.2.2 is-at 52:55:0a:00:02:02 (oui Unknown), length 50 118 | #+end_src 119 | 120 | This shows that we're sending the request correctly, and should be able 121 | to receive the reply. 122 | 123 | ** Receiving the ARP reply 124 | 125 | Currently the =rtl8139= driver has to be polled to check if a message has 126 | been received: 127 | #+begin_src rust 128 | loop { 129 | match rcall(&handle, message::READ, 130 | 0.into(), 0.into(), 131 | None).unwrap() { 132 | (message::DATA, md_length, md_handle) => { 133 | // Received. 134 | break; 135 | } 136 | _ => { 137 | // Wait and retry 138 | syscalls::thread_yield(); 139 | } 140 | } 141 | } 142 | #+end_src 143 | This code keeps checking if a packet has been received. If it has then 144 | it will do something with it; if not, or an error occurred, then just 145 | wait and try again. This is inefficient, and a better way would be to 146 | use interrupts to get notifications when a packet is received. 147 | 148 | Once a packet is received, for now we can just print it: 149 | #+begin_src rust 150 | let handle = md_handle.memory(); // Get MemoryHandle from MessageData 151 | 152 | // Get the ethernet frame via a &[u8] slice 153 | let frame = handle.as_slice::(md_length.value() as usize); 154 | let from_mac = MacAddress::new(frame[0..6].try_into().unwrap()); 155 | let to_mac = MacAddress::new(frame[6..12].try_into().unwrap()); 156 | debug_println!("Ethernet frame: to {} from {} type {:02x}{:02x}", 157 | from_mac, to_mac, frame[12], frame[13]); 158 | 159 | // ARP packet 160 | let arp = &frame[14..]; 161 | 162 | debug_println!("ARP packet: hw {:02x}{:02x} protocol {:02x}{:02x} hlen {:02x} plen {:02x} op {:02x}{:02x}", 163 | arp[0], arp[1], arp[2], arp[3], arp[4], arp[5], arp[6], arp[7]); 164 | debug_println!(" source {} / {}.{}.{}.{}", 165 | MacAddress::new(arp[8..14].try_into().unwrap()), arp[14], arp[15], arp[16], arp[17]); 166 | debug_println!(" target {} / {}.{}.{}.{}", 167 | MacAddress::new(arp[18..24].try_into().unwrap()), arp[24], arp[25], arp[26], arp[27]); 168 | #+end_src 169 | 170 | This now produces the result in figure [[fig-arp]] and it works! 171 | 172 | #+CAPTION: Sending an =ARP= broadcast request and receiving a reply from the QEMU gateway 173 | #+NAME: fig-arp 174 | [[./img/16-01-arp.png]] 175 | 176 | There are several problems with this, including: 177 | 1. The need to poll for packets, rather than being interrupt-driven. 178 | 2. This code will get very confused in a realistic situation where 179 | many different kinds of packets are being transmitted on the 180 | network: It assumes the packet that's received is a reply to the ARP packet 181 | that's sent, but that's not guaranteed. 182 | 183 | Implementing a network stack to handle multiple kinds of messages and simultaneous 184 | connections is very complicated. Fortunately there are libraries to do this 185 | including [[https://docs.rs/smoltcp/latest/smoltcp/][smoltcp]] which we'll use in the [[./17-tcp-stack.org][next section]]. 186 | --------------------------------------------------------------------------------