commit e491435deaea54ef6b3380418db09c016a61d93c Author: FantasyPvP Date: Fri Mar 10 00:25:08 2023 +0000 ok ok diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..c0affd2 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins", "alloc"] + +[build] +target = "x86_64-CrystalOS.json" + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.nf b/.nf new file mode 100644 index 0000000..8b4b326 --- /dev/null +++ b/.nf @@ -0,0 +1,43 @@ +[?25l[?7l -` + .o+` + `ooo/ + `+oooo: + `+oooooo: + -+oooooo+: + `/:-:++oooo+: + `/++++/+++++++: + `/++++++++++++++: + `/+++ooooooooooooo/` + ./ooosssso++osssssso+` + .oossssso-````/ossssss+` + -osssssso. :ssssssso. + :osssssss/ osssso+++. + /ossssssss/ +ssssooo/- + `/ossssso+/:- -:/+osssso+- + `+sso+:-` `.-/+oso: + `++:. `-/+/ + .` `/ +fantasypvp@arch-x240 +-------------------- +OS: Arch Linux x86_64 +Host: 20AL007YUK ThinkPad X240 +Kernel: 6.1.6-zen1-2-zen +Uptime: 19 days, 22 hours, 25 mins +Packages: 1064 (pacman) +Shell: bash 5.1.16 +Resolution: 1366x768 +DE: Plasma 5.26.5 +WM: KWin +WM Theme: Bismuth +Theme: Breeze Light [Plasma], Materia-dark [GTK2/3] +Icons: PlasmaXDark [Plasma], PlasmaXDark [GTK2/3] +Terminal: vscode +CPU: Intel i5-4200U (4) @ 2.600GHz +GPU: Intel Haswell-ULT +Memory: 5401MiB / 7637MiB + +         +         + + +[?25h[?7h \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..c15d989 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "cargo run", + "type": "shell", + "command": "~/.cargo/bin/cargo", // note: full path to the cargo + "args": [ + "run", + // "--release", + // "--", + // "arg1" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..fb76618 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,323 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "CrystalOS" +version = "0.2.1" +dependencies = [ + "ansi_rgb", + "async-trait", + "bootloader", + "conquer-once", + "crossbeam-queue", + "futures-util", + "lazy_static", + "linked_list_allocator", + "pc-keyboard", + "pic8259", + "rand", + "rgb", + "spin", + "uart_16550", + "volatile 0.2.7", + "x86_64", +] + +[[package]] +name = "ansi_rgb" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a730095eb14ee842a0f1e68504b85c8d4a19b1ef2ac2a9b4debf0ed982f9b08a" +dependencies = [ + "rgb", +] + +[[package]] +name = "async-trait" +version = "0.1.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "689894c2db1ea643a50834b999abf1c110887402542955ff5451dab8f861f9ed" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bootloader" +version = "0.9.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6e02311b16c9819e7c72866d379cdd3026c3b7b25c1edf161f548f8e887e7ff" + +[[package]] +name = "bytemuck" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "conquer-once" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c6d3a9775a69f6d1fe2cc888999b67ed30257d3da4d2af91984e722f2ec918a" +dependencies = [ + "conquer-util", +] + +[[package]] +name = "conquer-util" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e763eef8846b13b380f37dfecda401770b0ca4e56e95170237bd7c25c7db3582" + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "linked_list_allocator" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e322f259d225fbae43a1b053b2dc6a5968a6bdf8b205f5de684dab485b95030e" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "pc-keyboard" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6f2d937e3b8d63449b01401e2bae4041bc9dd1129c2e3e0d239407cf6635ac" + +[[package]] +name = "pic8259" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ec21f514e2e16e94649f1d041ca4a7069b512c037ac156360652a775e6229d" +dependencies = [ + "x86_64", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rgb" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spinning_top" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75adad84ee84b521fb2cca2d4fd0f1dab1d8d026bda3c5bea4ca63b5f9f9293c" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "uart_16550" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b074eb9300ad949edd74c529c0e8d451625af71bb948e6b65fe69f72dc1363d9" +dependencies = [ + "bitflags", + "rustversion", + "x86_64", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "volatile" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "x86_64" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "100555a863c0092238c2e0e814c1096c1e5cf066a309c696a87e907b5f8c5d69" +dependencies = [ + "bit_field", + "bitflags", + "rustversion", + "volatile 0.4.6", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e1243b2 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "CrystalOS" +version = "0.2.1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + +[package.metadata.bootimage] +test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", "-display", "none"] +test-success-exit-code = 33 +test-timeout = 30 + +run-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio"] + +[dependencies] +bootloader= { version = "0.9.23", features = ["map_physical_memory"] } +volatile="0.2.6" +spin = "0.5.2" +x86_64 = "0.14.2" +uart_16550 = "0.2.0" +pic8259 = "0.10.1" +pc-keyboard = "0.5.0" +linked_list_allocator = "0.10.2" +async-trait = "0.1.62" +# fatfs = { version = "0.3", default-features = false, features = ["core_io"] } +ansi_rgb = "0.2.0" +rgb = "0.8" +rand = { version = "0.8.5", default-features = false, features = ["small_rng"]} + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] + +[dependencies.crossbeam-queue] +version = "0.2.1" +default-features = false +features = ["alloc"] + +[dependencies.conquer-once] +version = "0.3.2" +default-features = false + +[dependencies.futures-util] +version = "0.3.4" +default-features = false +features = ["alloc"] + +[[test]] +name = "should_panic" +harness = false + +[[test]] +name = "stack_overflow" +harness = false diff --git a/README.md b/README.md new file mode 100644 index 0000000..995dde1 --- /dev/null +++ b/README.md @@ -0,0 +1,206 @@ +# CrystalOS + +## Phase 1: The kernel. + +the initial aim of this project was to follow a blog series on how to make a custom operating system found here: + +https://os.phil-opp.com/ + +with the github repo for his project here: + +https://github.com/phil-opp/blog_os + +After reading and implementing the features from the final chapter, (async/await) I could find +no further instruction on how to continue with the project from there despite the author of the +series saying over a year previously that there would be more posts coming soon. + +i guess im gonna just have to improvise :) + +the blog got me through the memory management side of the process so i believe that I should +have a lot more breathing room to implement the features that i want. As of completing the +tutorial, i obviously still dont have access to a standard library, however i can at least +use Vectors and Strings now which are important types, as well as the fact that i have access +to async and heap allocation + +### my aims going forwards: + +- whenever i have the chance to work on this project, i want to try and implement a new utility +which could be useful or cool for anyone using the operating system. + - this could be anything from a cool neofetch style ascii fetcher (if you dont know what im +talking about, its just a cool ascii logo of the operating system that appears when you open +a terminal sometimes) +- improve the text rendering system to create a set of globally accessible functions and/or macros +in order to render the text in a more visually appealing way to the user (as the default yellow text +does look extremely ugly lmao) +- implement a basic text editor (this will be difficult) + - i would need a way to move the cursor around the screen and print text at that location + - this would mean rewriting the majority of the code for the vga buffer module to create a more + flexible system which allows for applications (modules / commands) to take more direct control of + the text rendering whenever they are active + + +# Implementation + +## Phase 2: the shell. + +### shell.rs + +diverging from the original blog series, i have made some significant changes to keyboard.rs +- i have moved the source code that handles the keyboard input from keyboard.rs to shell.rs +- this means that instead of the operating system running a task on startup that continually +awaits a the next keystroke and works from there, the new layout works very differently +- firstly, i use a lazy_static creating a static called CMD which houses the shell itself +- this allows me to reference it from anywhere in the code and initialise it as soon as the program +runs +- this may be changed later as i could just make an init function in shell.rs if i needed to +- the shell contains a get_input function that awaits a keystroke from the user before continuing +- this is looped inside the main shell function and added to a buffer +- when the \n character is inputted, the buffer is copied to the command history vector and then cleared +- additionally the buffer is run through a match statement that will start any app that matches the command +or alias. + +## Phase 3: CrystalAPI + +### the basics: + +the crystal api will essentially be a standard library for any programs that are run by the shell +- it provides basic functions such as waiting for a keystroke or string to be entered by the user +- it will eventually support coloured text output once ive had a chance to modify the code for the vga +buffer to support coloured text output through a public function. + +### example: +here is a template that could be used to program using the crystal API + +```rust + +// ignore everything from this point up until the App struct + +// --------------OS-INTERFACE------------------------------------------------------------------------------------------------------- + +use std::io; +use std::io::Write; // ignore these, i have my own implementations that i will replace them with + +struct CommandHandler {} // a struct used in my code (just ignore) + +impl CommandHandler { // dont modify anything here + fn new() -> Self { + Self {} + } + + fn input(&mut self) -> String { // this function will get replaced by the custom input function + let mut string = String::new(); + io::stdin().read_line(&mut string).expect("error getting input"); + string + } +} + +fn main() { // the entry point to your code, it calls the code for the application + // will be removed when integrated into the os and replaced by the shell command + println!(""); + print!("enter arguments to run command with > "); + io::stdout().flush(); + let mut args = String::new(); + io::stdin().read_line(&mut args).expect("failed to get input"); + let mut app = App::new(CommandHandler::new()); + app.run(args); +} + + + + +// --------------IMPLEMENTATION----------------------------------------------------------------------------------------------------- + + +struct App { // change name to whatever you want + handler: CommandHandler, + // any global variables for the application should be put here + // in the form: varname: VarType, +} + +impl App { // name must be the same as the name of the struct + fn new(handler: CommandHandler) -> Self { + Self { // this should add any variables that are needed while the application is running + handler: handler, + // status: String, (example) + } + } + + fn input(&mut self) -> String { // this function gives command line input + self.handler.input() + } + + fn run(&mut self, args: String) -> Result<(), String> { /* + this represents your actual main function + write all the code for your program starting here + + use println!() to print to the screen + use self.input() to get input from terminal + */ + + println!("app running {}", args); // do stuff here + + // example of how you can use the input function + + println!("type something"); + println!("input: {}", self.input()); + + // if you want to return an error, write: return Err("error message") + // the error message tells the operating system what went wrong with the code or user input. + // if you want to return ok, write: return Ok(()) (make sure to have the 2 sets of brackets) + Ok(()) + } +} +``` + + + +## future plans (as of initial kernel build jan 2023): + +eventually i want to try rewriting the majority of the code for the VGA buffer. +this is so that i can implement what i'll call a 'sandbox mode' for the screen. +this mode will support: +- moving the cursor around with arrow keys +- writing text at the cursor +- writing coloured text anywhere +- reading the entire output of the vga buffer or just a line into a string +eventually, this could theoretically lead to a library that was able to support things like a basic text editor +for writing out messages and the capability to theoretically program basic 2d games in an ascii art style +(something like space invaders, tetris, etc.) + + + + +## UPDATE: 21/02/23 + - created a standard library of functions that any application can use + - implemented a random function to the standard library that can generate random numbers in a range + - added the print and println macros to be directly accessible from the standard library + - added global functions for getting input as a keystroke or from the command line + +```rust +pub async fn stdin() -> String // returns the string inputted by the user + +pub async fn stdchar() -> char // returns the next keystroke + +pub fn crate::std::random::Random::int(min: usize, max: usize) -> usize // returns random integer in range (min <= x <= max) + +pub fn crate::std::random::Random::selection(list: Vec) -> T // returns random element of vector argument +``` + +## UPDATE: 23/02/23 + +### changes + +since the last update i did a few things. + - refactored the entire codebase, moving the standard library, kernel and applications to + separate folders in the source code in order to better organise them + - this should make my next goal much more seamless - this goal will be to further abstract the applications + from the kernel, essentially there will be a lib crate containing the kernel and standard library + in addition to a binary crate which actually has the bootloader / init system / applications etc + + - wrote a basic ASCII rendering engine that can create and place 2d element anywhere on the screen + - the elements are placed using a coordinate system where the top left character of the element is + placed at that coordinate on the screen + +```rust + pub struct crate::std::io::Element +``` \ No newline at end of file diff --git a/docs/crystal-api.md b/docs/crystal-api.md new file mode 100644 index 0000000..1ee5f54 --- /dev/null +++ b/docs/crystal-api.md @@ -0,0 +1,28 @@ +# CrystalAPI + +## Concept: + +the Crystal API will be a set of functions and objects that make it relatively easy for anyone to develop an application +for the crystal operating system. +this means that anyone with the API documentation and source code can easily make a program that follows the + +### current features: +as of 24/01/23 the API allows for the following: +- standard input (String) +- standard output with regular or coloured text +- detecting individual keystrokes +- access to some basic system information + +### short term planned features: +- a greater set of OS information that the program can access + - this will also include a module that the OS and other programs can read from to change the active state of the OS + - this means that different applications can communicate with one another to share information. +- better support for coloured / formatted text + - applications should be able to control when the user can enter keystrokes + - this will have to be implemented in shell.rs with the text input so that the user cannot backspace text + that has been written by the system or the application. +- text sandbox mode + - this would essentially give the program more direct access to the vga buffer through some kind of wrapper function + / class that would grant the ability to make a much more flexible interface. + - the main benefit of this would be the ability for a developer to make simple 2d games by using characters on the vga + buffer as pixels diff --git a/docs/main.md b/docs/main.md new file mode 100644 index 0000000..fe3c1cb --- /dev/null +++ b/docs/main.md @@ -0,0 +1,167 @@ +# CrystalOS + +## Phase 1: The kernel. + +the initial aim of this project was to follow a blog series on how to make a custom operating system found here: + +https://os.phil-opp.com/ + +with the github repo for his project here: + +https://github.com/phil-opp/blog_os + +After reading and implementing the features from the final chapter, (async/await) I could find +no further instruction on how to continue with the project from there despite the author of the +series saying over a year previously that there would be more posts coming soon. + +i guess im gonna just have to improvise :) + +the blog got me through the memory management side of the process so i believe that I should +have a lot more breathing room to implement the features that i want. As of completing the +tutorial, i obviously still dont have access to a standard library, however i can at least +use Vectors and Strings now which are important types, as well as the fact that i have access +to async and heap allocation + +### my aims going forwards: + +- whenever i have the chance to work on this project, i want to try and implement a new utility +which could be useful or cool for anyone using the operating system. + - this could be anything from a cool neofetch style ascii fetcher (if you dont know what im +talking about, its just a cool ascii logo of the operating system that appears when you open +a terminal sometimes) +- improve the text rendering system to create a set of globally accessible functions and/or macros +in order to render the text in a more visually appealing way to the user (as the default yellow text +does look extremely ugly lmao) +- implement a basic text editor (this will be difficult) + - i would need a way to move the cursor around the screen and print text at that location + - this would mean rewriting the majority of the code for the vga buffer module to create a more + flexible system which allows for applications (modules / commands) to take more direct control of + the text rendering whenever they are active + + +# Implementation + +## Phase 2: the shell. + +### shell.rs + +diverging from the original blog series, i have made some significant changes to keyboard.rs +- i have moved the source code that handles the keyboard input from keyboard.rs to shell.rs +- this means that instead of the operating system running a task on startup that continually +awaits a the next keystroke and works from there, the new layout works very differently +- firstly, i use a lazy_static creating a static called CMD which houses the shell itself +- this allows me to reference it from anywhere in the code and initialise it as soon as the program +runs +- this may be changed later as i could just make an init function in shell.rs if i needed to +- the shell contains a get_input function that awaits a keystroke from the user before continuing +- this is looped inside the main shell function and added to a buffer +- when the \n character is inputted, the buffer is copied to the command history vector and then cleared +- additionally the buffer is run through a match statement that will start any app that matches the command +or alias. + +## Phase 3: CrystalAPI + +### the basics: + +the crystal api will essentially be a standard library for any programs that are run by the shell +- it provides basic functions such as waiting for a keystroke or string to be entered by the user +- it will eventually support coloured text output once ive had a chance to modify the code for the vga +buffer to support coloured text output through a public function. + +### example: +here is a template that could be used to program using the crystal API + +```rust + +// ignore everything from this point up until the App struct + +// --------------OS-INTERFACE------------------------------------------------------------------------------------------------------- + +use std::io; +use std::io::Write; // ignore these, i have my own implementations that i will replace them with + +struct CommandHandler {} // a struct used in my code (just ignore) + +impl CommandHandler { // dont modify anything here + fn new() -> Self { + Self {} + } + + fn input(&mut self) -> String { // this function will get replaced by the custom input function + let mut string = String::new(); + io::stdin().read_line(&mut string).expect("error getting input"); + string + } +} + +fn main() { // the entry point to your code, it calls the code for the application + // will be removed when integrated into the os and replaced by the shell command + println!(""); + print!("enter arguments to run command with > "); + io::stdout().flush(); + let mut args = String::new(); + io::stdin().read_line(&mut args).expect("failed to get input"); + let mut app = App::new(CommandHandler::new()); + app.run(args); +} + + + + +// --------------IMPLEMENTATION----------------------------------------------------------------------------------------------------- + + +struct App { // change name to whatever you want + handler: CommandHandler, + // any global variables for the application should be put here + // in the form: varname: VarType, +} + +impl App { // name must be the same as the name of the struct + fn new(handler: CommandHandler) -> Self { + Self { // this should add any variables that are needed while the application is running + handler: handler, + // status: String, (example) + } + } + + fn input(&mut self) -> String { // this function gives command line input + self.handler.input() + } + + fn run(&mut self, args: String) -> Result<(), String> { /* + this represents your actual main function + write all the code for your program starting here + + use println!() to print to the screen + use self.input() to get input from terminal + */ + + println!("app running {}", args); // do stuff here + + // example of how you can use the input function + + println!("type something"); + println!("input: {}", self.input()); + + // if you want to return an error, write: return Err("error message") + // the error message tells the operating system what went wrong with the code or user input. + // if you want to return ok, write: return Ok(()) (make sure to have the 2 sets of brackets) + Ok(()) + } +} +``` + +## future plans: + +eventually i want to try rewriting the majority of the code for the VGA buffer. +this is so that i can implement what i'll call a 'sandbox mode' for the screen. +this mode will support: +- moving the cursor around with arrow keys +- writing text at the cursor +- writing coloured text anywhere +- reading the entire output of the vga buffer or just a line into a string +eventually, this could theoretically lead to a library that was able to support things like a basic text editor +for writing out messages and the capability to theoretically program basic 2d games in an ascii art style +(something like space invaders, tetris, etc.) + diff --git a/hardware.sh b/hardware.sh new file mode 100755 index 0000000..c5979ea --- /dev/null +++ b/hardware.sh @@ -0,0 +1,2 @@ +cargo bootimage --release +sudo dd if=target/x86_64-CrystalOS/release/bootimage-CrystalOS.bin of=/dev/sdc diff --git a/src/applications/crystal_rpg/init.rs b/src/applications/crystal_rpg/init.rs new file mode 100644 index 0000000..d1870be --- /dev/null +++ b/src/applications/crystal_rpg/init.rs @@ -0,0 +1,126 @@ +use async_trait::async_trait; +use rand::prelude::*; + +use super::{ + engine::{eventcheck, Choice, Event}, + entity::{Entity, Enemy, EntityObject}, + player::Player, +}; + +use alloc::{boxed::Box, string::{String, ToString}, vec::Vec, format, borrow::ToOwned}; + +use crate::{ + std::application::{ + Application, + Error, + }, + std::{ + io::{self, println, serial_println, FRAMEGEN, Element}, + random, + }, +}; + + +pub struct GameLoop; + + +#[async_trait] +impl Application for GameLoop { + fn new() -> Self { + Self {} + } + async fn run(&mut self, _args: Vec) -> Result<(), Error> { + + let mut username: String = io::stdin().await; + username = username.trim().to_string(); + + let mut player = Player::new(username); + + let mut enemy = Enemy::new(); + + for _ in 0..30 { + match (eventcheck(player.attack_entity(&mut EntityObject::Enemy(&mut enemy)))) { + Choice::A(result) => { + println!("{}", result); + }, + Choice::B(event) => { + println!("{}", event); + match event { + Event::PlayerKilled => { + println!(" [!] {} was slain by Enemy\n\n[ You lost! ]", player.username); + break; + } + Event::EntityKilled(entity) => { + println!("\n [!] Enemy was slain by {}\n\n [ You won! ]", player.username); + break; + } + } + } + } + println!("{}", eventcheck(enemy.attack_entity(&mut EntityObject::Player(&mut player)))); + println!("[{}\n[{}", player, enemy); + } + + FRAMEGEN.lock().render_frame(); + + + let string = String::from(format!( +"┌────────────────────────────┐ +│ {} +│ {} / {} +└────────────────────────────┘" + , player.username, player.health_points, player.max_health_points)); + let mut healthbar = Element::from_str(string); + healthbar.render((1, 1)); + + let new2 = String::from("slushy stfu"); + let mut new = Element::from_str(new2); + + new.render((10, 20)); + + + + FRAMEGEN.lock().render_frame(); + + let fr = FRAMEGEN.lock().get_frame().to_owned(); + serial_println!("{}", { + let mut string = String::new(); + for row in fr { + let mut r = String::new(); + for col in row { + r.push(col); + } + string.push_str(&r); + string.push('\n') + }; + string + }); + + + loop { + println!("{}", io::stdchar().await) + } + + Ok(()) + } +} + +fn random() -> u64 { + let mut r = random::Random::int(0, 125) as u64; + r +} + + + + + + + + + + + + + + + diff --git a/src/applications/crystalfetch.rs b/src/applications/crystalfetch.rs new file mode 100644 index 0000000..c0a805d --- /dev/null +++ b/src/applications/crystalfetch.rs @@ -0,0 +1,63 @@ +use async_trait::async_trait; +use alloc::{boxed::Box, string::String, vec::Vec}; + +use crate::{ + kernel::{ + os::OS, + render::{Color, write} + }, + println, + std::application::{ + Application, + Error, + }, +}; + + +pub struct CrystalFetch {} + +#[async_trait] +impl Application for CrystalFetch { + + fn new() -> Self { + Self {} + } + + async fn run(&mut self, _args: Vec) -> Result<(), Error> { + + let os = OS.lock().os.clone(); + let version = OS.lock().version.clone(); + + + write(format_args!(" +──────────────────────────────────────────────────────── + _____ _ _ ____ _____ + / ____| | | | |/ __ \\ / ____| + | | _ __ _ _ ___| |_ __ _| | | | | (___ + | | | '__| | | / __| __/ _` | | | | |\\___ \\ + | |____| | | |_| \\__ \\ || (_| | | |__| |____) | + \\_____|_| \\__, |___/\\__\\__,_|_|\\____/|_____/ + __/ | + |___/ +"), (Color::Magenta, Color::Black)); + + println!(" + ╔═══════════════════════════════ + ║ + ║ OS » {} + ║ BUILD » {} + ║ RAM » idk + ║ Shell » CrystalSH + ║ API » CrystalAPI + ║ Pkgs » 4 + ║ Fetch » CrystalFetch + ║ + ╚═══════════════════════════════ + +──────────────────────────────────────────────────────── +", os, version); + Ok(()) + + } + +} diff --git a/src/applications/shell_rewrite.rs b/src/applications/shell_rewrite.rs new file mode 100644 index 0000000..c109b64 --- /dev/null +++ b/src/applications/shell_rewrite.rs @@ -0,0 +1,33 @@ + +/* + + [ Cry-SH ] + +CrystalOS shell rewrite to replace the original shell implementation +this shell should support: + - running basic commands + - a prompt that displays the status of the last command + - customised error messages returned from applications + - invoking any application with arguments + - cycling through previous commands with arrow keys + - parsing of basic mathematical expressions using the calc module + - chained commands using the '|' or pipe operator which sends the output + of one command to the next +*/ + +// import necessary modules + +use async_trait::async_trait; +use lazy_static::lazy_static; +use spin::Mutex; +use x86_64::instructions::interrupts; + +use alloc::{string::{String, ToString}, vec::Vec, boxed::Box}; + +use crate::{ + kernel::tasks::keyboard::KEYBOARD, + std::application::{Error, Application} + std::io::{println, print}; +}; + +use super::* diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f1e1303 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,109 @@ + +#![no_std] + +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] +#![feature(abi_x86_interrupt)] +#![feature(alloc_error_handler)] +#![feature(async_fn_in_trait)] +#![feature(global_asm)] + +use core::panic::PanicInfo; +use spin::Mutex; + +pub mod system; +pub mod user; + +pub use system::kernel as kernel; +pub use system::std as std; +pub use user::bin::*; + +extern crate alloc; +//extern crate fatfs; + +#[cfg(test)] +use bootloader::{entry_point, BootInfo}; + + +#[cfg(test)] +entry_point!(test_kernel_main); + +#[alloc_error_handler] +fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! { + panic!("error while allocating: {:?}", layout) +} + + + +pub fn init() { + system::init(); +} + +pub fn hlt() -> ! { + loop { + x86_64::instructions::hlt(); + } +} + +pub trait Testable { + fn run(&self) -> (); +} + +impl Testable for T where T: Fn(), { + fn run(&self) { + serial_print!("{}...\t", core::any::type_name::()); + self(); + serial_println!("OK"); + } +} + +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit(QemuExitCode::Ok); +} + +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("ERR"); + serial_println!("Error: {}\n", info); + exit(QemuExitCode::Err); + hlt(); +} + +#[cfg(test)] +fn test_kernel_main(_boot_info: &'static BootInfo) -> ! { + init(); + test_main(); + hlt(); +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Ok = 0x10, + Err = 0x11, +} + +pub fn poweroff() { + exit(QemuExitCode::Ok); +} + +pub fn exit(code: QemuExitCode) { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xf4); + port.write(code as u32); + } + println!("e"); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9574e8a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,59 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(CrystalOS::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use CrystalOS::{println, print, println_log, print_log}; +use CrystalOS::kernel::tasks::{Task, executor::Executor, keyboard}; +use bootloader::{BootInfo, entry_point}; +extern crate alloc; +use alloc::{boxed::Box, vec, vec::Vec, rc::Rc, string, string::String}; +use CrystalOS::user::bin::shell; + +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + println!("{}", _info); + CrystalOS::hlt(); +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + CrystalOS::test_panic_handler(info) +} + +entry_point!(main); + + +fn main(boot_info: &'static BootInfo) -> ! { + use CrystalOS::kernel::allocator; + use CrystalOS::kernel::memory; + use CrystalOS::kernel::memory::BootInfoFrameAllocator; + use x86_64::{structures::paging::{Page, Translate}, VirtAddr}; + + CrystalOS::init(); + + let physical_memory_offset = VirtAddr::new(boot_info.physical_memory_offset); + let mut mapper = unsafe { memory::init(physical_memory_offset) }; + let mut frame_allocator = unsafe { + BootInfoFrameAllocator::init(&boot_info.memory_map) + }; + + allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialisation failed"); + + let mut executor = Executor::new(); + + executor.spawn(Task::new(shell::command_handler())); + + executor.run(); + + #[cfg(test)] + test_main(); + + loop {} +} + + diff --git a/src/system/kernel/allocator.rs b/src/system/kernel/allocator.rs new file mode 100644 index 0000000..589d2f6 --- /dev/null +++ b/src/system/kernel/allocator.rs @@ -0,0 +1,63 @@ + +use alloc::alloc::{GlobalAlloc, Layout}; +use core::ptr::null_mut; +use linked_list_allocator::LockedHeap; + + +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, + }, + VirtAddr, +}; + +pub fn init_heap( + mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator +) -> Result<(), MapToError> { + let page_range = { + let heap_start = VirtAddr::new(HEAP_START as u64); + let heap_end = heap_start + HEAP_SIZE - 1u64; + let heap_start_page = Page::containing_address(heap_start); + let heap_end_page = Page::containing_address(heap_end); + Page::range_inclusive(heap_start_page, heap_end_page) + }; + + for page in page_range { + let frame = frame_allocator.allocate_frame() + .ok_or(MapToError::FrameAllocationFailed)?; + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + unsafe { + mapper.map_to(page, frame, flags, frame_allocator)?.flush() + }; + + unsafe { ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE); } + } + Ok(()) +} + + + + + + + + + +pub struct Dummy; + +unsafe impl GlobalAlloc for Dummy { + unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { + null_mut() + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + panic!("this method should not be used") + } +} + +#[global_allocator] +static ALLOCATOR: LockedHeap = LockedHeap::empty(); + +pub const HEAP_START: usize = 0x_4444_4444_0000; +pub const HEAP_SIZE: usize = 100 * 1024; diff --git a/src/system/kernel/fs/mod.rs b/src/system/kernel/fs/mod.rs new file mode 100644 index 0000000..1811993 --- /dev/null +++ b/src/system/kernel/fs/mod.rs @@ -0,0 +1,160 @@ +use lazy_static::lazy_static; +use async_trait::async_trait; +use spin::Mutex; +use alloc::{vec::Vec, string::String, boxed::Box}; +use crate::std::{self, application::{Error, Application}}; + + +lazy_static! { + pub static ref FILESYSTEM: Mutex> = Mutex::new(Box::new(File::new( + String::from(""), // root + FileType::Dir(Directory::new()), + ))); +} + +pub type Directory = Vec>; + +#[derive(Debug)] +pub struct File { + pub name: String, + pub data: FileType, +} +impl File { + pub fn new(name: String, data: FileType) -> Self { + Self { name, data } + } +} + +#[derive(Debug)] +pub enum FileType { + Dir(Directory), + Txt(String), + Exe(Apppp), +} + + + + +pub fn mkfs() { + let mut fs = FILESYSTEM.lock(); + + match fs.data { + FileType::Dir(ref mut dir) => { + dir.push(Box::new( + File::new( + String::from("hello there"), + FileType::Txt(String::from("this is a basic text file")), + ) + )); + dir.push(Box::new( + File::new( + String::from("function that prints out an integer"), + FileType::Exe(Apppp::new()), + ) + )); + } + _ => { + () + } + } +} + + +#[derive(Debug)] +pub struct Apppp {} + +#[async_trait] +impl std::application::Application for Apppp { + fn new() -> Self { + Self {} + } + async fn run(&mut self, _: Vec) -> Result<(), Error> { + Ok(()) + } +} + +/* +lazy_static! { + pub static ref FILESYSTEM: Mutex = Mutex::new(Filesystem::new()); +} + +enum FsError { + FileNotFound, + AlreadyExists, + InvalidPath, +} + +pub type Path = String; + +impl Path { + pub fn new(path: &str) -> Result { + if path.is_empty() { + return Err(FsError::FileNotFound); + } + Ok(path.to_string()) + } + pub fn dirs(&self) -> Vec { + self.split('/').map(|s| s.to_string()).collect() + } +} + + +pub struct Filesystem { + pub root: Vec +} + + + +pub type Directory = Vec; + +impl Directory { + pub fn new(name: String, files: Vec) -> Self { + Self { name, files } + } + pub fn size(&self) -> usize { + self.files.len() + } + pub fn mkdir(&mut self, name: &str, location: Path) -> Result<(), FsError> { + if self.exists(location.as_str()) { + return Err(FsError::AlreadyExists); + } + self.files.push(File::new(name, FileType::Directory(Box::new(Directory::new())))) + } +} + + +pub enum FileType { + Directory(Box), + TextFile(Box), + BinaryFile(Box), + Executable(fn()), +} + + +pub struct File { + pub name: String, + pub file_type: FileType, +} +impl File { + pub fn new(name: String, file_type: FileType) -> Self { + Self { name, file_type } + } +} + + + +pub struct TextFile { + pub name: String, + pub data: String, +} +impl TextFile { + pub fn new(name: String, data: String) -> Self { + Self { name, data } + } + pub fn size(&self) -> usize { + self.data.len() + } +} + + +*/ \ No newline at end of file diff --git a/src/system/kernel/gdt.rs b/src/system/kernel/gdt.rs new file mode 100644 index 0000000..635af5f --- /dev/null +++ b/src/system/kernel/gdt.rs @@ -0,0 +1,50 @@ + +use x86_64::VirtAddr; +use x86_64::structures::tss::TaskStateSegment; +use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor, SegmentSelector}; +use lazy_static::lazy_static; + +pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; + +lazy_static! { + static ref TSS: TaskStateSegment = { + let mut tss = TaskStateSegment::new(); + tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { + const STACK_SIZE: usize = 4096 * 5; + static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; + + let stack_start = VirtAddr::from_ptr(unsafe{ &STACK }); + let stack_end = stack_start + STACK_SIZE; + stack_end + }; + tss + }; +} + +lazy_static! { + static ref GDT: (GlobalDescriptorTable, Selectors) = { + let mut gdt = GlobalDescriptorTable::new(); + let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); + let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); + (gdt, Selectors { code_selector, tss_selector }) + }; +} + +pub fn init() { + + use x86_64::instructions::tables::load_tss; + use x86_64::instructions::segmentation::{CS, Segment}; + + GDT.0.load(); + + unsafe { + CS::set_reg(GDT.1.code_selector); + load_tss(GDT.1.tss_selector); + } + +} + +struct Selectors { + code_selector: SegmentSelector, + tss_selector: SegmentSelector, +} diff --git a/src/system/kernel/interrupts.rs b/src/system/kernel/interrupts.rs new file mode 100644 index 0000000..9d5de70 --- /dev/null +++ b/src/system/kernel/interrupts.rs @@ -0,0 +1,110 @@ + +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use crate::{print, println}; +use crate::kernel::gdt; +use lazy_static::lazy_static; +use spin; +use pic8259::ChainedPics; + + +pub fn init_idt() { + IDT.load(); +} + +extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + println!("EXCEPTION: breakpoint\n{:#?}", stack_frame); +} + +extern "x86-interrupt" fn double_fault_handler(stack_frame: InterruptStackFrame, _error_code: u64) -> ! { + panic!("EXCEPTION: double fault\n{:#?}", stack_frame) +} + +extern "x86-interrupt" fn timer_interrupt_handler(stack_frame: InterruptStackFrame) { + unsafe { + GLOBALTIMER.lock().inc(); + PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); + } +} + +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { + + use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; + use spin::Mutex; + use x86_64::instructions::port::Port; + + lazy_static! { + static ref KEYBOARD: Mutex> = { + Mutex::new(Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore)) + }; + } + + let mut keyboard = KEYBOARD.lock(); + let mut port = Port::new(0x60); + let scancode: u8 = unsafe { port.read() }; + + crate::kernel::tasks::keyboard::add_scancode(scancode); + + unsafe { + PICS.lock().notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); + } +} + + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + unsafe { + idt.double_fault.set_handler_fn(double_fault_handler) + .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); + } + idt[InterruptIndex::Timer.as_usize()].set_handler_fn(timer_interrupt_handler); + idt[InterruptIndex::Keyboard.as_usize()].set_handler_fn(keyboard_interrupt_handler); + idt + }; +} + +lazy_static! { + pub static ref GLOBALTIMER: spin::Mutex = spin::Mutex::new(Timer::new()); +} + +pub struct Timer { + pub val: i64 +} + +impl Timer { + pub fn new() -> Self { + Self { val: 0 } + } + pub fn inc(&mut self) { + self.val += 1 + } + pub fn clear(&mut self) { + self.val = 0 + } +} + + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +pub static PICS: spin::Mutex = spin::Mutex::new( unsafe { + ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) +}); + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum InterruptIndex { + Timer = PIC_1_OFFSET, + Keyboard, +} + +impl InterruptIndex { + fn as_u8(self) -> u8 { + self as u8 + } + + fn as_usize(self) -> usize { + usize::from(self.as_u8()) + } +} diff --git a/src/system/kernel/memory.rs b/src/system/kernel/memory.rs new file mode 100644 index 0000000..74c4ffc --- /dev/null +++ b/src/system/kernel/memory.rs @@ -0,0 +1,140 @@ + +use x86_64::{ + structures::paging::{Page, PhysFrame, Mapper, Size4KiB, FrameAllocator, PageTable}, + VirtAddr, + PhysAddr +}; +use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; +use x86_64::structures::paging::OffsetPageTable; + +unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { + + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + let phys = level_4_table_frame.start_address(); + let virt = physical_memory_offset + phys.as_u64(); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr +} + +pub unsafe fn init(physical_memory_offset: VirtAddr) -> OffsetPageTable<'static> { + let level_4_table = active_level_4_table(physical_memory_offset); + OffsetPageTable::new(level_4_table, physical_memory_offset) +} + + +pub struct BootInfoFrameAllocator { + memory_map: &'static MemoryMap, + next: usize, +} + +impl BootInfoFrameAllocator { + pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { + BootInfoFrameAllocator { + memory_map, + next: 0, + } + } + + fn usable_frames(&self) -> impl Iterator { + let regions = self.memory_map.iter(); + let usable_regions = regions.filter(|r| + r.region_type == MemoryRegionType::Usable + ); + + let address_ranges = usable_regions.map(|r| + r.range.start_addr()..r.range.end_addr() + ); + + let frame_addresses = address_ranges.flat_map(|r| r.step_by(4096)); + + frame_addresses.map(|a| PhysFrame::containing_address(PhysAddr::new(a))) + } + +} + + +pub struct EmptyFrameAllocator; + +unsafe impl FrameAllocator for EmptyFrameAllocator { + fn allocate_frame(&mut self) -> Option { + None + } + } + +unsafe impl FrameAllocator for BootInfoFrameAllocator { + fn allocate_frame(&mut self) -> Option { + let frame = self.usable_frames().nth(self.next); + self.next += 1; + frame + } +} + +/* + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct StackBounds { + start: VirtAddr, + end: VirtAddr, +} + +impl StackBounds { + pub fn start(&self) -> VirtAddr { + self.start + } + + pub fn end(&self) -> VirtAddr { + self.end + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct ThreadId(u64); + +impl ThreadId { + pub fn as_u64(&self) -> u64 { + self.0 + } + + fn new() -> Self { + use core::sync::atomic::{AtomicU64, Ordering}; + static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1); + ThreadId(NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed)) + } +} + +fn reserve_stack_memory(size_in_pages: u64) -> Page { + use core::sync::atomic::{AtomicU64, Ordering}; + + static STACK_ALLOC_NEXT: AtomicU64 = AtomicU64::new(0x_5555_5555_0000); + let start_addr = VirtAddr::new(STACK_ALLOC_NEXT.fetch_add( + size_in_pages * Page::::SIZE, + Ordering::Relaxed, + )); + Page::from_start_address(start_addr).expect("STACK_ALLOC_NEXT: not page aligned") +} + +pub fn alloc_stack( + size_in_pages: u64, mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator +) -> Result { + use x86_64::structures::paging::PageTableFlags as Flags; + + let guard_page = reserve_stack_memory(size_in_pages + 1); + let stack_start = guard_page + 1; + let stack_end = stack_start + size_in_pages; + + for page in Page::range(stack_start, stack_end) { + let frame = frame_allocator.allocate_frame().ok_or(mapper.MapToError::FrameAllocatorFailed)?; + let flags = Flags::PRESENT | Flags::WRITABLE; + mapper.map_to(page, frame, flags, frame_allocator)?.flush(); + } + + Ok(StackBounds { + start: stack_start.start_address(), + end: stack_end.start_address(), + }) +} +*/ diff --git a/src/system/kernel/mod.rs b/src/system/kernel/mod.rs new file mode 100644 index 0000000..9949c33 --- /dev/null +++ b/src/system/kernel/mod.rs @@ -0,0 +1,8 @@ +pub mod allocator; +pub mod fs; +pub mod gdt; +pub mod interrupts; +pub mod memory; +pub mod render; +pub mod serial; +pub mod tasks; \ No newline at end of file diff --git a/src/system/kernel/os.rs b/src/system/kernel/os.rs new file mode 100644 index 0000000..dba84c0 --- /dev/null +++ b/src/system/kernel/os.rs @@ -0,0 +1,16 @@ +use lazy_static::lazy_static; +use spin::Mutex; +use alloc::{string::String}; + +lazy_static! { + pub static ref OS: Mutex = Mutex::new(SysInfo { + os: String::from("CrystalOS Alpha"), + version: String::from("0.2.1"), + }); +} + +pub struct SysInfo { + pub os: String, + pub version: String, +} + diff --git a/src/system/kernel/render.rs b/src/system/kernel/render.rs new file mode 100644 index 0000000..ddf2886 --- /dev/null +++ b/src/system/kernel/render.rs @@ -0,0 +1,286 @@ + +use volatile::Volatile; +use lazy_static::lazy_static; +use core::fmt; +use spin::Mutex; + +use alloc::vec::Vec; +use alloc::vec; +use alloc::borrow::ToOwned; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ColorCode(u8); + +impl ColorCode { + pub fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 5 | (foreground as u8)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + character: u8, + colour: ColorCode, +} + +pub const BUFFER_HEIGHT: usize = 25; +pub const BUFFER_WIDTH: usize = 80; + +#[repr(transparent)] +struct Buffer { + chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +struct BufferSwap { + chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT], +} +struct CharGrid { + chars: Vec<[ScreenChar; BUFFER_WIDTH]> +} + +pub struct Renderer { + col_pos: usize, + pub col_code: ColorCode, + buffer: &'static mut Buffer, + userspace: BufferSwap, + upwards: CharGrid, + downwards: CharGrid, + pub sandbox: bool, +} + + +lazy_static! { + pub static ref RENDERER: Mutex = Mutex::new(Renderer { + col_pos: 0, + col_code: ColorCode::new(Color::White, Color::Black), + buffer: unsafe { + &mut *(0xb8000 as *mut Buffer) + }, + userspace: BufferSwap { + chars: [[ScreenChar { + character: 179u8, + colour: ColorCode::new(Color::White, Color::Black), + }; BUFFER_WIDTH]; BUFFER_HEIGHT] + }, + upwards: CharGrid { + chars: vec![ + [ScreenChar { + character: 32u8, + colour: ColorCode::new(Color::White, Color::Black), + }; 80] + ] + }, + downwards: CharGrid { + chars: vec![ + [ScreenChar { + character: 32u8, + colour: ColorCode::new(Color::White, Color::Black), + }; 80] + ] + }, + sandbox: false, + }); +} + + + +impl Renderer { + + pub fn text_mode(&mut self) -> Result<(), ()> { + if !self.sandbox { return Err(()) }; + self.buffer_swap().unwrap(); + self.sandbox = false; + Ok(()) + } + + pub fn sandbox_mode(&mut self) -> Result<(), ()> { + if self.sandbox { return Err(()) }; + self.buffer_swap().unwrap(); + self.sandbox = true; + Ok(()) + } + + fn buffer_swap(&mut self) -> Result<(), ()> { + + for (i, _) in self.userspace.chars.clone().iter().enumerate() { + + let tmp = self.buffer.chars[i].clone(); + + for (j, col) in self.userspace.chars[i].clone().iter().enumerate() { + self.buffer.chars[i][j].write(col.to_owned()) + } + + for (j, _) in tmp.iter().enumerate() { + self.userspace.chars[i][j] = tmp[j].read().to_owned() + } + } + + Ok(()) + } + + pub fn render_frame(&mut self, frame: [ [ char; BUFFER_WIDTH ]; BUFFER_HEIGHT]) { + for (i, row) in frame.iter().enumerate() { + for (j, col) in row.iter().enumerate() { + + if let Some(c) = self.fancy_char(*col) { + self.buffer.chars[i][j].write(ScreenChar { character: c, colour: self.col_code}); + } else { + self.buffer.chars[i][j].write(ScreenChar { character: *col as u8, colour: self.col_code}); + } + } + } + } + + + pub fn write_string(&mut self, string: &str) { + for ch in string.chars() { + + if let Some(x) = self.fancy_char(ch) { + self.write_byte(x) + } else { + match ch as u8 { + 0x20..=0xff | b'\n' => self.write_byte(ch as u8), + _ => self.write_byte(0xfe), + } + } + } + } + + fn fancy_char(&self, ch: char) -> Option { + let res: u8 = match ch { + '│' => 179, + '─' => 196, + '┴' => 193, + '┤' => 180, + '═' => 205, + '║' => 186, + '╗' => 187, + '╝' => 188, + '╚' => 200, + '╔' => 201, + '»' => 175, + '┐' => 191, + '└' => 192, + '┘' => 217, + '┌' => 218, + _ => { return None; } + }; + Some(res) + } + + pub fn backspace(&mut self) -> Result<(), ()> { + if self.col_pos == 0 { + self.undonewline(); + } + self.col_pos -= 1; + let row = BUFFER_HEIGHT -1; + let col = self.col_pos; + + let blank = ScreenChar { + character: b' ', + colour: self.col_code, + }; + self.buffer.chars[row][col].write(blank); + Ok(()) + } + + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => { + self.newline() + }, + byte => { + if self.col_pos >= BUFFER_WIDTH { + self.newline(); + } + let row = BUFFER_HEIGHT -1; + let col = self.col_pos; + let col_code = self.col_code; + self.buffer.chars[row][col].write(ScreenChar { + character: byte, + colour: col_code, + }); + self.col_pos += 1 + } + } + } + fn newline(&mut self) { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(BUFFER_HEIGHT -1); + self.col_pos = 0; + } + + pub fn undonewline(&mut self) { + for row in (0..BUFFER_HEIGHT-1).rev() { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row + 1][col].write(character); + } + } + self.clear_row(0); + self.col_pos = BUFFER_WIDTH; + } + pub fn clear(&mut self) { + for row in (0..BUFFER_HEIGHT-1).rev() { + self.clear_row(row); + } + } + + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + character: b' ', + colour: self.col_code, + }; + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } +} + +impl fmt::Write for Renderer { + fn write_str(&mut self, string:&str) -> fmt::Result { + self.write_string(string); + Ok(()) + } +} + +pub fn write(args: fmt::Arguments, cols: (Color, Color)) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + interrupts::without_interrupts(|| { + let mut writer = RENDERER.lock(); + writer.col_code = ColorCode::new(cols.0, cols.1); + writer.write_fmt(args).unwrap() + }) +} + + diff --git a/src/system/kernel/serial.rs b/src/system/kernel/serial.rs new file mode 100644 index 0000000..de56cfd --- /dev/null +++ b/src/system/kernel/serial.rs @@ -0,0 +1,46 @@ + +use uart_16550::SerialPort; +use spin::Mutex; +use lazy_static::lazy_static; + + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { + SerialPort::new(0x3F8) + }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[doc(hidden)] +pub fn _print(args: core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + SERIAL1.lock().write_fmt(args).expect("unable to print to serial!") + }) + +} + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::kernel::serial::_print(format_args!($($arg)*)); + }; +} + +#[macro_export] +macro_rules! serial_println { + () => (serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ( + $crate::serial_print!( + concat!($fmt, "\n"), $($arg)* + ) + ); +} + + diff --git a/src/system/kernel/sysinit.rs b/src/system/kernel/sysinit.rs new file mode 100644 index 0000000..fa32c58 --- /dev/null +++ b/src/system/kernel/sysinit.rs @@ -0,0 +1,3 @@ +fn init() -> Result<(), ()> { + Ok(()) +} \ No newline at end of file diff --git a/src/system/kernel/tasks/executor.rs b/src/system/kernel/tasks/executor.rs new file mode 100644 index 0000000..d1c3ee7 --- /dev/null +++ b/src/system/kernel/tasks/executor.rs @@ -0,0 +1,106 @@ + +use super::{Task, TaskId}; +use alloc::{collections::BTreeMap, sync::Arc, task::Wake}; +use core::task::{Waker, Context, Poll}; +use crossbeam_queue::ArrayQueue; + +pub struct Executor { + tasks: BTreeMap, + task_queue: Arc>, + waker_cache: BTreeMap, +} + +impl Executor { + pub fn new() -> Self { + Executor { + tasks: BTreeMap::new(), + task_queue: Arc::new(ArrayQueue::new(100)), + waker_cache: BTreeMap::new(), + } + } + + pub fn spawn(&mut self, task: Task) { + let task_id = task.id; + if self.tasks.insert(task.id, task).is_some() { + panic!("a task with this id has already been allocated"); + } + self.task_queue.push(task_id).expect("task queue is full"); + } + + fn run_ready_tasks(&mut self) { + let Self { + tasks, task_queue, waker_cache + } = self; + + while let Ok(task_id) = task_queue.pop() { + let task = match tasks.get_mut(&task_id) { + Some(task) => task, + None => continue, + }; + let waker = waker_cache.entry(task_id) + .or_insert_with(|| TaskWaker::new(task_id, task_queue.clone())); + let mut context = Context::from_waker(waker); + match task.poll(&mut context) { + Poll::Ready(()) => { + tasks.remove(&task_id); + waker_cache.remove(&task_id); + } + Poll::Pending => {} + } + } + } + + pub fn run(&mut self) -> ! { + loop { + self.run_ready_tasks(); + self.sleep_if_idle(); + } + } + + fn sleep_if_idle(&self) { + + use x86_64::instructions::interrupts::{self, enable_and_hlt}; + + interrupts::disable(); + if self.task_queue.is_empty() { + enable_and_hlt(); + } else { + interrupts::enable(); + } + } +} + + + + + + + + +struct TaskWaker { + task_id: TaskId, + task_queue: Arc>, +} + +impl TaskWaker { + fn new(task_id: TaskId, task_queue: Arc>) -> Waker { + Waker::from(Arc::new(TaskWaker { + task_id, + task_queue, + })) + } + + + fn wake_task(&self) { + self.task_queue.push(self.task_id).expect("task queue is full") + } +} + +impl Wake for TaskWaker { + fn wake(self: Arc) { + self.wake_task(); + } + fn wake_by_ref(self: &Arc) { + self.wake_task(); + } +} diff --git a/src/system/kernel/tasks/keyboard.rs b/src/system/kernel/tasks/keyboard.rs new file mode 100644 index 0000000..7936828 --- /dev/null +++ b/src/system/kernel/tasks/keyboard.rs @@ -0,0 +1,161 @@ +use lazy_static::lazy_static; +use spin::Mutex; +use x86_64::instructions::interrupts; + + +use conquer_once::spin::OnceCell; +use crossbeam_queue::ArrayQueue; +use crate::println; + +use core::{pin::Pin, task::{Poll, Context}}; +use futures_util::stream::Stream; +use futures_util::task::AtomicWaker; +use futures_util::stream::StreamExt; +use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; +use crate::print; +use crate::kernel::render::RENDERER; +use alloc::{string::String}; + +static WAKER: AtomicWaker = AtomicWaker::new(); +static SCANCODE_QUEUE: OnceCell> = OnceCell::uninit(); + + +/* +pub async fn print_keypresses() { + let mut scancodes = ScanCodeStream::new(); + let mut keyboard = Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore); + while let Some(scancode) = scancodes.next().await { + if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { + if let Some(key) = keyboard.process_keyevent(key_event) { + match key { + DecodedKey::Unicode(character) => { + let mut cmd = CMD.lock(); + cmd.input(character).await; + } + DecodedKey::RawKey(key) => print!("{:?}", key), + } + } + } + } +} +*/ + +lazy_static! { + pub static ref KEYBOARD: Mutex = Mutex::new(KeyboardHandler::new()); +} + +pub struct KeyboardHandler { + scancodes: ScanCodeStream, + keyboard: Keyboard, +} + +impl KeyboardHandler { + pub fn new() -> KeyboardHandler { + KeyboardHandler { + scancodes: ScanCodeStream::new(), + keyboard: Keyboard::new(layouts::Uk105Key, ScancodeSet1, HandleControl::Ignore), + } + } + + pub async fn get_keystroke_inner(&mut self) -> Option { + loop { + if let Some(scancode) = self.scancodes.next().await { + if let Ok(Some(key_event)) = self.keyboard.add_byte(scancode) { + if let Some(key) = self.keyboard.process_keyevent(key_event) { + match key { + DecodedKey::Unicode(character) => { + if character == b'\x08' as char { // checks if the character is a backspace + interrupts::without_interrupts(|| { + RENDERER.lock().backspace(); // runs the backspace function of the vga buffer to remove the last character + }); + return None; + } else { + return Some(character); + } + }, + DecodedKey::RawKey(key) => { print!("{:?}", key) }, + } + } + } + } + } + } + + pub async fn get_keystroke(&mut self) -> char { + loop { + match self.get_keystroke_inner().await { + Some(c) => return c, + None => () + } + } + } + + + pub async fn get_string(&mut self) -> String { + let mut val = String::new(); + loop { + let character = match self.get_keystroke_inner().await { + Some(c) => { c }, + None => { val.pop(); continue; }, + }; + print!("{}", character); + let (character, execute): (char, bool) = match character { + '\n' => (character, true), + _ => (character, false), + }; + val.push(character); + if execute { + return val; + } + } + + } + +} + +pub(crate) fn add_scancode(scancode: u8) { + if let Ok(queue) = SCANCODE_QUEUE.try_get() { + if let Err(_) = queue.push(scancode) { + println!("WARNING: queue is full - ignoring input"); + } else { + WAKER.wake(); + } + } else { + println!("WARNING: scancode queue has not been initialised"); + } +} + + +pub struct ScanCodeStream { + _private: (), +} + +impl ScanCodeStream { + pub fn new() -> Self { + SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100)) + .expect("ScanCodeStream::new has already been called once"); + ScanCodeStream { _private: () } + } +} + +impl Stream for ScanCodeStream { + type Item = u8; + + fn poll_next(self: Pin<&mut Self>, ctx: &mut Context) -> Poll> { + let queue = SCANCODE_QUEUE.try_get().expect("not initialised"); + + if let Ok(scancode) = queue.pop() { + return Poll::Ready(Some(scancode)); + } + + WAKER.register(&ctx.waker()); + + match queue.pop() { + Ok(scancode) => { + WAKER.take(); + Poll::Ready(Some(scancode)) + }, + Err(crossbeam_queue::PopError) => Poll::Pending, + } + } +} diff --git a/src/system/kernel/tasks/mod.rs b/src/system/kernel/tasks/mod.rs new file mode 100644 index 0000000..3e6b058 --- /dev/null +++ b/src/system/kernel/tasks/mod.rs @@ -0,0 +1,36 @@ + +use core::{future::Future, pin::Pin}; +use alloc::boxed::Box; +use core::task::{Context, Poll}; +pub mod executor; +pub mod keyboard; +use core::sync::atomic::{AtomicU64, Ordering}; + + +pub struct Task { + id: TaskId, + future: Pin>>, +} + +impl Task { + pub fn new(future: impl Future + 'static) -> Self { + Self { + id: TaskId::new(), + future: Box::pin(future), + } + } + + fn poll(&mut self, context: &mut Context) -> Poll<()> { + self.future.as_mut().poll(context) + } + } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +struct TaskId(u64); + +impl TaskId { + fn new() -> Self { + static NEXT_ID: AtomicU64 = AtomicU64::new(0); + TaskId(NEXT_ID.fetch_add(1, Ordering::Relaxed)) + } +} diff --git a/src/system/kernel/threading/mod.rs b/src/system/kernel/threading/mod.rs new file mode 100644 index 0000000..dcfd0c1 --- /dev/null +++ b/src/system/kernel/threading/mod.rs @@ -0,0 +1,15 @@ +/* + +pub mod thread_switch; + +use x86_64::VirtAddr; +use crate::memory::{ThreadId, StackBounds}; + + +#[derive(Debug)] +pub struct Thread { + id: ThreadId, + stack_pointer: Option, + stack_bounds: Option, +} +*/ diff --git a/src/system/kernel/threading/thread_switch.rs b/src/system/kernel/threading/thread_switch.rs new file mode 100644 index 0000000..12a5a6d --- /dev/null +++ b/src/system/kernel/threading/thread_switch.rs @@ -0,0 +1,3 @@ +use core::arch::global_asm; + +global_asm!(include_str!("thread_switch.s")); diff --git a/src/system/kernel/threading/thread_switch.s b/src/system/kernel/threading/thread_switch.s new file mode 100644 index 0000000..06877f8 --- /dev/null +++ b/src/system/kernel/threading/thread_switch.s @@ -0,0 +1,13 @@ + + +asm_thread_switch: + pushfq + + mov rax, rsp + mov rsp, rdi + + mov rdi, rax + call add_paused_thread + + popfq + ret diff --git a/src/system/mod.rs b/src/system/mod.rs new file mode 100644 index 0000000..d8f9d4b --- /dev/null +++ b/src/system/mod.rs @@ -0,0 +1,9 @@ +pub mod std; +pub mod kernel; + +pub fn init() { + kernel::gdt::init(); + kernel::interrupts::init_idt(); + unsafe { kernel::interrupts::PICS.lock().initialize() }; + x86_64::instructions::interrupts::enable(); +} \ No newline at end of file diff --git a/src/system/std/application.rs b/src/system/std/application.rs new file mode 100644 index 0000000..12fa3ce --- /dev/null +++ b/src/system/std/application.rs @@ -0,0 +1,19 @@ +use async_trait::async_trait; +use alloc::{string::String, vec::Vec, boxed::Box}; + +#[async_trait] +pub trait Application { + fn new() -> Self; + + async fn run(&mut self, _: Vec) -> Result<(), Error> { + Ok(()) + } +} + +#[derive(Debug)] +pub enum Error { + UnknownCommand(String), + CommandFailed(String), + EmptyCommand, +} + diff --git a/src/system/std/io.rs b/src/system/std/io.rs new file mode 100644 index 0000000..7cc2532 --- /dev/null +++ b/src/system/std/io.rs @@ -0,0 +1,210 @@ +use crate::{ + kernel::render::{RENDERER, BUFFER_WIDTH, BUFFER_HEIGHT, ColorCode}, + kernel::tasks::keyboard::KEYBOARD, +}; + + +use alloc::{boxed::Box, string::{String, ToString}, vec::Vec}; + +pub use crate::{print, println, serial_print, serial_println}; + +use lazy_static::lazy_static; +use spin::Mutex; + +pub async fn stdin() -> String { + let string = KEYBOARD.lock().get_string().await; + string +} + +pub async fn stdchar() -> char { + let chr = KEYBOARD.lock().get_keystroke().await; + chr +} + +pub fn text_mode() { + RENDERER.lock().text_mode().unwrap(); +} + +pub fn sandbox_mode() { + RENDERER.lock().sandbox_mode().unwrap(); +} + +pub fn switch_mode() { + if RENDERER.lock().sandbox == true { + RENDERER.lock().text_mode().unwrap(); + } else { + RENDERER.lock().sandbox_mode().unwrap(); + } +} + +pub fn clear() { + RENDERER.lock().clear(); +} + + + + + +pub type Frame = [ [ char; BUFFER_WIDTH ]; BUFFER_HEIGHT]; + +#[derive(Clone)] +pub struct Element { + frame: Vec>, + dimensions: (u8, u8) +} +/// elements can be created using their from_str() method +/// you can then render the element to the current frame using the render() method +/// the position of the element by passing a tuple (x,y) to render() +/// +/// nothing will appear on the screen until the frame is actually +impl Element { + pub fn from_str(elemstr: String) -> Self { + let mut element = Element { frame: Vec::>::new(), dimensions: (0, 0) }; + + for line in elemstr.split("\n") { + let mut ln = Vec::::new(); + for col in line.chars() { + ln.push(col) + }; + element.frame.push(ln); + } + + for row in element.clone().frame { + let n = row.len(); + if n > element.dimensions.0 as usize { + element.dimensions.0 = n as u8; + } + } + element + } + + pub fn render(&mut self, pos: (u8, u8)) { // x,y + for (i, row) in self.frame.iter().enumerate() { + for (j, col) in row.iter().enumerate() { + println!("{} {} {}", i, j, col); + FRAMEGEN.lock().frame[i + pos.1 as usize][j + pos.0 as usize] = *col; + }; + } + } +} + + +lazy_static! { + pub static ref FRAMEGEN: Mutex = Mutex::new(FrameGen::new() ); +} + + +#[derive(Clone, Copy)] +pub struct FrameGen { + frame: Frame, +} + + +impl FrameGen { + pub fn render_frame(&self) { + RENDERER.lock().render_frame(self.frame) + } + + fn new() -> Self { + let mut frame: [[char; BUFFER_WIDTH]; BUFFER_HEIGHT] = [[' '; BUFFER_WIDTH]; BUFFER_HEIGHT]; + for i in 0..BUFFER_WIDTH { + frame[0][i] = "┌──────────────────────────────────────────────────────────────────────────────┐".chars().collect::>()[i]; + frame[BUFFER_HEIGHT -1][i] = "└──────────────────────────────────────────────────────────────────────────────┘".chars().collect::>()[i]; + } + + for j in 1..BUFFER_HEIGHT -1 { + for i in 0..BUFFER_WIDTH { + frame[j][i] = "│ │".chars().collect::>()[i]; + } + } + + Self { frame: Frame::from(frame) } + } + + pub fn get_frame(&self) -> &[ [ char; BUFFER_WIDTH ]; BUFFER_HEIGHT] { + &self.frame + } + +} + + +impl core::fmt::Display for FrameGen { + fn fmt(&self, _: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + println!(" "); + for row in &self.frame { + println!("{}", row.iter().collect::()); + }; + Ok(()) + } +} + + + + + +#[macro_export] +macro_rules! println_log { + () => ($crate::print_log!("/n")); + ($($arg:tt)*) => ($crate::print_log!("{}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! print_log { + ($($arg:tt)*) => ($crate::std::io::_log(format_args!($($arg)*))); +} + + +#[macro_export] +macro_rules! println { + () => ($crate::print!("/n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::std::io::_print(format_args!($($arg)*))); +} + +pub use crate::kernel::render::Color; + + +#[doc(hidden)] +pub fn _print(args: core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + let mut writer = RENDERER.lock(); + writer.col_code = ColorCode::new(Color::White, Color::Black); + writer.write_fmt(args).unwrap(); + + //WRITER.lock().write_fmt(args).unwrap(); + }); +} + +#[doc(hidden)] +pub fn _log(args: core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + let mut writer = RENDERER.lock(); + writer.col_code = ColorCode::new(Color::Yellow, Color::Black); + writer.write_fmt(args).unwrap(); + + //WRITER.lock().write_fmt(args).unwrap(); + }); +} + +pub fn write(args: core::fmt::Arguments, cols: (Color, Color)) { + crate::kernel::render::write(args, cols); +} + + + + +pub fn mkfs() { + use crate::kernel::fs; + fs::mkfs(); + println!("{:?}", *(fs::FILESYSTEM.lock())); +} \ No newline at end of file diff --git a/src/system/std/mod.rs b/src/system/std/mod.rs new file mode 100644 index 0000000..989f186 --- /dev/null +++ b/src/system/std/mod.rs @@ -0,0 +1,13 @@ +pub mod io; +pub mod random; +pub mod application; +pub mod tasks; +pub mod os; + + +// this is where the standard library for the operating system will be defined +// my aim is to completely separate this from the shell. + +// these functions should all be asynchronous. + + diff --git a/src/system/std/os.rs b/src/system/std/os.rs new file mode 100644 index 0000000..dba84c0 --- /dev/null +++ b/src/system/std/os.rs @@ -0,0 +1,16 @@ +use lazy_static::lazy_static; +use spin::Mutex; +use alloc::{string::String}; + +lazy_static! { + pub static ref OS: Mutex = Mutex::new(SysInfo { + os: String::from("CrystalOS Alpha"), + version: String::from("0.2.1"), + }); +} + +pub struct SysInfo { + pub os: String, + pub version: String, +} + diff --git a/src/system/std/random.rs b/src/system/std/random.rs new file mode 100644 index 0000000..199a57c --- /dev/null +++ b/src/system/std/random.rs @@ -0,0 +1,35 @@ + +use alloc::{boxed::Box, string::{String, ToString}, vec::Vec}; +use rand::{Rng, SeedableRng, rngs::SmallRng, RngCore}; +use spin::Mutex; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref RANDOM: Mutex = Mutex::new(SmallRng::seed_from_u64(1)); +} + + + + +pub struct Random; + +impl Random { + pub fn int(lower: usize, upper: usize) -> usize { + loop { + let integer: u64 = RANDOM.lock().next_u64(); + let mut integer: String = integer.to_string(); + integer = "0".repeat(20 - integer.len()) + &integer; + let integer: usize = integer[1..upper.to_string().len() + 1].parse().unwrap(); + if integer <= upper && integer >= lower { + return integer; + } else { + continue; + } + } + + } + pub fn selection(ls: Vec) -> T { + let range = Random::int(0, ls.len() - 1); + ls[range as usize].clone() + } +} \ No newline at end of file diff --git a/src/system/std/tasks.rs b/src/system/std/tasks.rs new file mode 100644 index 0000000..794b655 --- /dev/null +++ b/src/system/std/tasks.rs @@ -0,0 +1,7 @@ +pub use crate::kernel::tasks::{Task, executor::Executor}; + +pub fn stop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} \ No newline at end of file diff --git a/src/user/bin/calc.rs b/src/user/bin/calc.rs new file mode 100755 index 0000000..5790157 --- /dev/null +++ b/src/user/bin/calc.rs @@ -0,0 +1,582 @@ +use core::fmt; +use alloc::{boxed::Box, string::String, vec::Vec}; +use alloc::string::ToString; +use alloc::borrow::ToOwned; +use crate::{println, print, mknode, std}; + + +use async_trait::async_trait; +use crate::std::application::{ + Application, + Error as ShellError +}; + +struct Parser { + tokens: Vec, + idx: i32, + current: Token +} + + +struct Interpreter {} + + + +impl Interpreter { + + fn new() -> Result{ + return Ok(Self {}) + } + + fn visit(&mut self, node: Node) -> Result { + match node { + Node::BinaryOperation(_) => return self.visit_binary_operation(node), + Node::UnaryOperation(_) => return self.visit_unary_operation(node), + Node::Number(_) => return self.visit_number(node), + Node::Operator(_) => return self.visit_operator(node), + } + } + + + fn visit_number(&mut self, node: Node) -> Result { + + if let Node::Number(x) = node { + Ok(Value::Number(x)) + } else { + Err(Error::Other(String::from("value accessed was not an number"))) + } + } + + + fn visit_operator(&mut self, node: Node) -> Result { + + if let Node::Operator(x) = node { + Ok(Value::Operator(x)) + } else { + Err(Error::Other(String::from("value is not an operator"))) + } + + } + + + fn visit_binary_operation(&mut self, node: Node) -> Result { + + let left = match self.visit(self.get_node(node.clone(), "left")?.expect("returned none").to_owned())? { + Value::Number(x) => x, + _ => return Err(Error::Other(String::from("value is not a number"))), + }; + + let right = match self.visit(self.get_node(node.clone(), "right")?.expect("returned none").to_owned())? { + Value::Number(x) => x, + _ => return Err(Error::Other(String::from("value is not a number"))), + }; + + let operator = match self.visit(self.get_node(node.clone(), "operator")?.expect("returned none").to_owned())? { + Value::Operator(x) => x, + _ => return Err(Error::Other(String::from("value is not a binary operator"))), + }; + + match operator { + Operator::Add => { + return Ok(Value::Number(left + right)) + }, + Operator::Sub => { + return Ok(Value::Number(left - right)) + }, + Operator::Div => { + if right != 0.0 { + return Ok(Value::Number(left / right)) + } else { + return Err(Error::LogicalError(String::from("division by 0"))) + } + }, + Operator::Mod => { + return Ok(Value::Number(left % right)) + }, + Operator::Qot => { + if right != 0.0 { + return Ok(Value::Number( + ((left / right) as i64) as f64 + )) + } else { + return Err(Error::LogicalError(String::from("division by 0"))) + } + }, + Operator::Mul => { + return Ok(Value::Number(left * right)) + }, + Operator::Exp => { + return Ok(Value::Number({ + let mut val = 1.0; + for _ in 0..(right as i64) { + val *= left + }; + val + })) + } + } + + } + + + fn visit_unary_operation(&mut self, node: Node) -> Result { + + let other: f64 = match self.visit(self.get_node(node.clone(), "other")?.expect("returned none").to_owned())? { + Value::Number(x) => x, + _ => return Err(Error::LogicalError("value is not a number".to_string())) + }; + + if let Node::UnaryOperation(x) = node { + match x.operator { + Node::Operator(Operator::Sub) => { + return Ok(Value::Number(other * -1f64)); + }, + _ => return Err(Error::LogicalError("value is not an operator".to_string())) + } + } else { + return Err(Error::LogicalError("node is not a binary operator".to_string())) + } + } + + fn get_node(&self, node: Node, position: &str) -> Result, Error> { + let result = match position { + "left" => { + if let Node::BinaryOperation(x) = node { + Some(x.left) + } else { + None + } + } + "right" => { + if let Node::BinaryOperation(x) = node { + Some(x.right) + } else { + None + } + } + "other" => { + if let Node::UnaryOperation(x) = node { + Some(x.other) + } else { + None + } + } + "operator" => { + if let Node::BinaryOperation(x) = node { + Some(x.operator) + } else { + None + } + }, + _ => return Err(Error::Other(String::from("invalid param for get_Node"))) + }; + Ok(result) + + } + +} + + + + + + + + + + + +impl Parser { + fn new(tokens: Vec) -> Result { + let mut parser = Self { tokens, idx: -1, current: Token::Null }; + parser.advance()?; + Ok(parser) + + } + + fn parse(&mut self) -> Result { + let result = self.expr(); + result + } + + + + fn advance(&mut self) -> Result, Error> { + self.idx += 1; + if self.idx < self.tokens.len() as i32 { + self.current = self.tokens[self.idx as usize].clone(); + Ok(Some(self.current.clone())) + } else if self.idx == self.tokens.len() as i32 { + Ok(None) + } else { + Err(Error::Eof) + } + } + + fn atom(&mut self) -> Result { + let token = self.current.clone(); + + match token { + Token::Number(x) => { + self.advance()?; + return Ok(Node::Number(x)) + }, + + Token::Bracket('(') => { + self.advance()?; + let expr = self.expr()?; + + if let Token::Bracket(')') = self.current { + self.advance()?; + return Ok(expr) + } else { + return Err(Error::InvalidSyntax(0)) + } + }, + _ => return Err(Error::InvalidSyntax(0)) + } + } + + fn power(&mut self) -> Result { + let mut left = self.atom()?; + + while let Token::Operator(Operator::Exp) = self.current { + let current = self.current.clone(); + self.advance()?; + let operator = mknode!(current).expect("mknode function returned None"); + let right = self.factor()?; + + left = Node::BinaryOperation(Box::new(BinaryOperation { left: left.clone(), operator, right })); + } + Ok(left) + } + + + + fn factor(&mut self) -> Result { + + let token = self.current.clone(); + + match token { + Token::Operator(Operator::Add) | Token::Operator(Operator::Sub) => { + self.advance()?; + let operator = mknode!(token).expect("mknode returned none"); + let other = self.factor().expect("holup"); + return Ok(Node::UnaryOperation(Box::new(UnaryOperation { operator, other}))) + }, + + _ => { + return Ok(self.power()?) + } + } + } + + + + fn term(&mut self) -> Result { + + let mut left = self.factor()?; + + while let Token::Operator(Operator::Div) + | Token::Operator(Operator::Mul) + | Token::Operator(Operator::Mod) + | Token::Operator(Operator::Qot) = self.current { + + let current = self.current.clone(); + self.advance()?; + let operator = mknode!(current).expect("mknode function returned None"); + let right = self.factor()?; + + left = Node::BinaryOperation(Box::new(BinaryOperation { left: left.clone(), operator, right })); + } + Ok(left) + } + + + + fn expr(&mut self) -> Result { + let mut left = self.term()?; + + while let Token::Operator(Operator::Sub) | Token::Operator(Operator::Add) = self.current { + let current = self.current.clone(); + self.advance()?; + let operator = mknode!(current).expect("mknode returned None"); + let right = self.term()?; + + left = Node::BinaryOperation(Box::new(BinaryOperation { left: left.clone(), operator, right })); + } + Ok(left) + } +} + + + +#[macro_export] +macro_rules! mknode { + ($token:expr) => { + match $token { + Token::Operator(x) => Some(Node::Operator(x)), + Token::Number(x) => Some(Node::Number(x)), + _ => None + } + }; +} + + + +pub struct Calculator {} + +#[async_trait] +impl Application for Calculator { + fn new() -> Self { + Self {} + } + + async fn run(&mut self, args: Vec) -> Result<(), ShellError> { + if args.len() == 0 { + loop { + print!("enter equation > "); + let inp = std::io::stdin().await; + println!("{}", inp); + if inp == String::from("exit\n") { + return Ok(()); + } + match calculate_inner(inp) { + Ok(_) => (), + Err(_) => { println!("your input must be a valid mathematical expression contaning only numbers (including floats) and the operators: [ +, -, *, **, /, //, % ]"); return Err(ShellError::CommandFailed(String::from("failed"))) }, + }; + } + } else { + match calculate_inner(args.into_iter().collect()) { + Ok(x) => x, + Err(_) => { println!("your input must be a valid mathematical expression contaning only numbers (including floats) and the operators: [ +, -, *, **, /, //, % ]"); return Err(ShellError::CommandFailed(String::from("failed"))) }, + }; + Ok(()) + } + + } +} + + +fn calculate_inner(mut equation: String) -> Result { + + equation.push('\n'); + let mut neweq = equation.clone(); + neweq.pop(); + + + let tokens = tokenise(&equation)?; + + let mut parser = Parser::new(tokens)?; + let ast = parser.parse()?; + + + let mut interpreter = Interpreter::new()?; + let result = interpreter.visit(ast)?; + + let return_res = { + if let Value::Number(x) = result { + x + } else { + panic!("did not return a float!"); + } + }; + println!("\n\n + _____ _ _ + / ____| | | | | + | | _ __ _ _ ___| |_ __ _| | + | | | '__| | | / __| __/ _` | | + | |____| | | |_| \\__ \\ || (_| | | + \\_____|_| \\__, |___/\\__\\__,_|_| + _____ __/ | + / ____||___/ | + | | __ _| | ___ + | | / _` | |/ __| + | |___| (_| | | (__ + \\_____\\__,_|_|\\___| + + ┌────────────────────────────────────────────┐ + │ │ + │ Expression -> [ {} ] + │ │ + │ Calculated Solution -> [ {} ] + │ │ + └────────────────────────────────────────────┘ + ", neweq, return_res); + + Ok(return_res) +} + + + +fn tokenise(equation: &str) -> Result, Error> { + let mut tokens = Vec::new(); + let mut current_num = "".to_string(); + let current_string: String = "".to_string(); + + 'mainloop: for (x, character) in equation.chars().enumerate() { + + + match character { + '0'..='9' => current_num.push(character), + '.' => current_num.push(character), + _ => { + if current_num.len() != 0 { + tokens.push(Token::Number(current_num.parse::().unwrap())); + current_num = "".to_string(); + } else if current_string.len() != 0 { + + } match character { + '+' => tokens.push(Token::Operator(Operator::Add)), + '-' => tokens.push(Token::Operator(Operator::Sub)), + '%' => tokens.push(Token::Operator(Operator::Mod)), + '*' => { + if &equation.chars().nth(x-1).unwrap() == &'*' { + tokens.push(Token::Operator(Operator::Exp)); + } else if &equation.chars().nth(x+1).unwrap() == &'*' { + () + } else { + tokens.push(Token::Operator(Operator::Mul)); + } + }, + '/' => { + if &equation.chars().nth(x-1).unwrap() == &'/' { + tokens.push(Token::Operator(Operator::Qot)); + } else if &equation.chars().nth(x+1).unwrap() == &'/' { + () + } else { + tokens.push(Token::Operator(Operator::Div)); + } + }, + '(' | ')' | '[' | ']' | '{' | '}' | '<' | '>' => tokens.push(Token::Bracket(character)), + '\n' => break 'mainloop, + ' ' => (), + _ => { + return Err(Error::InvalidCharacter(x)) + }, + } + } + } + } + Ok(tokens) +} + + + + +#[derive(Debug)] +enum Value { + Number(f64), + Operator(Operator) +} + + + +#[derive(Debug, Clone, PartialEq)] +enum Token { + Number(f64), + Operator(Operator), + Bracket(char), + Null, +} + + +#[derive(Copy, Debug, Clone, PartialEq)] +enum Operator { + Add, + Sub, + Mul, + Div, + Qot, + Mod, + Exp, +} + +#[derive(Debug)] +enum Error { + InvalidSyntax(usize), + InvalidCharacter(usize), + LogicalError(String), + Eof, + Other(String), +} + + + +#[derive(Debug, Clone, PartialEq)] +enum Node { + Number(f64), + Operator(Operator), + BinaryOperation(Box), + UnaryOperation(Box) +} + + +#[derive(Debug, Clone, PartialEq)] +struct BinaryOperation { + left: Node, + operator: Node, + right: Node, +} + +#[derive(Debug, Clone, PartialEq)] +struct UnaryOperation { + operator: Node, + other: Node, +} + + + + + + + +impl fmt::Display for BinaryOperation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, "(\n{} \n{} \n{}\n) + + ", self.left, self.operator, self.right + ) + } +} +impl fmt::Display for UnaryOperation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, "(\n{} \n{} \n) + + ", self.operator, self.other, + ) + } +} + +impl fmt::Display for Node { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Node::Number(x) => write!(f, "{}", x), + Node::Operator(x) => write!(f, "{}", x), + Node::BinaryOperation(x) => { + let inner = *x.clone(); + write!(f, "{}", inner) + } + Node::UnaryOperation(x) => { + let inner = *x.clone(); + write!(f, "{}", inner) + } + } + } +} + +impl fmt::Display for Operator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Operator::Add => write!(f, "+"), + Operator::Sub => write!(f, "-"), + Operator::Mul => write!(f, "*"), + Operator::Div => write!(f, "/"), + Operator::Mod => write!(f, "%"), + Operator::Qot => write!(f, "//"), + Operator::Exp => write!(f, "**"), + } + } +} diff --git a/src/user/bin/crystal_rpg/engine.rs b/src/user/bin/crystal_rpg/engine.rs new file mode 100644 index 0000000..336e770 --- /dev/null +++ b/src/user/bin/crystal_rpg/engine.rs @@ -0,0 +1,53 @@ +use super::entity::Enemy; +use alloc::vec::Vec; + +pub enum Event { + PlayerKilled, + EntityKilled(Enemy), +} + +pub enum Choice { + A(A), + B(B), +} + +impl core::fmt::Display for Event { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Event::PlayerKilled => write!(f, "Player killed!"), + Event::EntityKilled(x) => write!(f, "Entity killed! {}", x), + } + } +} + +impl core::fmt::Display for Choice where + A: core::fmt::Display, + B: core::fmt::Display +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Choice::A(a) => write!(f, "{}", a), + Choice::B(b) => write!(f, "{}", b), + } + } +} + +pub fn eventcheck(e: (A, Option>)) -> Choice { + match e.1 { + Some(events) => { + for event in events { + match event { + Event::PlayerKilled => { + return Choice::B(event) + } + Event::EntityKilled(entity) => { + return Choice::B(event) + } + } + } + }, + None => (), + }; + + Choice::A(e.0) +} \ No newline at end of file diff --git a/src/user/bin/crystal_rpg/entity.rs b/src/user/bin/crystal_rpg/entity.rs new file mode 100644 index 0000000..6f4b524 --- /dev/null +++ b/src/user/bin/crystal_rpg/entity.rs @@ -0,0 +1,102 @@ +use super::player::Player; +use super::engine::Event; + +use alloc::{string::String, vec::Vec, vec}; +use crate::std::random; + + +pub trait Entity { + fn attack_entity(&mut self, _: &mut EntityObject) -> (AttackResult, Option>) { + (AttackResult::Miss, None) + } +} +pub enum EntityObject<'a> { + Player(&'a mut Player), + Enemy(&'a mut Enemy), +} + +#[derive(Debug, Clone, Copy)] +pub enum AttackResult { + Miss, + GlancingBlow(f64), + Hit(f64), + CriticalHit(f64), + FriendlyFire, +} + +impl core::fmt::Display for AttackResult { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + AttackResult::Miss => write!(f, "Missed!"), + AttackResult::GlancingBlow(damage) => write!(f, "Glancing Blow: {}", damage), + AttackResult::Hit(damage) => write!(f, "Hit: {}", damage), + AttackResult::CriticalHit(damage) => write!(f, "Critical Hit: {}", damage), + AttackResult::FriendlyFire => write!(f, "Friendly Fire (no damage dealt)!"), + } + } +} + + +#[derive(Debug, Clone, Copy)] +pub struct Enemy { + pub health_points: f64, + pub max_health_points: f64, + pub base_attack_damage: f64, + pub speed: f64, +} +impl Enemy { + pub fn new() -> Self { + Self { + health_points: 200.0, + max_health_points: 200.0, + base_attack_damage: 5.0, + speed: 100.0, + } + } +} +impl core::fmt::Display for Enemy { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Enemy: {}/{}", self.health_points, self.max_health_points) + } +} + +impl Entity for Enemy { + fn attack_entity(&mut self, target: &mut EntityObject) -> (AttackResult, Option>) { + let mut entity = if let EntityObject::Player(player) = target { + player + } else { + return (AttackResult::FriendlyFire, None); + }; + + // combat implementation + + let dmg: f64; + + let r = random::Random::int(0, 125) as f64; + let rs = self.speed / entity.speed * 100 as f64; + + let attack = if r < rs * 0.2 { + dmg = self.base_attack_damage * 1.5; + entity.health_points -= dmg; + AttackResult::CriticalHit(dmg) + + } else if r < rs * 0.8 { + dmg = self.base_attack_damage; + entity.health_points -= dmg; + AttackResult::Hit(dmg) + + } else if r < rs { + dmg = self.base_attack_damage * 0.5; + entity.health_points -= dmg; + AttackResult::GlancingBlow(dmg) + } else { + AttackResult::Miss + }; + + if entity.health_points <= 0.0 { + return (attack, Some(vec![Event::PlayerKilled])); + } else { + return (attack, None) + } + } +} \ No newline at end of file diff --git a/src/user/bin/crystal_rpg/init.rs b/src/user/bin/crystal_rpg/init.rs new file mode 100644 index 0000000..d390d4a --- /dev/null +++ b/src/user/bin/crystal_rpg/init.rs @@ -0,0 +1,129 @@ +use async_trait::async_trait; +use rand::prelude::*; + +use super::{ + engine::{eventcheck, Choice, Event}, + entity::{Entity, Enemy, EntityObject}, + player::Player, +}; + +use alloc::{boxed::Box, string::{String, ToString}, vec::Vec, format, borrow::ToOwned}; + +use crate::{ + std::application::{ + Application, + Error, + }, + std::{ + io::{self, println, serial_println, FRAMEGEN, Element}, + random, + }, +}; + + +pub struct GameLoop; + + +#[async_trait] +impl Application for GameLoop { + fn new() -> Self { + Self {} + } + async fn run(&mut self, _args: Vec) -> Result<(), Error> { + + let mut username: String = io::stdin().await; + username = username.trim().to_string(); + + let mut player = Player::new(username); + + let mut enemy = Enemy::new(); + + for _ in 0..30 { + match (eventcheck(player.attack_entity(&mut EntityObject::Enemy(&mut enemy)))) { + Choice::A(result) => { + println!("{}", result); + }, + Choice::B(event) => { + println!("{}", event); + match event { + Event::PlayerKilled => { + println!(" [!] {} was slain by Enemy\n\n[ You lost! ]", player.username); + break; + } + Event::EntityKilled(entity) => { + println!("\n [!] Enemy was slain by {}\n\n [ You won! ]", player.username); + break; + } + } + } + } + println!("{}", eventcheck(enemy.attack_entity(&mut EntityObject::Player(&mut player)))); + println!("[{}\n[{}", player, enemy); + } + + FRAMEGEN.lock().render_frame(); + + + let string = String::from(format!( +"┌────────────────────────────┐ +│ {} +│ {} / {} +└────────────────────────────┘" + , player.username, player.health_points, player.max_health_points)); + let mut healthbar = Element::from_str(string); + healthbar.render((1, 1)); + + let new2 = String::from("[an element]"); + let mut new = Element::from_str(new2); + + + new.render((10, 10)); + new.render((10, 15)); + new.render((5, 20)); + new.render((34, 16)); + + + FRAMEGEN.lock().render_frame(); + + let fr = FRAMEGEN.lock().get_frame().to_owned(); + serial_println!("{}", { + let mut string = String::new(); + for row in fr { + let mut r = String::new(); + for col in row { + r.push(col); + } + string.push_str(&r); + string.push('\n') + }; + string + }); + + + loop { + println!("{}", io::stdchar().await) + } + + Ok(()) + } +} + +fn random() -> u64 { + let mut r = random::Random::int(0, 125) as u64; + r +} + + + + + + + + + + + + + + + diff --git a/src/user/bin/crystal_rpg/items.rs b/src/user/bin/crystal_rpg/items.rs new file mode 100644 index 0000000..2003b32 --- /dev/null +++ b/src/user/bin/crystal_rpg/items.rs @@ -0,0 +1,19 @@ + + +#[derive(Debug, Clone, Copy)] +pub struct Armour; +#[derive(Debug, Clone, Copy)] +pub struct Weapon; +#[derive(Debug, Clone, Copy)] +pub struct Charm; +#[derive(Debug, Clone, Copy)] +pub struct OtherItem; + + +#[derive(Debug, Clone, Copy)] +pub enum Item { + Armour(Armour), + Weapon(Weapon), + Charm(Charm), + Other(OtherItem), +} diff --git a/src/user/bin/crystal_rpg/mod.rs b/src/user/bin/crystal_rpg/mod.rs new file mode 100644 index 0000000..6758733 --- /dev/null +++ b/src/user/bin/crystal_rpg/mod.rs @@ -0,0 +1,6 @@ +pub mod player; +pub mod items; +pub mod entity; +pub mod engine; +pub mod renderer; +pub mod init; \ No newline at end of file diff --git a/src/user/bin/crystal_rpg/player.rs b/src/user/bin/crystal_rpg/player.rs new file mode 100644 index 0000000..4a574fd --- /dev/null +++ b/src/user/bin/crystal_rpg/player.rs @@ -0,0 +1,82 @@ +use super::{ + items::{Item, Armour}, + entity::{Entity, EntityObject, AttackResult}, + engine::Event, +}; + + +use alloc::{string::String, vec::Vec, vec}; + +use crate::std::random; + +pub struct Player { + pub username: String, + pub health_points: f64, + pub max_health_points: f64, + pub base_attack_damage: f64, + pub speed: f64, + + pub inventory: [ Item ; 15 ], + pub equipped: [ Item ; 7 ], // helmet, chestplate, leggings, boots, mainhand, offhand, charm +} +impl Player { + pub fn new(username: String) -> Self { + Self { + username, + health_points: 100.0, + max_health_points: 100.0, + base_attack_damage: 10.0, + speed: 100.0, + inventory: [ Item::Armour(Armour {}); 15 ], + equipped: [ Item::Armour(Armour {}); 7 ], + } + } +} +impl core::fmt::Display for Player { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}: {}/{}", self.username, self.health_points, self.max_health_points) + } +} + + + +impl Entity for Player { + fn attack_entity(&mut self, target: &mut EntityObject) -> (AttackResult, Option>) { + let mut entity = if let EntityObject::Enemy(enemy) = target { + enemy + } else { + return (AttackResult::FriendlyFire, None); + }; + + // combat implementation + + let dmg: f64; + + let r = random::Random::int(0, 125) as f64; + let rs = self.speed / entity.speed * 100 as f64; + + let attack = if r < rs * 0.2 { + dmg = self.base_attack_damage * 1.5; + entity.health_points -= dmg; + AttackResult::CriticalHit(dmg) + + } else if r < rs * 0.8 { + dmg = self.base_attack_damage; + entity.health_points -= dmg; + AttackResult::Hit(dmg) + + } else if r < rs { + dmg = self.base_attack_damage * 0.5; + entity.health_points -= dmg; + AttackResult::GlancingBlow(dmg) + } else { + AttackResult::Miss + }; + + if entity.health_points <= 0.0 { + return (attack, Some(vec![Event::EntityKilled(entity.clone())])); + } else { + return (attack, None) + } + } +} \ No newline at end of file diff --git a/src/user/bin/crystal_rpg/renderer.rs b/src/user/bin/crystal_rpg/renderer.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/user/bin/crystalfetch.rs b/src/user/bin/crystalfetch.rs new file mode 100644 index 0000000..212a6e9 --- /dev/null +++ b/src/user/bin/crystalfetch.rs @@ -0,0 +1,60 @@ +use async_trait::async_trait; +use alloc::{boxed::Box, string::String, vec::Vec}; + +use crate::{ + std::os::OS, + std::io::{Color, write}, + println, + std::application::{ + Application, + Error, + }, +}; + + +pub struct CrystalFetch {} + +#[async_trait] +impl Application for CrystalFetch { + + fn new() -> Self { + Self {} + } + + async fn run(&mut self, _args: Vec) -> Result<(), Error> { + + let os = OS.lock().os.clone(); + let version = OS.lock().version.clone(); + + + write(format_args!(" +──────────────────────────────────────────────────────── + _____ _ _ ____ _____ + / ____| | | | |/ __ \\ / ____| + | | _ __ _ _ ___| |_ __ _| | | | | (___ + | | | '__| | | / __| __/ _` | | | | |\\___ \\ + | |____| | | |_| \\__ \\ || (_| | | |__| |____) | + \\_____|_| \\__, |___/\\__\\__,_|_|\\____/|_____/ + __/ | + |___/ +"), (Color::Magenta, Color::Black)); + + println!(" + ╔═══════════════════════════════ + ║ + ║ OS » {} + ║ BUILD » {} + ║ RAM » idk + ║ Shell » CrystalSH + ║ API » CrystalAPI + ║ Pkgs » 4 + ║ Fetch » CrystalFetch + ║ + ╚═══════════════════════════════ + +──────────────────────────────────────────────────────── +", os, version); + Ok(()) + } + +} diff --git a/src/user/bin/mod.rs b/src/user/bin/mod.rs new file mode 100644 index 0000000..bb8f9b9 --- /dev/null +++ b/src/user/bin/mod.rs @@ -0,0 +1,6 @@ +pub mod calc; +pub mod rickroll; +pub mod crystalfetch; +pub mod tasks; +pub mod crystal_rpg; +pub mod shell; \ No newline at end of file diff --git a/src/user/bin/rickroll.rs b/src/user/bin/rickroll.rs new file mode 100644 index 0000000..3c49ffe --- /dev/null +++ b/src/user/bin/rickroll.rs @@ -0,0 +1,41 @@ +use async_trait::async_trait; + +use crate::std::application::{ + Application, + Error, +}; + +use crate::{println}; +use alloc::{string::String, boxed::Box, vec::Vec}; + + +pub struct Rickroll {} + +#[async_trait] +impl Application for Rickroll { + fn new() -> Self { + Self {} + } + + async fn run(&mut self, _args: Vec) -> Result<(), Error> { + println!(" + + _ _ _____ + | \\ | | / ____| + | \\| | _____ _____ _ __ | | __ ___ _ __ _ __ __ _ + | . ` |/ _ \\ \\ / / _ \\ '__| | | |_ |/ _ \\| '_ \\| '_ \\ / _` | + | |\\ | __/\\ V / __/ | | |__| | (_) | | | | | | | (_| | + |_| \\_|\\___| \\_/ \\___|_| \\_____|\\___/|_| |_|_| |_|\\__,_| + _______ __ __ ___ ___ + / ____(_) \\ \\ / / | | | | + | | __ ___ _____ \\ \\_/ /__ _ _ | | | |_ __ + | | |_ | \\ \\ / / _ \\ \\ / _ \\| | | | | | | | '_ \\ + | |__| | |\\ V / __/ | | (_) | |_| | | |__| | |_) | + \\_____|_| \\_/ \\___| |_|\\___/ \\__,_| \\____/| .__/ + | | + |_| + +"); + Ok(()) + } +} diff --git a/src/user/bin/shell.rs b/src/user/bin/shell.rs new file mode 100644 index 0000000..9e1f8a4 --- /dev/null +++ b/src/user/bin/shell.rs @@ -0,0 +1,197 @@ +use async_trait::async_trait; +use lazy_static::lazy_static; +use spin::Mutex; +use x86_64::instructions::interrupts; + +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, +}; + +use crate::{ + print, println, + std::application::{Application, Error}, + user::bin::*, +}; + +lazy_static! { + pub static ref CMD: Mutex = Mutex::new(CommandHandler::new()); +} + +/// boilerplate function +/// may provide other interfacing options later on idk. +pub async fn command_handler() { + eventloop().await; +} + +/// this function starts the shell running, the function will loop repeatedly until the command to shutdown +/// TODO: implement shutdown command +pub async fn eventloop() { + println!("running!"); + + let mut fetch = crystalfetch::CrystalFetch::new(); + let string = String::from(" "); + let mut vec: Vec = Vec::new(); + vec.push(string); + fetch.run(vec).await.unwrap(); + + CMD.lock().prompt(); + + loop { + let string = crate::std::io::stdin().await; + CMD.lock().current.push_str(&string); + match exec().await { + Ok(_) => { + (); + } + Err(e) => { + handle_error(e); + } + }; + CMD.lock().prompt(); + } +} + +fn handle_error(e: Error) {} + +async fn exec() -> Result<(), Error> { + let mut current = CMD.lock().current.clone(); + + CMD.lock().history.history.push(current.clone()); + + current.pop(); + CMD.lock().current = String::new(); + + let (cmd, args) = match CommandHandler::parse_args(current) { + Ok((cmd, args)) => (cmd, args), + Err(_) => { + return Err(Error::EmptyCommand); + } + }; + + match cmd.as_str() { + "calculate" | "calc" | "solve" => { + let mut cmd = calc::Calculator::new(); + cmd.run(args).await?; + } + + "rickroll" => { + let mut cmd = rickroll::Rickroll::new(); + cmd.run(args).await?; + } + + "crystalfetch" => { + let mut cmd = crystalfetch::CrystalFetch::new(); + cmd.run(args).await?; + } + "tasks" => { + let mut cmd = tasks::Tasks::new(); + cmd.run(args).await?; + } + "play" => { + let mut gameloop = crystal_rpg::init::GameLoop::new(); + gameloop.run(args).await?; + } + + // direct OS functions (not applications) + "echo" => { + println!( + "Crystal: '{}'", + args.into_iter() + .map(|mut s| { + s.push_str(" "); + s + }) + .collect::() + ) + } + + "clear" => { + interrupts::without_interrupts(|| { + crate::std::io::clear(); + }); + } + + "print" => { + use crate::std::os::OS; + let x: String = OS.lock().version.clone(); + println!("{}", x); + } + "switch" => { + crate::std::io::switch_mode(); + } + "random" => { + use crate::std::random::Random; + let vec = Vec::from(["a", "b", "c", "d", "e", "f"]); + let sel = Random::selection(vec); + println!("{}", sel); + } + "filesystem" => { + use crate::std::io; + io::mkfs(); + } + + _ => { + return Err(Error::UnknownCommand( + "command not yet implemented".to_string(), + )) + } + }; + + Ok(()) +} + +pub struct CommandHandler { + current: String, + history: CmdHistory, +} + +impl CommandHandler { + pub fn new() -> Self { + let handler = Self { + current: String::new(), + history: CmdHistory { + history: Vec::new(), + }, + }; + handler + } + + pub fn parse_args(command: String) -> Result<(String, Vec), String> { + let temp = command.split(" ").collect::>(); + let mut args: Vec = Vec::new(); + for arg in temp { + match arg { + "" => {} + x => args.push(x.to_string()), + } + } + let cmd: String; + if args.len() > 0 { + cmd = args[0].clone(); + args.remove(0); + } else { + return Err("command was empty.".to_string()); + }; + Ok((cmd, args)) + } + + // this function is activated every time the user presses a key on the keyboard + // it accesses the queue of keys (a static ref in src/tasks/keyboard.rs) + + // displays a text prompt for the user to type into. + // this is a separate function so that it can be developed as necessary later on + // TODO: coloured prompt + + pub fn prompt(&self) { + print!("\n [ Crystal ] >> "); + } + + // this function is run every time the enter key is pressed in the command line mode. + // it detects the command that is being run and then executes it, passing the arguments to it. +} + +struct CmdHistory { + history: Vec, +} diff --git a/src/user/bin/shellrewrite.rs b/src/user/bin/shellrewrite.rs new file mode 100644 index 0000000..fdfbd6c --- /dev/null +++ b/src/user/bin/shellrewrite.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use lazy_static::lazy_static; +use spin::Mutex; +use x86_64::instructions::interrupts; + +use alloc::{ + boxed::Box, + string::{String, ToString}, + vec::Vec, +}; + +use crate::{ + applications::*, + std::application::{Application, Error}, + std::io::{print, println}, + user::bin::*, +}; + +lazy_static! { + pub static ref CMD: Mutex = Mutex::new(Cmd::new()); +} +struct Cmd {} +impl Cmd { + fn new() -> Self { + Self {} + } +} + +// [ CRYSTAL SHELL ] +// the purpose of this module is to provide a basic unix shell like experience for the user +// to interact with the OS +// this is a rewrite of my original shell. +// this shell should support: +// - browsing the virtual filesystem +// - executing programs +// - basic arithmetic +// - chained execution ( multiple commands linked together) eg: '5 + 5 | echo' which calculates +// the result of 5 + 5 and then sends the result to an echo command which prints it to console + +pub fn init_sh() -> Result<(), String> { + Ok(()) +} diff --git a/src/user/bin/tasks.rs b/src/user/bin/tasks.rs new file mode 100644 index 0000000..4d9888a --- /dev/null +++ b/src/user/bin/tasks.rs @@ -0,0 +1,168 @@ +use alloc::{string::String, vec::Vec, boxed::Box}; +use crate::std::application::{ + Application, + Error +}; +use crate::{print, println}; +use lazy_static::lazy_static; +use spin::Mutex; +use async_trait::async_trait; +use alloc::{ + string::ToString, + borrow::ToOwned, +}; + +use crate::std::random; + + +lazy_static! { + static ref TASKS: Mutex = Mutex::new(TaskList::new()); +} + + + + +pub struct Tasks; + +#[async_trait] +impl Application for Tasks { + fn new() -> Self { Self {} } + + async fn run(&mut self, args: Vec) -> Result<(), Error> { + + if args[0].clone() == String::from("add") { + + let content = args[1..].to_owned().into_iter().map(|mut s| {s.push_str(" "); s} ).collect::(); + self.add_task(content); + + } + + if args[0].clone() == String::from("remove") { + let idx = match args[1].to_owned().parse::() { + Ok(x) => x, + Err(_) => { return Err(Error::CommandFailed(String::from("number must be an integer"))) }, + }; + self.remove_task(idx); + } + + if args[0].clone() == String::from("select") { + let arg2 = args[1].clone(); + if arg2 == String::from("random") { + let len = TASKS.lock().tasks.len(); + self.select_task(random::Random::int(0, len -1) as i32); + } else if arg2.parse::().is_ok() { + () + } + } + + if args[0].clone() == String::from("priority") { + let idx = TASKS.lock().current; + if idx < 0 { + println!( +"------------------------------------- +no task currently set as priority +-------------------------------------\n" + ); + return Ok(()) + } + + let task = TASKS.lock().tasks[idx as usize].clone(); + let content = task.content.clone(); + + println!( +"------------------------------------- +PRIORITY TASK: {} : {} +-------------------------------------\n", + idx, content + ) + } + + + if args[0].as_str() == "list" { + + println!( +"------------------------------------- + Your TODO List: +-------------------------------------\n"); + + for task in TASKS.lock().tasks.iter() { + + let idx = task.taskid; + let content = task.content.clone(); + println!(" | Task -> {} \n | {}\n", idx, content); + } +println!("\n-------------------------------------"); + + } + + Ok(()) + } +} + +impl Tasks { + fn add_task(&mut self, content: String) { + TASKS.lock().add(content); + } + fn remove_task(&self, idx: usize) { + TASKS.lock().remove(idx); + } + fn select_task(&self, idx: i32) { + TASKS.lock().select(idx); + } +} + +pub struct TaskList { + current: i32, + tasks: Vec, + next_idx: usize, +} + +impl TaskList { + pub fn new() -> Self { + Self { + current: -1, + tasks: Vec::new(), + next_idx: 1 + } + } + pub fn next(&mut self) -> usize { + self.next_idx += 1; + self.next_idx -1 + } + pub fn add(&mut self, content: String) -> Result<(), Error> { + let task = Task::new(self.next(), content); + let id = task.taskid.clone(); + self.tasks.push(task); + Ok(()) + } + pub fn remove(&mut self, id: usize) -> Result<(), Error> { + for (i, task) in self.tasks.clone().iter().enumerate() { + match task.taskid { + id => { self.tasks.remove(i); }, + _ => { return Err(Error::CommandFailed(String::from("this task does not exist"))); }, + } + }; + Ok(()) + } + pub fn select(&mut self, idx: i32) -> Result<(), Error> { + self.current = idx; + Ok(()) + } +} + + + +#[derive(Debug, Clone)] +pub struct Task { + taskid: usize, + content: String, +} + +impl Task { + fn new(id: usize, content: String) -> Self { + Self { + taskid: id, + content, + } + } +} diff --git a/src/user/lib/mod.rs b/src/user/lib/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/user/mod.rs b/src/user/mod.rs new file mode 100644 index 0000000..204a9f4 --- /dev/null +++ b/src/user/mod.rs @@ -0,0 +1,2 @@ +pub mod lib; +pub mod bin; diff --git a/templates_and_stuff/application_template.rs b/templates_and_stuff/application_template.rs new file mode 100644 index 0000000..56ce183 --- /dev/null +++ b/templates_and_stuff/application_template.rs @@ -0,0 +1,87 @@ + +// ignore everything from this point up until the App struct + +// --------------OS-INTERFACE------------------------------------------------------------------------------------------------------- + +use std::io; +use std::io::Write; // ignore these, i have my own implementations that i will replace them with + +struct CommandHandler {} // a struct used in my code (just ignore) + +impl CommandHandler { // dont modify anything here + fn new() -> Self { + Self {} + } + + fn input(&mut self) -> String { // this function will get replaced by the custom input function + let mut string = String::new(); + io::stdin().read_line(&mut string).expect("error getting input"); + string + } +} + +fn main() { // the entry point to your code, it calls the code for the application + // will be removed when integrated into the os and replaced by the shell command + println!(""); + print!("enter arguments to run command with > "); + io::stdout().flush(); + let mut args = String::new(); + io::stdin().read_line(&mut args).expect("failed to get input"); + let mut app = App::new(CommandHandler::new()); + app.run(args); +} + + + + +// --------------IMPLEMENTATION----------------------------------------------------------------------------------------------------- + + +struct App { // change name to whatever you want + handler: CommandHandler, + // any global variables for the application should be put here + // in the form: varname: VarType, +} + +impl App { // name must be the same as the name of the struct + fn new(handler: CommandHandler) -> Self { + Self { // this should add any variables that are needed while the application is running + handler: handler, + // status: String, (example) + } + } + + fn input(&mut self) -> String { // this function gives command line input + self.handler.input() + } + + fn keystroke(&mut self) -> String { + self.handler.input() + } + + fn run(&mut self, args: String) -> Result<(), String> { /* + this represents your actual main function + write all the code for your program starting here + + use println!() to print to the screen + use self.input() to get input from terminal + */ + + println!("app running {}", args); // do stuff here + + // example of how you can use the input function + + println!("type something"); + println!("input: {}", self.input()); + + // if you want to return an error, write: return Err("error message") + // the error message tells the operating system what went wrong with the code or user input. + // if you want to return ok, write: return Ok(()) (make sure to have the 2 sets of brackets) + Ok(()) + } +} + + + + + diff --git a/tests/allocation.rs b/tests/allocation.rs new file mode 100644 index 0000000..58f8903 --- /dev/null +++ b/tests/allocation.rs @@ -0,0 +1,69 @@ + +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(CrystalOS::test_runner)] +#![reexport_test_harness_main = "test_main"] + +extern crate alloc; + +use bootloader::{entry_point, BootInfo}; +use core::panic::PanicInfo; + +use alloc::{ boxed::Box, vec::Vec }; + + +entry_point!(main); + +fn main(boot_info: &'static BootInfo) -> ! { + use CrystalOS::kernel::allocator; + use CrystalOS::kernel::memory::{self, BootInfoFrameAllocator}; + use x86_64::VirtAddr; + + CrystalOS::init(); + + let physical_memory_offset = VirtAddr::new(boot_info.physical_memory_offset); + let mut mapper = unsafe { memory::init(physical_memory_offset)}; + let mut frame_allocator = unsafe { + BootInfoFrameAllocator::init(&boot_info.memory_map) + }; + allocator::init_heap(&mut mapper, &mut frame_allocator).expect("failed to initialise heap"); + + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + CrystalOS::test_panic_handler(info) +} + +#[test_case] +fn box_allocation() { + let heap_1 = Box::new(69); + let heap_2 = Box::new(420); + assert_eq!(*heap_1, 69); + assert_eq!(*heap_2, 420); +} + +#[test_case] +fn vec_allocation() { + let x = 1000; + let mut vector = Vec::new(); + for i in 0..x { + vector.push(i); + } + assert_eq!(vector.iter().sum::(), (x-1) * x/2); + +} + +#[test_case] +fn reallocation() { + use CrystalOS::kernel::allocator::HEAP_SIZE; + + for i in 0..HEAP_SIZE { + let x = Box::new(i); + assert_eq!(*x, i); + } +} diff --git a/tests/should_panic.rs b/tests/should_panic.rs new file mode 100644 index 0000000..3dfaec8 --- /dev/null +++ b/tests/should_panic.rs @@ -0,0 +1,29 @@ + +#![no_std] +#![no_main] + + +use core::panic::PanicInfo; +use CrystalOS::{QemuExitCode, exit, serial_println, serial_print}; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + serial_println!("OK"); + exit(QemuExitCode::Ok); + + loop {} +} + + +#[no_mangle] +pub extern "C" fn _start() -> ! { + shouldpanic(); + serial_println!("Err: Test did not panic"); + exit(QemuExitCode::Err); + loop {} +} + +fn shouldpanic() { + serial_print!("{}...\t", "should_panic::should_panic"); + assert_eq!(1,2); +} diff --git a/tests/stack_overflow.rs b/tests/stack_overflow.rs new file mode 100644 index 0000000..0b70491 --- /dev/null +++ b/tests/stack_overflow.rs @@ -0,0 +1,57 @@ + +#![no_std] +#![no_main] +#![feature(abi_x86_interrupt)] + +use core::panic::PanicInfo; +use lazy_static::lazy_static; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use CrystalOS::{exit, QemuExitCode, serial_println, serial_print}; + + +#[no_mangle] +pub extern "C" fn _start() -> ! { + serial_print!("stack_overflow::stack_overflow...\t"); + + CrystalOS::kernel::gdt::init(); + init_test_idt(); + + stack_overflow(); + + panic!("stack overflow did not stop execution!"); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + CrystalOS::test_panic_handler(info); +} + +#[allow(unconditional_recursion)] +fn stack_overflow() { + stack_overflow(); + volatile::Volatile::new(0).read(); +} + +lazy_static! { + static ref TEST_IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + unsafe { + idt.double_fault.set_handler_fn(test_double_fault_handler).set_stack_index(CrystalOS::kernel::gdt::DOUBLE_FAULT_IST_INDEX); + } + idt + }; +} + +extern "x86-interrupt" fn test_double_fault_handler( + _stack_frame: InterruptStackFrame, + _error_code: u64, +) -> ! { + serial_println!("OK"); + exit(QemuExitCode::Ok); + loop {} +} + + +pub fn init_test_idt() { + TEST_IDT.load(); +} diff --git a/tests/startup.rs b/tests/startup.rs new file mode 100644 index 0000000..cfa0a90 --- /dev/null +++ b/tests/startup.rs @@ -0,0 +1,26 @@ + +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(CrystalOS::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use CrystalOS::println; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + CrystalOS::test_panic_handler(info) +} + +#[test_case] +fn test_println() { + println!("testing println output"); +} diff --git a/x86_64-CrystalOS.json b/x86_64-CrystalOS.json new file mode 100644 index 0000000..ef2d806 --- /dev/null +++ b/x86_64-CrystalOS.json @@ -0,0 +1,16 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-sse,+soft-float" +} +