118 Commits

Author SHA1 Message Date
zxq5 ecdc76b068 Merge remote-tracking branch 'origin' into unified 2025-03-06 01:18:43 +00:00
zxq5 8ee4af1a48 someone broke a mod.rs so i fixed it. 2025-03-05 21:10:36 +00:00
zxq5 03c07cb5c4 Merging with origin/unified 2025-03-05 21:04:58 +00:00
zxq5 ef70bcf51e - heap allocator now makes sure virtual memory pages are mapped before using them. this means we no longer have to use page faults to allocate frames, it is done automatically.
- fixed the apic code that caused a page fault. it now writes the correct values at the correct physical address
- improved startup logging including displaying how much RAM is installed.
2025-03-05 20:49:21 +00:00
nullndvoid 2fb1741100 Begin setting up stack unwinding/tracing.
This actually worked so I am chuffed, but it doesn't read the necessary
DWARF structures just yet. Still a good step forwards.
2025-03-05 20:44:09 +00:00
nullndvoid af995d1d76 Tried and failed to use unwinding crate: shall do it myself tomorrow. 2025-03-05 03:24:26 +00:00
nullndvoid 014ec5310c Some more clippy fixes, warnings are noisy 2025-03-05 00:57:06 +00:00
nullndvoid d53661b9a0 Ran cargo fmt, clippy fixes, suppressed some warns
I will start working on stack traces tonight and tomorrow.

We need to be able to 'unwind' by finding calling functions.
2025-03-04 23:06:47 +00:00
zxq5 8704b5d249 FIXED THE APIC HELL YEAH 2025-03-04 12:44:42 +00:00
zxq5 2186b829aa - made improvements to memory code (refactored)
- started on improvements to the page frame allocator. it should be able to provide a usable page for any given virtual memory address requested.
2025-03-04 01:28:39 +00:00
zxq5 f502104a6e changes 2025-03-04 00:53:38 +00:00
zxq5 48dcde0c02 minor changes & work on threading 2025-03-04 00:53:06 +00:00
nullndvoid 6e913b15d7 Update macro to use relative working dirs, Spans 2025-03-03 15:57:16 +00:00
nullndvoid 0a5269eeb0 Fix clippy errors 2025-03-03 15:51:39 +00:00
zxq5 a0f5ce8797 reorganised memory. starting on threads 2025-03-03 15:50:58 +00:00
nullndvoid 2d3bc56962 Fix some clippy errors 2025-03-03 14:53:30 +00:00
nullndvoid 5703032f87 Fix module inception 2025-03-03 14:47:24 +00:00
zxq5 3966e697da refactor mega-commit.
- reorganised the entire project so that the entire kernel is a single codebase rather than a kernel and a libk.
2025-03-03 02:49:56 +00:00
aha480 e8237f4610 run_debug.sh now checks if it running on wsl and disables boot_flags if it is since it was causing issues with qemu 2025-03-01 12:49:42 +00:00
nullndvoid 53d325749d Fix incorrect target name in config 2025-02-28 16:15:26 +00:00
zxq5 8a3e9e3afc - implemented a custom allocator (fixed size block) with a fallback (linked list allocator) for larger block sizes
- apic code still not working (commented out, check lib.rs)
2025-02-28 04:18:35 +00:00
zxq5 e38c20dbd3 Merge pull request 'editor' (#6) from editor into dev
Reviewed-on: OsDev/FoundryOS#6
2025-02-28 03:05:38 +00:00
zxq5 c8bb85364c - updated to-dos
- removed unnecessary debugging code
- moved x86_64-kernel.json to the project root
2025-02-28 03:05:10 +00:00
zxq5 fe18004f7d Merge remote-tracking branch 'origin' into editor
merging into dev
2025-02-27 23:58:01 +00:00
zxq5 192100be7a changed page allocation to happen on page fault for performance reasons 2025-02-27 23:57:23 +00:00
nullndvoid 2915d0c879 Add tips on debugging/disassembling kernel sources 2025-02-27 23:32:05 +00:00
zxq5 9852cb14eb updated build scripts 2025-02-27 22:58:10 +00:00
zxq5 db8dbff9f2 added memory allocation flag to run command ``VM_MEMORY=<amount>G cargo run`` 2025-02-27 21:57:58 +00:00
zxq5 2178215a01 fixed triple fault 2025-02-27 16:52:31 +00:00
zxq5 821759ec63 apic broken pushing to debug 2025-02-27 16:19:43 +00:00
zxq5 0b3dbed4be updated documentation 2025-02-27 02:30:48 +00:00
zxq5 3b6e272fd2 not working 2025-02-27 01:16:07 +00:00
zxq5 ac0b47a45c merge 2025-02-26 04:00:00 +00:00
zxq5 15f59e68d5 initial apic implementaion (Commented out / NOT WORKING) 2025-02-26 03:48:11 +00:00
nullndvoid b7397d8a1b Remove unused clippy lints 2025-02-25 03:20:05 +00:00
nullndvoid 4fe6109e5b Rename scheduling to 'async_io', general cleanup 2025-02-25 03:18:12 +00:00
nullndvoid e294a13a91 Cleanup the README a little more 2025-02-25 02:59:55 +00:00
nullndvoid 752800a3ca Merge UEFI support for qemu into our runner scripts/build system 2025-02-25 02:56:48 +00:00
nullndvoid 177fddcf7d Add warnings to hardware script (dd implies 'doubly dangerous') 2025-02-25 02:55:52 +00:00
nullndvoid 375c5aa561 Update limine.conf to boot up faster 2025-02-25 02:54:19 +00:00
nullndvoid b945416665 Add qemu UEFI firmware support. 2025-02-25 02:53:28 +00:00
zxq5 27ee8226d8 implemented stdin methods for reading a string (async) and reading keystrokes (sync + async). added a very basic shell on top of it for debugging 2025-02-25 02:16:01 +00:00
nullndvoid 00d3a1de72 fb: Added support for typing backspaces. 2025-02-24 23:24:03 +00:00
zxq5 f7723a3944 making the keyboard module more flexible, still needs work 2025-02-24 18:24:21 +00:00
zxq5 438ef7a748 stop banning mod.rs 2025-02-24 16:30:38 +00:00
zxq5 2f7ab8fe6b Merge remote-tracking branch 'refs/remotes/origin/dev' into dev 2025-02-24 16:29:58 +00:00
zxq5 ca1c695e32 created stdio module, added hardware script 2025-02-24 16:28:56 +00:00
zxq5 1c22f89665 reverted dumb change 2025-02-24 15:34:46 +00:00
nullndvoid 1c147dd933 Ban module inception, this is annoying.
@zxq5 thoughts?
2025-02-24 15:34:29 +00:00
zxq5 91044f5fad merged dumb change 2025-02-24 15:33:59 +00:00
zxq5 d5b15826e2 adding std io idk 2025-02-24 15:33:42 +00:00
nullndvoid 39f1f4905d Ban mod.rs. 2025-02-24 15:29:39 +00:00
nullndvoid 03a3469d20 Formatting changes to task.rs 2025-02-24 15:19:01 +00:00
nullndvoid 2cbe9641aa Use prelude lol. 2025-02-24 15:14:12 +00:00
zxq5 e303ecf5a4 fixed merge commit tomfoolery. 2025-02-24 15:14:21 +00:00
zxq5 2f08835d69 merge commit. probably broken tbh 2025-02-24 15:10:58 +00:00
nullndvoid 8d57540566 Fix clippy errors 2025-02-24 15:02:44 +00:00
zxq5 03290e52a3 fixed a dumb error about me hardcoding paths 2025-02-24 13:07:11 +00:00
zxq5 8908a6a281 Update README.md 2025-02-24 03:45:02 +00:00
zxq5 aefc368d41 updated readme for more comprehensive build instructions 2025-02-24 03:44:22 +00:00
zxq5 76ec29b7ec changed #usage back to being a tag
(was not a markdown fail, it's an obsidian tag)
2025-02-24 03:36:28 +00:00
zxq5 d9bbdff08c - added a new libary libm containing procedural macros for the kernel.
these should be used to include external files and resources in the kernel binary
  at compile time.
  - libm currently supports loading psf-1 formatted fonts
- added two fonts that are included in the binary at compile time
- refactored libk to make the crate structure more organised and maintainable in future.
  new structure:
    - drivers   (hardware interaction)
    - resources (consts and statics included either manually or via macros)
    - std       (standard functions for higher level interaction with the os, for example creating windows)
- added geometry.rs
  - provides the Vec2<T> struct for use with dimensions, coordinates etc.
- added window.rs
  - provides the Window struct for rendering the state of an application to the screen
- added application.rs
  - provides the Application trait for custom programs to implement in order to run
2025-02-24 03:26:49 +00:00
zxq5 7ff33659fe async works! 2025-02-23 19:27:13 +00:00
zxq5 efe3034ad0 Merge pull request 'libk now replaces the plethora of lib crates' (#5) from nullndvoid/FoundryOS:dev into dev
Reviewed-on: OsDev/FoundryOS#5
2025-02-23 17:07:25 +00:00
nullndvoid d9b66f8008 Update README to remove refs to submodules. 2025-02-23 12:06:50 +00:00
nullndvoid 2be5b63af7 Fix some more clippy lints. 2025-02-23 11:58:52 +00:00
nullndvoid 7e62bdae35 Make rust-analyzer use clippy as the check command. 2025-02-23 11:53:42 +00:00
nullndvoid b8aa203c05 Bump edition to now stable 2024 edition (shiny!). 2025-02-23 11:52:54 +00:00
nullndvoid 9f83c5f295 Merge GDB support (and updated runner scripts) into dev. 2025-02-23 11:47:51 +00:00
nullndvoid 914cb4e409 Enable support for $USE_GDB when running in debug mode. 2025-02-23 11:43:26 +00:00
nullndvoid 43b1db41ca Introducing the hottest library crate in town (libk!) 2025-02-23 04:43:22 +00:00
nullndvoid c763f512f1 Accidentally forgot to add lib alloc lol 2025-02-23 03:16:22 +00:00
nullndvoid 5dc73e7000 Update Cargo.toml to use new registry 2025-02-23 03:15:19 +00:00
nullndvoid ca906767db Update docs (markdown fail) and checkout libraries again 2025-02-23 02:19:38 +00:00
nullndvoid ef8d028211 Update liballoc and libapplication. 2025-02-23 02:15:44 +00:00
nullndvoid 2886fe5ecf Update 2025-02-23 02:07:01 +00:00
nullndvoid c2c552ef25 Fixed most of the clippy warnings/suggestions. 2025-02-23 02:05:13 +00:00
nullndvoid e722874cee Formatting and fixing warnings 2025-02-23 01:38:21 +00:00
zxq5 27b0ed634c setup but not working 2025-02-22 23:12:46 +00:00
zxq5 72fe78cbc6 Merge pull request 'dev' (#4) from dev into main
Continuous integration / build (push) Failing after 2m12s
Reviewed-on: OsDev/FoundryOS#4
2025-02-22 21:32:27 +00:00
zxq5 f9bc75c4f3 added boot messages 2025-02-22 21:30:13 +00:00
zxq5 494d00c53b Update .gitea/workflows/rust.yml 2025-02-22 21:18:14 +00:00
zxq5 114c70ffe9 updated submodules
Continuous integration / build (push) Failing after 2m7s
2025-02-22 21:03:17 +00:00
zxq5 361c67764d totally didn't import an allocator... 2025-02-22 21:02:29 +00:00
zxq5 36cb118933 paging done, starting on allocation.
Continuous integration / build (push) Failing after 1m51s
2025-02-22 16:56:01 +00:00
zxq5 7b3ba170f0 changes to submodules
Continuous integration / build (push) Failing after 1m56s
2025-02-22 15:43:06 +00:00
zxq5 49880fa9d7 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev
merging into dev
2025-02-22 15:41:54 +00:00
zxq5 0bbbf653f8 setup GDT & fixed a deadlock 2025-02-22 15:41:41 +00:00
nullndvoid 40ad5dbbf4 Add page fault handler that does nothing, I am tired
Continuous integration / build (push) Failing after 38m24s
2025-02-22 05:00:18 +00:00
nullndvoid 2b2219f5be Update submodules
Continuous integration / build (push) Failing after 7m47s
2025-02-22 04:00:37 +00:00
nullndvoid c1a8afb836 Formatted random JSON file whoops
Continuous integration / build (push) Has been cancelled
2025-02-22 03:59:28 +00:00
zxq5 28afe25cca check boot was successful else panic
Continuous integration / build (push) Has been cancelled
2025-02-22 03:52:32 +00:00
zxq5 90faace7a2 changed name to snake case
Continuous integration / build (push) Has been cancelled
2025-02-22 03:50:13 +00:00
zxq5 5b1f04c1da changed some code order
Continuous integration / build (push) Has been cancelled
2025-02-22 03:47:54 +00:00
zxq5 3a232c8023 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev (and add cargo fmt on save)
Continuous integration / build (push) Has been cancelled
2025-02-22 03:39:22 +00:00
zxq5 5eaf0d7c69 Merge remote-tracking branch 'refs/remotes/origin/dev' into dev
Continuous integration / build (push) Has been cancelled
2025-02-22 03:36:32 +00:00
zxq5 b4d0b05e13 updated submodule 2025-02-22 03:35:02 +00:00
nullndvoid 969756c691 Suppress erroneous build errors from rust-analyzer.
Continuous integration / build (push) Has been cancelled
2025-02-22 03:33:54 +00:00
zxq5 5c9717d384 cargo fmt
Continuous integration / build (push) Has been cancelled
2025-02-22 03:33:19 +00:00
zxq5 bb5bf9115b fixed weird merge issues
Continuous integration / build (push) Has been cancelled
2025-02-22 03:29:33 +00:00
zxq5 ae82e9c24b idk 2025-02-22 03:27:08 +00:00
zxq5 ab0ec35094 updated submodules
Continuous integration / build (push) Has been cancelled
2025-02-22 03:24:48 +00:00
zxq5 2ee21dea05 did interrupts stuff 2025-02-22 03:19:05 +00:00
zxq5 d12160c5d0 setup TSS 2025-02-22 03:16:13 +00:00
nullndvoid f5f5aeb8dc Formatting changes, called 'cargo fmt'
Continuous integration / build (push) Has been cancelled
2025-02-22 03:09:46 +00:00
nullndvoid 34213ca744 Merge remote into local dev, I should probably find a new branch lol
Continuous integration / build (push) Has been cancelled
2025-02-22 02:44:09 +00:00
nullndvoid 3aca8fd720 Add some docs on building, bumped limine to latest version
No issues occurred having bumped the version, all seems well.
2025-02-22 02:41:23 +00:00
zxq5 a88059c5ff workflow builds nightly
Continuous integration / build (push) Has been cancelled
2025-02-22 02:13:38 +00:00
zxq5 68c3d2fa0e workflow changes
Continuous integration / build (push) Failing after 1m37s
2025-02-22 01:55:36 +00:00
zxq5 af3215d887 changed order of steps in rust.yml
Continuous integration / build (push) Failing after 23m3s
2025-02-22 01:28:31 +00:00
zxq5 11d63535e9 added submodule cloning to rust.yml
Continuous integration / build (push) Has been cancelled
2025-02-22 01:27:08 +00:00
zxq5 ee8dae4981 added rust.yml workflow, probably wont work.
Continuous integration / build (push) Failing after 7m45s
2025-02-22 01:12:15 +00:00
zxq5 650f34f354 removed redundant script 2025-02-22 01:08:07 +00:00
zxq5 1b313c67e0 first interrupt handler + setup IDT 2025-02-22 01:06:55 +00:00
zxq5 cb77883f4c done 2025-02-22 01:06:27 +00:00
zxq5 16d94f37cb moving libs to submodules 2025-02-22 01:05:18 +00:00
zxq5 d9f6b4b69a e 2025-02-22 00:58:51 +00:00
zxq5 cc56dc8072 idk 2025-02-22 00:58:40 +00:00
102 changed files with 4504 additions and 983 deletions
+15 -4
View File
@@ -1,13 +1,24 @@
[build] [build]
target = "x86_64-kernel" target = "x86_64-kernel.json"
target-dir = "build/target" target-dir = "build/target"
[unstable] [unstable]
build-std = ["core", "compiler_builtins"] build-std = ["core", "compiler_builtins", "alloc"]
build-std-features = ["compiler-builtins-mem"] build-std-features = ["compiler-builtins-mem"]
[env] [env]
RUST_TARGET_PATH = { value = "kernel", relative = true } RUST_TARGET_PATH = { value = "kernel", relative = true }
[target.x86_64-kernel] # Run in debug mode.
runner = "scripts/run.sh" [target.'cfg(all(target_arch = "x86_64", target_os = "none", debug_assertions))']
runner = "scripts/run_debug.sh"
# Otherwise we run in release mode.
[target.'cfg(all(target_arch = "x86_64", target_os = "none", not(debug_assertions)))']
runner = "scripts/run_release.sh"
# [registry]
# default = "gitea"
[registries.gitea]
index = "sparse+https://git.zxq5.dev/api/packages/OsDev/cargo/" # Sparse index
+22
View File
@@ -0,0 +1,22 @@
on:
push:
branches: [ main ]
pull-request:
branches: [ main ]
name: Continuous integration
jobs:
build:
name: build
runs-on: ubuntu-latest
steps:
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: x86_64-unknown-none
components: rust-src, llvm-tools-preview
toolchain: nightly
- uses: actions/checkout@v4
with:
submodules: true
- run: cargo build
+3
View File
@@ -4,6 +4,9 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/kernel/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/kernel/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/lib_example/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/lib_example/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/libk/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/libm/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/kernel_fonts/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
+5
View File
@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>
+10
View File
@@ -0,0 +1,10 @@
{
"rust-analyzer.cargo.allTargets": false,
"rust-analyzer.cargo.target": "x86_64-kernel.json",
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"rust-analyzer.check.command": "clippy",
"rust-analyzer.cargo.buildScripts.enable": true
}
Generated
+213 -35
View File
@@ -22,65 +22,157 @@ checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.14" version = "1.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
[[package]] [[package]]
name = "kernel" name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "darling"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "elf"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foundry_os"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"cc", "cc",
"lib_ascii", "crossbeam",
"lib_framebuffer", "elf",
"lib_serial", "futures-util",
"gimli",
"libm",
"limine", "limine",
"linked_list_allocator",
"pc-keyboard",
"pic8259",
"spin",
"x86_64",
] ]
[[package]] [[package]]
name = "lazy_static" name = "futures-core"
version = "1.5.0" version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [ dependencies = [
"spin", "futures-core",
"futures-task",
"pin-project-lite",
"pin-utils",
] ]
[[package]] [[package]]
name = "lib_application" name = "gimli"
version = "0.1.0" version = "0.31.1"
dependencies = [ source = "registry+https://github.com/rust-lang/crates.io-index"
"lib_ascii", checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
]
[[package]] [[package]]
name = "lib_ascii" name = "ident_case"
version = "0.1.0" version = "1.0.1"
dependencies = [ source = "registry+https://github.com/rust-lang/crates.io-index"
"lazy_static", checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
"lib_framebuffer",
"spin",
"x86_64",
]
[[package]] [[package]]
name = "lib_framebuffer" name = "libm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"limine", "darling",
"spin", "proc-macro2",
] "quote",
"syn",
[[package]]
name = "lib_serial"
version = "0.1.0"
dependencies = [
"lazy_static",
"spin",
"x86_64",
] ]
[[package]] [[package]]
@@ -92,6 +184,15 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "linked_list_allocator"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286"
dependencies = [
"spinning_top",
]
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@@ -102,6 +203,51 @@ dependencies = [
"scopeguard", "scopeguard",
] ]
[[package]]
name = "pc-keyboard"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0ca629cbb3f0d5b699c338f0129ff78c9bfd7ea8b1258ad529bff490dc8ed5a"
[[package]]
name = "pic8259"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62d9a86c292b165f757e47e7fd66855def189b2564609bc4203727b27c33db22"
dependencies = [
"x86_64",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[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.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.19" version = "1.0.19"
@@ -129,6 +275,38 @@ dependencies = [
"lock_api", "lock_api",
] ]
[[package]]
name = "spinning_top"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0"
dependencies = [
"lock_api",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "syn"
version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[package]] [[package]]
name = "volatile" name = "volatile"
version = "0.4.6" version = "0.4.6"
+7 -9
View File
@@ -1,19 +1,18 @@
[workspace] [workspace]
members = [ members = ["kernel", "libm"]
"lib/lib_framebuffer",
"lib/lib_serial",
"lib/lib_ascii",
"kernel"
, "lib/lib_application"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
authors = ["The FoundryOS Contributors"]
[profile.dev] [profile.dev]
opt-level = "z" opt-level = "z"
debug = true debug = true
# Leave this on to force Cargo to use the debug runner,
# which adds the necessary flags for GDB debugging if you
# set $USE_GDB (to any value) e.g. USE_GDB=1 cargo run
debug-assertions = true debug-assertions = true
overflow-checks = true overflow-checks = true
lto = false lto = false
@@ -21,11 +20,10 @@ incremental = false
codegen-units = 1 codegen-units = 1
[profile.release] [profile.release]
opt-level = "z" opt-level = 3
debug = false debug = false
debug-assertions = false debug-assertions = false
overflow-checks = false overflow-checks = false
lto = true lto = true
incremental = false incremental = false
codegen-units = 1 codegen-units = 1
+50
View File
@@ -1,2 +1,52 @@
# FoundryOS # FoundryOS
## Cloning
```sh
git clone https://git.zxq5.dev/OsDev/FoundryOS.git
```
## Build dependencies
* latest rust nightly release
* all necessary rust components installed
* xorriso: creates ISO images to be booted from.
* (Optional / Recommended) qemu: to run the kernel. (this may be packaged as qemu-desktop)
* (Optional) GDB: for debugging the kernel.
```sh
rustup update
rustup override set nightly
rustup component add rust-src
rustup component add llvm-tools-preview
```
## Building & Running in qemu
```sh
cargo run
```
## Running in GDB
```sh
USE_GDB=1 cargo run
```
## Build errors
If you see a qemu error like this:
```
qemu-system-x86_64: -drive if=pflash,format=raw,readonly=on,file=<...>/OVMF_CODE.fd: Could not open '<...>/OVMF_CODE.fd': No such file or directory
```
Simply delete the ./build directory and try to rebuild the program. Using the runner script or `cargo run` will download the required files for you, this is because the script only checks for the presence of one file and not the VARS file.
Alternatively, you may disable using a UEFI firmware with qemu like so:
```sh
USE_LEGACY_BIOS=1 cargo run
```
## Debugging
See [debugging](docs/Debugging/DEBUGGING.md) for some help with this, including commands to help with disassembly.
If you have any other issues, feel free to create an issue or a PR.
+1 -1
View File
@@ -1,5 +1,5 @@
# Timeout in seconds that Limine will use before automatically booting. # Timeout in seconds that Limine will use before automatically booting.
timeout: 1 timeout: 0
# The entry name that will be displayed in the boot menu. # The entry name that will be displayed in the boot menu.
/foundry-os /foundry-os
+17
View File
@@ -0,0 +1,17 @@
 Running kernel in debug mode.
info: Creating build directory structure
info: Copying files to ISO root
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/build/target/x86_64-kernel/debug/kernel to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/boot/kernel
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/config/limine.conf to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/boot/limine/limine.conf
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/build/limine/limine-bios-cd.bin to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/boot/limine/
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/build/limine/limine-uefi-cd.bin to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/boot/limine/
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/build/limine/limine-bios.sys to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/boot/limine/
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/build/limine/BOOTX64.EFI to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/EFI/BOOT/
 Copying: /home/fantasypvp/Projects/OSdev/FoundryOS/build/limine/BOOTIA32.EFI to /home/fantasypvp/Projects/OSdev/FoundryOS/build/iso_root/EFI/BOOT/
Building: bootable ISO image
info: Installing Limine bootloader
info: KVM acceleration enabled
info: Running OS in QEMU...
[=3h[=3h[=3hBdsDxe: loading Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
BdsDxe: starting Boot0001 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0)
map worked!got apic base
+3 -1
View File
@@ -1 +1,3 @@
{} {
"cssTheme": "Dark Moss"
}
+1
View File
@@ -0,0 +1 @@
[]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
{
"id": "obsidian-excalidraw-plugin",
"name": "Excalidraw",
"version": "2.8.3",
"minAppVersion": "1.1.6",
"description": "An Obsidian plugin to edit and view Excalidraw drawings",
"author": "Zsolt Viczian",
"authorUrl": "https://www.zsolt.blog",
"fundingUrl": "https://ko-fi.com/zsolt",
"helpUrl": "https://github.com/zsviczian/obsidian-excalidraw-plugin#readme",
"isDesktopOnly": false
}
File diff suppressed because one or more lines are too long
+20 -33
View File
@@ -4,41 +4,21 @@
"type": "split", "type": "split",
"children": [ "children": [
{ {
"id": "a20540b7c1ddbfca", "id": "c178a2dff57ae0aa",
"type": "tabs", "type": "tabs",
"children": [ "children": [
{ {
"id": "e1fb15cab546d0b6", "id": "6d395e6a4c72e1c8",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "markdown", "type": "markdown",
"state": { "state": {
"file": "Usage/Building The Kernel.md", "file": "Planning & To-Dos.md",
"mode": "source", "mode": "source",
"source": false "source": false
}, },
"icon": "lucide-file", "icon": "lucide-file",
"title": "Building The Kernel" "title": "Planning & To-Dos"
}
}
]
},
{
"id": "6bf1f87bf81a3031",
"type": "tabs",
"children": [
{
"id": "add883d295e04659",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "Usage/Building The Kernel.md",
"mode": "preview",
"source": false
},
"icon": "lucide-file",
"title": "Building The Kernel"
} }
} }
] ]
@@ -73,7 +53,7 @@
"state": { "state": {
"type": "search", "type": "search",
"state": { "state": {
"query": "tag:#usage", "query": "",
"matchingCase": false, "matchingCase": false,
"explainSearch": false, "explainSearch": false,
"collapseAll": false, "collapseAll": false,
@@ -98,8 +78,7 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 200, "width": 307.5
"collapsed": true
}, },
"right": { "right": {
"id": "baa2a8e61b737fa6", "id": "baa2a8e61b737fa6",
@@ -177,7 +156,8 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 300 "width": 300,
"collapsed": true
}, },
"left-ribbon": { "left-ribbon": {
"hiddenItems": { "hiddenItems": {
@@ -185,14 +165,21 @@
"graph:Open graph view": false, "graph:Open graph view": false,
"canvas:Create new canvas": false, "canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false, "daily-notes:Open today's daily note": false,
"templates:Insert template": false, "command-palette:Open command palette": false,
"command-palette:Open command palette": false "templates:Insert template": false
} }
}, },
"active": "e1fb15cab546d0b6", "active": "6d395e6a4c72e1c8",
"lastOpenFiles": [ "lastOpenFiles": [
"Usage/Building The Kernel.md",
"Planning & To-Dos.md",
"structure/idk.canvas",
"structure/kernel.md",
"structure/libk.md",
"structure/libm.md",
"structure/libk",
"structure",
"Usage", "Usage",
"Welcome.md", "Welcome.md"
"Usage/Building The Kernel.md"
] ]
} }
+16
View File
@@ -0,0 +1,16 @@
# Debugging the Kernel
Here we will add some helpful tips on debugging the kernel.
## Disassembling a public function
To disassemble a public function, first we need a symbol in the public symbol table, so start by making the function fully public (including any parent modules). Do this as though you are trying to make a public function for a library crate (this includes the `kernel` crate). Simply mark the function and any parent modules as public, up until the point of [lib.rs (kernel link)](../../kernel/src/lib.rs).
Then, we need to find the specific demangled symbol to disassemble, because the default objdump output can be very verbose.
```sh
# Change as required, I pipe to less and /SEARCH FOR FUNCTION HERE.
nm --demangle ./build/target/x86_64-kernel/debug/kernel | less
# Now just paste the symbol where it says YOUR_SYMBOL_HERE and profit. Use -Mintel for Intel assembly syntax.
objdump -Matt --source --line-numbers --visualize-jumps ./build/target/x86_64-kernel/debug/kernel --demangle=rust --disassemble="YOUR_SYMBOL_HERE"
```
+135
View File
@@ -0,0 +1,135 @@
> [!todo] Testing
> - [ ] setup custom test frameworks (Use CrystalOS-V2 as a reference)
> - [ ] write a sub-crate for tests
> - [ ] implement several unit tests on things that we are likely to break often
> - [ ] setup Git CI/CD pipeline with testing
> - [ ] figure out how to run QEMU in the test environment
> - [ ] write a script to run tests
> - [ ] a release should only be published if all tests pass
> [!todo] Memory
> - [ ] make sure page tables are correct. we're still getting the annoying page fault errors for the APIC
> - [ ] page allocator
> - [ ] e
> [!todo] Allocator
> - [x] learn about several allocator designs and decide on the optimal one
> - [x] implementation
> - [x] implement an allocate method
> - [x] implement a deallocate method
> - [x] set the global allocator
> - [ ] testing
> - [ ] write unit tests
> - [ ] many allocations
> - [ ] large allocations
> - [ ] allocating more memory than is available
> - [ ] pass unit tests
> [!todo] Threading
> - [ ] implement thread switching functionality
> - [ ] switch stacks
> - [ ] implement multi-threading
> - [ ] figure out how to turn on another core
> - [ ] Scheduler
> - [ ] which task goes on which thread?
> - [ ] round robin / time slicing algorithm
> - [ ] switch tasks on a given core
> - [ ] push registers
> - [ ] switch stacks
> - [ ] ensure the scheduler knows where to find the stack for each thread
> - [ ] move a given task to a new core
> - [ ] differentiate between kernel threads and user threads
> - [ ] create syscall to create a new thread
> [!todo] Filesystem
> - [ ] AHCI Implementation
> - [ ] figure out what hard drives are attached
> - [ ] read data
> - [ ] write data
> - [ ] FAT32 Implementation
> - [ ] parse FAT header
> - [ ] figure out where files are / what files are what
> - [ ] parse file metadata / figure out type of file
> - [ ] reading the entirety of a file into memory
> - [ ] *(extension)* ext2 Implementation
> - [ ] Syscall API
> - [ ] provide a user program with file read / write access given a filename
> - [ ] translate a filename into a disk location of data & the size of the data
> - [ ] provide an interface to read from a file or write to a file
> [!todo] APIC
> - [ ] Enable APIC
> - [x] Get apic location from MSR
> - [x] Enable APIC bit flag
> - [x] Disable Legacy PIC
> - [ ] Map physical APIC address to virtual memory
>
>> [!error]
>> Page fault exception occurs when APIC is activated
>
>- [ ] Setup Interrupt handlers to handle interrupts generated by the APIC
> - [ ] Timer Interrupts
> - [ ] Keyboard Interrupts
> [!todo] Userspace (req: Threading, Filesystem)
> - [ ] Load program into memory from disk
> - [ ] requires:
> - [ ] Program Loader
> - [ ] Parse ELF File
> - [ ] Load file into memory
> - [ ] Filesystem
> - [ ] Scheduler (to start and manage the process)
> - [ ] Start a user process
> - [ ] switch stacks
> - [ ] jump to user code
>[!todo] User Interface
>- This will be a library so that programs in userspace can provide a user interface rather than relying on text commands from a basic terminal
>- features required:
> - [ ] Rendering frames to a display buffer
> - [ ] requires: Frame rendering Syscall
> - [ ] I/O
> - [ ] syncio
> - [ ] keystrokes
> - [ ] asyncio
> - [ ] keystrokes
> - [ ] requires: I/O Syscalls
> - [ ] Window struct
> - [ ] stores it's size and location
> - [ ] handles rendering pixels to the screen
> - [ ] Widgets
> - [ ] textbox / label
> - [ ] single line text input
> - [ ] how do we capture keyboard inputs?
> - [ ] multiline text input
> - [ ] how do we capture keyboard inputs?
> - [ ] button
> - [ ] dialog
> - [ ] box widget
> - [ ] figure out how to nest other widgets inside it
>```rust
>pub struct Window {
> position;
> dimensions;
>}
>
>impl Window {
> fn display(&self, frame: &Frame) -> Result<(), RenderError>;
>}
>
>pub struct Frame<'a> {
> window: &'a Window,
> data: <"... a suitable type to store pixels">
>}
>
>impl<'a> Frame<'a> {
> pub fn display(&self) -> Result<(), RenderError> {
> self.window.display(self)
> }
>}
>
> pub trait Widget {
> fn render(&self) -> Frame;
> }
>```
+41
View File
@@ -0,0 +1,41 @@
{
"nodes":[
{"id":"8391ed519ab6cb3d","type":"group","x":-920,"y":600,"width":1595,"height":360,"label":"Hardware Layer"},
{"id":"967decfbdbf166e2","type":"group","x":-920,"y":120,"width":1595,"height":320,"label":"Kernel Layer (kernelspace)"},
{"id":"efd3cf75db396678","type":"group","x":-920,"y":-280,"width":1595,"height":240,"label":"System Layer (userspace)"},
{"id":"d577e6c4fa3d7479","type":"group","x":-920,"y":-600,"width":1595,"height":140,"label":"User Layer (userspace)"},
{"id":"549099b4188ff073","type":"text","text":"Interrupt handling & initialisation","x":-204,"y":320,"width":250,"height":60,"color":"2"},
{"id":"d0c73e9e86edba62","type":"text","text":"Kernel #kernel","x":-220,"y":260,"width":281,"height":140,"color":"2"},
{"id":"4fdc8ae730503ad0","type":"text","text":"libk #kernel","x":-880,"y":260,"width":280,"height":140,"color":"2"},
{"id":"4839a4f4f0980254","type":"text","text":"System Libraries #userspace","x":-220,"y":-240,"width":281,"height":160,"color":"3"},
{"id":"ba48ee2ccc891a62","type":"text","text":"Scheduler","x":-880,"y":160,"width":280,"height":60,"color":"2"},
{"id":"3d31fc276e0ba4a0","type":"text","text":"UI Shell #userspace","x":-880,"y":-560,"width":280,"height":60,"color":"4"},
{"id":"6b5e17c10589a7b6","type":"text","text":"Generic Application #userspace","x":-219,"y":-560,"width":281,"height":60,"color":"4"},
{"id":"0a47f47a2c7b3609","type":"text","text":"CPU #hardware","x":-880,"y":640,"width":280,"height":280,"color":"1"},
{"id":"a7002e4427f0540d","type":"text","text":"lib_io","x":-200,"y":-160,"width":122,"height":60,"color":"3"},
{"id":"e09d0bccffd19ed9","type":"text","text":"lib_ui","x":-78,"y":-160,"width":118,"height":60,"color":"3"},
{"id":"cbfeda8989b260d8","type":"text","text":"Userspace Drivers #userspace","x":360,"y":-240,"width":281,"height":160,"color":"3"},
{"id":"3acced35498279a0","type":"text","text":"Hardware #hardware\n- Display\n- Hard drives\n- Keyboard\n- Mouse","x":360,"y":640,"width":281,"height":280,"color":"1"},
{"id":"31548bb91b4977eb","type":"text","text":"Syscall API","x":-219,"y":160,"width":281,"height":60,"color":"2"},
{"id":"110b7bee2ba99819","type":"text","text":"Drivers & low level kernel libraries","x":-860,"y":320,"width":240,"height":60,"color":"2"}
],
"edges":[
{"id":"6a8d6b7540c2898d","fromNode":"3d31fc276e0ba4a0","fromSide":"right","toNode":"4839a4f4f0980254","toSide":"left","color":"3"},
{"id":"57aedf427a150ff1","fromNode":"6b5e17c10589a7b6","fromSide":"bottom","toNode":"4839a4f4f0980254","toSide":"top","color":"3","label":"Applications are statically \nlinked to system libraries"},
{"id":"d292a19f88032809","fromNode":"4839a4f4f0980254","fromSide":"bottom","toNode":"31548bb91b4977eb","toSide":"top","color":"2","label":"system libraries make syscalls"},
{"id":"8b437f1c08e1caf2","fromNode":"ba48ee2ccc891a62","fromSide":"top","toNode":"3d31fc276e0ba4a0","toSide":"bottom","color":"4","label":"Scheduler Starts\nUI in userspace"},
{"id":"3e2c6f6d5dcd7613","fromNode":"4839a4f4f0980254","fromSide":"right","toNode":"cbfeda8989b260d8","toSide":"left","color":"3"},
{"id":"88f6e95c288c933d","fromNode":"31548bb91b4977eb","fromSide":"top","toNode":"4839a4f4f0980254","toSide":"bottom","color":"2"},
{"id":"156c8a89b537e219","fromNode":"cbfeda8989b260d8","fromSide":"left","toNode":"4839a4f4f0980254","toSide":"right","color":"3","label":"some libs talk to\nhardware through\nuserspace drivers"},
{"id":"0d04c5b0f76a5d9b","fromNode":"cbfeda8989b260d8","fromSide":"bottom","toNode":"3acced35498279a0","toSide":"top","fromEnd":"arrow","color":"1","label":"Userspace drivers can talk\ndirectly to hardware so\nin the case of a driver crash\nthe kernel is unaffected"},
{"id":"e9f1afb45bf17b77","fromNode":"4fdc8ae730503ad0","fromSide":"bottom","toNode":"3acced35498279a0","toSide":"left","fromEnd":"arrow","color":"1","label":"Libk handles most hardware\ninteraction, keeping the core kernel codebase as minimal\nas possible"},
{"id":"99414d640e519c8c","fromNode":"4fdc8ae730503ad0","fromSide":"right","toNode":"d0c73e9e86edba62","toSide":"left","fromEnd":"arrow","color":"2","label":"kernel static linked to libk\nmost kernel functionality\nisfound in libk"},
{"id":"85cd865c914dd125","fromNode":"549099b4188ff073","fromSide":"top","toNode":"d0c73e9e86edba62","toSide":"top","color":"2"},
{"id":"e882694ec774c0d6","fromNode":"4fdc8ae730503ad0","fromSide":"top","toNode":"ba48ee2ccc891a62","toSide":"bottom","color":"2"},
{"id":"c689b667be8cdfab","fromNode":"d0c73e9e86edba62","fromSide":"top","toNode":"31548bb91b4977eb","toSide":"bottom","color":"2"},
{"id":"0c9da0cce11e4bd1","fromNode":"ba48ee2ccc891a62","fromSide":"bottom","toNode":"4fdc8ae730503ad0","toSide":"top","color":"2"},
{"id":"c45efb0ad498f8a8","fromNode":"ba48ee2ccc891a62","fromSide":"right","toNode":"6b5e17c10589a7b6","toSide":"left","color":"4","label":"Scheduler\nstarts applications"},
{"id":"aee2cd6ef9682f0f","fromNode":"31548bb91b4977eb","fromSide":"left","toNode":"ba48ee2ccc891a62","toSide":"right","color":"2","label":"system libs can talk to\nthe scheduler to manage\nprocesses"},
{"id":"b1b2b96d8ac2b490","fromNode":"4fdc8ae730503ad0","fromSide":"bottom","toNode":"0a47f47a2c7b3609","toSide":"top","fromEnd":"arrow","color":"1"}
]
}
+16
View File
@@ -0,0 +1,16 @@
- contains all proc macros for building the kernel
## Current Features
### font importer:
- a macro that takes a filename to a font file and turns it into a font.
```rust
pub struct Font {
width: usize,
height: usize,
length: u16,
data: [[u8; 16]; 512],
}
pub static FONT_CP850_8X16: Font =
Font::new(include_font!("./libk/resources/font/cp850-8x16.psf"));
```
+20 -6
View File
@@ -1,13 +1,26 @@
[package] [package]
name = "kernel" name = "foundry_os"
version = "0.1.0" edition.workspace = true
edition = "2021" version.workspace = true
authors.workspace = true
[dependencies] [dependencies]
limine = "0.3.1" limine = "0.3.1"
lib_framebuffer = { path = "../lib/lib_framebuffer" } x86_64 = "0.15.2"
lib_serial = { path = "../lib/lib_serial" } spin = "0.9.8"
lib_ascii = { path = "../lib/lib_ascii" } pic8259 = "0.11.0"
pc-keyboard = "0.8.0"
libm = { path = "../libm" }
crossbeam = { version = "0.8.4", default-features = false, features = [
"alloc",
"crossbeam-queue",
] }
futures-util = { version = "0.3.31", default-features = false, features = [
"alloc",
] }
linked_list_allocator = { version = "0.10.5", features = ["use_spin"] }
gimli = { version = "0.31.1", default-features = false, features = ["read"] }
elf = { version = "0.7.4", default-features = false, features = ["nightly"] }
[build-dependencies] [build-dependencies]
cc = "1.2.14" cc = "1.2.14"
@@ -18,3 +31,4 @@ default = []
[[bin]] [[bin]]
name = "kernel" name = "kernel"
path = "src/main.rs" path = "src/main.rs"
test = false
-4
View File
@@ -1,7 +1,3 @@
use std::process::Command;
use std::{env, path::Path};
use cc;
fn main() { fn main() {
// Tell cargo to rerun if these files change // Tell cargo to rerun if these files change
println!("cargo:rerun-if-changed=src"); println!("cargo:rerun-if-changed=src");
+1 -2
View File
@@ -62,9 +62,8 @@ SECTIONS
*(COMMON) *(COMMON)
} :data } :data
/* Discard .note.* and .eh_frame* since they may cause issues on some hosts. */ /* Discard .note.* and ~~.eh_frame*~~ since they may cause issues on some hosts. */
/DISCARD/ : { /DISCARD/ : {
*(.eh_frame*)
*(.note .note.*) *(.note .note.*)
} }
} }
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
pub mod x86_64;
+100
View File
@@ -0,0 +1,100 @@
#![expect(unused)]
use core::arch::x86_64::__cpuid;
use crate::arch::x86_64::memory::mapping::PHYSICAL_MEMORY_OFFSET;
use crate::{debugln, serial_print, serial_println};
use x86_64::{
PhysAddr, VirtAddr,
structures::paging::{Mapper, Page, PageTableFlags, PhysFrame, Size4KiB},
};
use x86_64::structures::paging::Translate;
use crate::arch::x86_64::cpu::msr::*;
use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
const IA32_APIC_BASE_MSR: u32 = 0x1b;
const IA32_APIC_BASE_MSR_BSP: u64 = 0x100;
const IA32_APIC_BASE_MSR_ENABLE: u64 = 0x800;
const IA32_APIC_BASE_MSR_DISABLE: u64 = !IA32_APIC_BASE_MSR_ENABLE;
const CPUID_FEAT_EDX_APIC: u64 = 1 << 9; // the cpuid instruction will return this flag if it supports APIC
// const APIC_VIRTUAL_ADDRESS: Lazy<VirtAddr> = Lazy::new(|| {
// let apic_base = get_apic_base();
// let virt_addr = unsafe { phys_to_virt(apic_base) };
// virt_addr
// });
fn set_apic_base_enable(apic: PhysAddr) {
let rax = (apic.as_u64() & 0xfffff0000) | IA32_APIC_BASE_MSR_ENABLE;
debugln!("apic {:?}", PhysAddr::new(rax));
cpu_set_msr(IA32_APIC_BASE_MSR, rax);
}
#[allow(dead_code)]
fn set_apic_base_disable(apic: PhysAddr) {
let rax = (apic.as_u64() & 0xfffff0000) & IA32_APIC_BASE_MSR_DISABLE;
cpu_set_msr(IA32_APIC_BASE_MSR, rax);
}
fn get_apic_base() -> PhysAddr {
let mut value: u64 = 0;
cpu_get_msr(IA32_APIC_BASE_MSR, &mut value);
debugln!("apic base {:#x}", value);
PhysAddr::new(value & 0xfffff0000)
}
fn write_apic_register(apic_base: &PhysAddr, reg: u64, value: u32) {
let apic_base = apic_base.as_u64();
let reg_addr = (apic_base & 0xFFFFF0000) + reg;
let virt_addr = unsafe { phys_to_virt(PhysAddr::new(reg_addr)) };
let phys_check = OFFSET_PAGE_TABLE.get().unwrap().lock().translate(virt_addr);
debugln!("{:?}", phys_check);
unsafe { *(virt_addr.as_u64() as *mut u32) = value };
}
fn read_apic_register(apic_base: &PhysAddr, reg: u64) -> u32 {
let apic_base = apic_base.as_u64();
let reg_addr = (apic_base & 0xFFFFF0000) + reg;
let virt_addr = unsafe { phys_to_virt(PhysAddr::new(reg_addr)) };
unsafe { *(virt_addr.as_u64() as *const u32) }
}
pub fn check_apic() -> bool {
let res = unsafe { __cpuid(1) };
res.edx as u64 & CPUID_FEAT_EDX_APIC != 0
}
#[inline(always)]
unsafe fn phys_to_virt(phys: PhysAddr) -> VirtAddr {
let phys = phys.as_u64();
phys.checked_add(*PHYSICAL_MEMORY_OFFSET)
.map_or_else(|| panic!(" overflow"), VirtAddr::new)
}
pub fn enable_apic() {
let apic_base_physical_addr = get_apic_base();
set_apic_base_enable(apic_base_physical_addr);
debugln!("{:?}", apic_base_physical_addr);
enable_timer();
write_apic_register(
&apic_base_physical_addr,
0xF0,
read_apic_register(&apic_base_physical_addr, 0xF0) | 0x1FF
);
}
pub fn enable_timer() {
let apic_base = get_apic_base();
let current = read_apic_register(&apic_base, 0x320);
write_apic_register(&get_apic_base(), 0x320, (current & 0xFFFFFF00) | 0x20);
}
+3
View File
@@ -0,0 +1,3 @@
pub mod apic;
mod msr;
pub mod pic;
+25
View File
@@ -0,0 +1,25 @@
#![expect(unused)]
use core::arch::x86_64::__cpuid;
use spin::Lazy;
use x86_64::registers::model_specific::Msr;
const CPUID_FLAG_MSR: u32 = 1 << 5;
static EDX: Lazy<u32> = Lazy::new(|| unsafe { __cpuid(1).edx });
pub fn cpu_has_msr() -> bool {
*EDX & CPUID_FLAG_MSR != 0
}
pub fn cpu_get_msr(msr: u32, value: &mut u64) {
let msr = Msr::new(msr);
unsafe {
*value = msr.read();
}
}
pub fn cpu_set_msr(msr: u32, value: u64) {
let mut msr = Msr::new(msr);
unsafe {
msr.write(value);
}
}
+162
View File
@@ -0,0 +1,162 @@
#![allow(clippy::missing_safety_doc)]
use x86_64::instructions::port::Port;
const CMD_INIT: u8 = 0x11;
const CMD_END_OF_INT: u8 = 0x20;
const MODE_8086: u8 = 0x01;
struct Pic {
offset: u8,
data: Port<u8>,
command: Port<u8>,
}
impl Pic {
/// Are we in charge of handling the specified interrupt?
/// (Each PIC handles 8 interrupts.)
const fn handles_interrupt(&self, interrupt_id: u8) -> bool {
self.offset <= interrupt_id && interrupt_id < self.offset + 8
}
/// Notify us that an interrupt has been handled and that we're ready
/// for more.
unsafe fn end_of_interrupt(&mut self) {
unsafe { self.command.write(CMD_END_OF_INT) };
}
/// Reads the interrupt mask of this PIC.
unsafe fn read_mask(&mut self) -> u8 {
unsafe { self.data.read() }
}
/// Writes the interrupt mask of this PIC.
unsafe fn write_mask(&mut self, mask: u8) {
unsafe { self.data.write(mask) }
}
}
/// A pair of chained PICs. This is the standard setup on x86.
pub struct ChainedPics {
pics: [Pic; 2],
}
impl ChainedPics {
/// Construct a new `ChainedPics` given the base offsets of the two PICs.
///
/// # Safety
///
/// This function is unsafe because it requires you to pass valid offsets.
/// If you pass offsets that are out of range, or if you pass offsets that
/// conflict with one another, the resulting object will be useless and
/// will likely cause problems for your system.
pub const unsafe fn new(offset1: u8, offset2: u8) -> Self {
Self {
pics: [
Pic {
offset: offset1,
command: Port::new(0x20),
data: Port::new(0x21),
},
Pic {
offset: offset2,
command: Port::new(0xA0),
data: Port::new(0xA1),
},
],
}
}
/// .
///
/// # Safety
///
/// .
pub const unsafe fn new_contiguous(primary_offset: u8) -> Self {
unsafe { Self::new(primary_offset, primary_offset + 8) }
}
/// Returns a new instance of [`ChainedPics`].
///
/// # Safety
///
/// this is unsafe because it required writing to CPU I/O ports.
pub unsafe fn initialize(&mut self) {
unsafe {
let mut wait_port: Port<u8> = Port::new(0x80);
let mut wait = || wait_port.write(0);
// Save our original interrupt masks, because I'm too lazy to
// figure out reasonable values. We'll restore these when we're
// done.
let saved_masks = self.read_masks();
// Tell each PIC that we're going to send it a three-byte
// initialization sequence on its data port.
self.pics[0].command.write(CMD_INIT);
wait();
self.pics[1].command.write(CMD_INIT);
wait();
// Byte 1: Set up our base offsets.
self.pics[0].data.write(self.pics[0].offset);
wait();
self.pics[1].data.write(self.pics[1].offset);
wait();
// Byte 2: Configure chaining between PIC1 and PIC2.
self.pics[0].data.write(4);
wait();
self.pics[1].data.write(2);
wait();
// Byte 3: Set our mode.
self.pics[0].data.write(MODE_8086);
wait();
self.pics[1].data.write(MODE_8086);
wait();
// Restore our saved masks.
self.write_masks(saved_masks[0], saved_masks[1])
}
}
/// Reads the interrupt masks of both PICs.
pub unsafe fn read_masks(&mut self) -> [u8; 2] {
unsafe { [self.pics[0].read_mask(), self.pics[1].read_mask()] }
}
/// Writes the interrupt masks of both PICs.
pub unsafe fn write_masks(&mut self, mask1: u8, mask2: u8) {
unsafe {
self.pics[0].write_mask(mask1);
self.pics[1].write_mask(mask2);
}
}
/// Disables both PICs by masking all interrupts.
pub unsafe fn disable(&mut self) {
unsafe { self.write_masks(u8::MAX, u8::MAX) }
}
/// Do we handle this interrupt?
pub fn handles_interrupt(&self, interrupt_id: u8) -> bool {
self.pics.iter().any(|p| p.handles_interrupt(interrupt_id))
}
/// Figure out which (if any) PICs in our chain need to know about this
/// interrupt. This is tricky, because all interrupts from `pics[1]`
/// get chained through `pics[0]`.
pub unsafe fn notify_end_of_interrupt(&mut self, interrupt_id: u8) {
if self.handles_interrupt(interrupt_id) {
if self.pics[1].handles_interrupt(interrupt_id) {
unsafe {
self.pics[1].end_of_interrupt();
}
}
unsafe {
self.pics[0].end_of_interrupt();
}
}
}
}
+248
View File
@@ -0,0 +1,248 @@
use core::fmt;
use spin::{Lazy, Mutex};
use x86_64::instructions::interrupts;
use crate::arch::x86_64::drivers::framebuffer::{
colour::Colour, display::FRAMEBUFFER_WRITER,
};
use crate::resources::font::{FONT_SPLEEN_8X16, Font};
static FONT_WIDTH: u32 = 8;
static FONT_HEIGHT: u32 = 16;
pub static WRITER: Lazy<Mutex<Writer>> =
Lazy::new(|| Mutex::new(Writer::new()));
pub fn screensize_chars() -> (u32, u32) {
let writer = WRITER.lock();
(writer.screen_width, writer.screen_height)
}
pub struct Writer {
font: &'static Font,
/// Measured in chars not pixels.
screen_width: u32,
/// Measured in chars not pixels.
screen_height: u32,
/// 16 pixels tall.
text_line: u32,
/// 8 pixels wide.
text_col: u32,
fg_color: Colour,
bg_color: Colour,
}
impl Default for Writer {
fn default() -> Self {
Self::new()
}
}
impl Writer {
pub fn new() -> Self {
FRAMEBUFFER_WRITER.lock().as_mut().map_or_else(
|| {
panic!("Framebuffer writer not initialized.");
},
|writer| Self {
font: &FONT_SPLEEN_8X16,
screen_width: writer.width() / 8,
screen_height: writer.height() / 16,
text_line: 0,
text_col: 0,
fg_color: Colour::White,
bg_color: Colour::Black,
},
)
}
pub const fn set_font(&mut self, font: &'static Font) {
self.font = font;
}
/// This is sent when the user types a backspace.
const BACKSPACE: u8 = 8;
pub fn write_glyph(&mut self, c: u8) {
if c == b'\n' {
self.newline();
return;
} else if c == Self::BACKSPACE {
self.backspace();
return;
}
// Get the character data from the font array. -- each byte is a row of
// pixels
let data: &[u8] = self.font.glyph_for(c as u16);
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
for (row, line) in data.iter().enumerate().take(16) {
for col in 0..8 {
let pixel_x: u32 = self.text_col * FONT_WIDTH + col;
let pixel_y: u32 =
self.text_line * FONT_HEIGHT + row as u32;
if line & (0x80 >> col) != 0 {
// Write the foreground color
writer.write_pixel(
pixel_x as usize,
pixel_y as usize,
self.fg_color,
);
} else {
// Write the background color
writer.write_pixel(
pixel_x as usize,
pixel_y as usize,
self.bg_color,
);
}
}
}
}
// Go to next position
if self.text_col + 1 >= self.screen_width {
self.newline();
} else {
self.text_col += 1;
}
}
pub const fn dimensions(&self) -> (u32, u32) {
(self.screen_width, self.screen_height)
}
pub const fn next_char(&mut self) {
self.text_col += 1;
}
pub const fn newline(&mut self) {
self.text_col = 0;
if self.text_line + 1 >= self.screen_height {
self.text_line = 0;
} else {
self.text_line += 1;
}
}
/// Handles the backspace character. TODO: Implement VT-100 style terminal
/// control codes alongside a shell. Not simple.
pub fn backspace(&mut self) {
if self.text_col > 0 {
self.text_col -= 1;
// Blank out the previous char.
self.write_glyph(b' ');
self.text_col -= 1;
}
}
pub fn write_string(&mut self, s: &str) {
for c in s.chars() {
self.write_glyph(c as u8);
}
}
pub const fn set_colour(&mut self, fg: Colour, bg: Colour) {
self.fg_color = fg;
self.bg_color = bg;
}
pub const fn reset_colour(&mut self) {
self.fg_color = Colour::White;
self.bg_color = Colour::Black;
}
}
impl fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.write_string(s);
Ok(())
}
}
fn write(args: fmt::Arguments, fg: Colour, bg: Colour) {
use core::fmt::Write;
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
writer.set_colour(fg, bg);
writer.write_fmt(args).unwrap();
writer.reset_colour();
});
}
pub fn _print(args: fmt::Arguments) {
interrupts::without_interrupts(|| {
write(args, Colour::White, Colour::Black);
})
}
pub fn _print_err(args: fmt::Arguments) {
interrupts::without_interrupts(|| {
write(args, Colour::Red, Colour::Black);
})
}
pub fn _print_log(args: fmt::Arguments) {
interrupts::without_interrupts(|| {
write(args, Colour::Yellow, Colour::Black);
})
}
pub fn clear_screen() {
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
writer.text_line = 0;
writer.text_col = 0;
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
writer.clear();
}
});
}
pub fn reset_cursor() {
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
writer.text_line = 0;
writer.text_col = 0;
});
}
#[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::prelude::_print_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::prelude::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! eprintln {
() => ($crate::printerr!("\n"));
($($arg:tt)*) => ($crate::eprint!("{}\n", format_args!($($arg)*)));
}
#[macro_export]
macro_rules! eprint {
($($arg:tt)*) => ($crate::prelude::_print_err(format_args!($($arg)*)));
}
@@ -0,0 +1,61 @@
#[repr(u32)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Colour {
ARGB(u8, u8, u8, u8),
RGB(u8, u8, u8),
HexARGB(u32),
Black = 0x000000FF,
Blue = 0x0000FFFF,
Green = 0x00FF00FF,
Cyan = 0x00FFFFFF,
Red = 0xFF0000FF,
Magenta = 0xFF00FFFF,
Yellow = 0xFFFF00FF,
White = 0xFFFFFFFF,
}
#[allow(clippy::use_self)]
impl From<Colour> for u32 {
fn from(val: Colour) -> Self {
match val {
Colour::ARGB(a, r, g, b) => {
(a as u32) << 24
| (r as u32) << 16
| (g as u32) << 8
| (b as u32)
}
Colour::RGB(r, g, b) => {
((r as u32) << 16) | (g as u32) << 8 | (b as u32)
}
Colour::HexARGB(hex) => hex,
Colour::Black => 0xFF000000,
Colour::Blue => 0xFF0000FF,
Colour::Green => 0xFF00FF00,
Colour::Cyan => 0xFF00FFFF,
Colour::Red => 0xFFFF0000,
Colour::Magenta => 0xFFFF00FF,
Colour::Yellow => 0xFFFFFF00,
Colour::White => 0xFFFFFFFF,
}
}
}
impl core::fmt::Display for Colour {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ARGB(r, g, b, a) => {
write!(f, "RGBA(#{:x}{:x}{:x}{:x})", r, g, b, a)
}
Self::RGB(r, g, b) => write!(f, "RGB(#{:x}{:x}{:x})", r, g, b),
Self::HexARGB(hex) => write!(f, "Hex(#{:x})", hex),
Self::Black => write!(f, "Black"),
Self::Blue => write!(f, "Blue"),
Self::Green => write!(f, "Green"),
Self::Cyan => write!(f, "Cyan"),
Self::Red => write!(f, "Red"),
Self::Magenta => write!(f, "Magenta"),
Self::Yellow => write!(f, "Yellow"),
Self::White => write!(f, "White"),
}
}
}
@@ -0,0 +1,107 @@
use limine::request::FramebufferRequest;
#[used]
#[unsafe(link_section = ".requests")]
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
use super::colour::Colour;
use core::panic;
use limine::framebuffer::Framebuffer;
use spin::{Lazy, Mutex};
pub static FRAMEBUFFER_WRITER: Lazy<Mutex<Option<FramebufferWriter>>> =
Lazy::new(|| {
Mutex::new(FRAMEBUFFER_REQUEST.get_response().map_or_else(
|| {
panic!("Framebuffer request failed");
},
|framebuffer_response| {
let framebuffer =
framebuffer_response.framebuffers().next().unwrap();
Some(FramebufferWriter::new(framebuffer))
},
))
});
/// The updated writer stores necessary fields from the [Framebuffer].
/// This ensures that the contained types are Send, as Framebuffer was
/// not marked as Send.
///
/// It also avoids the requirement for lifetimes.
///
/// Note this does not implement Writer as these functions only handle drawing
/// pixels.
pub struct FramebufferWriter {
pitch: u64,
bpp: u16,
addr: *mut u8,
width: u64,
height: u64,
}
unsafe impl Send for FramebufferWriter {}
unsafe impl Sync for FramebufferWriter {}
impl FramebufferWriter {
pub fn new(framebuffer: Framebuffer) -> Self {
Self {
pitch: framebuffer.pitch(),
bpp: framebuffer.bpp(),
addr: framebuffer.addr(),
width: framebuffer.width(),
height: framebuffer.height(),
}
}
pub fn write_pixel(&self, x: usize, y: usize, color: Colour) {
let pitch = self.pitch as usize;
let bpp = (self.bpp / 8) as usize;
let pixel_offset = y * pitch + x * bpp;
unsafe {
*(self.addr.add(pixel_offset) as *mut u32) = color.into();
}
}
pub fn render_frame(&self, buffer: &[&[Colour]]) {
// TODO: this should return errors
for (y, &row) in buffer.iter().enumerate() {
if y >= self.height() as usize {
break;
}
for (x, pixel) in row.iter().enumerate() {
if x >= self.width() as usize {
break;
}
self.write_pixel(x, y, *pixel);
}
}
}
pub const fn width(&self) -> u32 {
self.width as u32
}
pub const fn height(&self) -> u32 {
self.height as u32
}
pub fn clear(&self) {
let width = self.width as usize;
let height = self.height as usize;
for y in 0..height {
for x in 0..width {
self.write_pixel(x, y, Colour::Black);
}
}
}
}
pub fn screensize_px() -> (u32, u32) {
FRAMEBUFFER_WRITER
.lock()
.as_mut()
.map_or_else(|| (0, 0), |writer| (writer.width(), writer.height()))
}
@@ -0,0 +1,2 @@
pub mod colour;
pub mod display;
+209
View File
@@ -0,0 +1,209 @@
use core::{
pin::Pin,
task::{Context, Poll},
};
use crate::println;
use crossbeam::queue::ArrayQueue;
use futures_util::{Stream, StreamExt, task::AtomicWaker};
use pc_keyboard::{
DecodedKey, HandleControl, KeyCode, Keyboard, ScancodeSet1,
layouts::Uk105Key,
};
use spin::{Lazy, Mutex, Once};
static KBD_QUEUE: Once<ArrayQueue<u8>> = Once::new();
static WAKER: AtomicWaker = AtomicWaker::new();
pub static KEYBOARD: Lazy<Mutex<Keyboard<Uk105Key, ScancodeSet1>>> =
Lazy::new(|| {
Mutex::new(Keyboard::new(
ScancodeSet1::new(),
// TODO: Expose an API to change the default KB layout.
Uk105Key,
HandleControl::Ignore,
))
});
pub static SCANCODE_STREAM: Lazy<Mutex<ScancodeStream>> =
Lazy::new(|| Mutex::new(ScancodeStream::new()));
pub fn add_scancode(scancode: u8) {
if let Some(queue) = KBD_QUEUE.get() {
if queue.push(scancode).is_err() {
println!("WARNING: scancode queue full; dropping keyboard input");
} else {
WAKER.wake();
}
} else {
println!("WARNING: scancode queue not initialized");
}
}
pub struct ScancodeStream {
_private: (),
}
impl ScancodeStream {
pub fn new() -> Self {
KBD_QUEUE.call_once(|| ArrayQueue::new(5));
Self { _private: () }
}
pub fn try_next(&mut self) -> Option<u8> {
KBD_QUEUE.get().and_then(|queue| queue.pop())
}
}
impl Default for ScancodeStream {
fn default() -> Self {
Self::new()
}
}
impl Stream for ScancodeStream {
type Item = u8;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let queue = KBD_QUEUE.get().unwrap();
if let Some(scancode) = queue.pop() {
return Poll::Ready(Some(scancode));
}
WAKER.register(cx.waker());
WAKER.register(cx.waker());
queue.pop().map_or(Poll::Pending, |scancode| {
WAKER.take();
Poll::Ready(Some(scancode))
})
}
}
pub async fn get_keystroke_async() -> KeyStroke {
loop {
if let Some(scancode) = SCANCODE_STREAM.lock().next().await {
if let Ok(keystroke) = KeyStroke::try_from(scancode) {
return keystroke;
}
}
}
}
pub fn get_keystroke_optional() -> Option<KeyStroke> {
if let Some(scancode) = SCANCODE_STREAM.lock().try_next() {
if let Ok(keystroke) = KeyStroke::try_from(scancode) {
return Some(keystroke);
}
}
None
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeyStroke {
Char(char),
Ctrl,
RCtrl,
Alt,
RAlt,
Shift,
RShift,
Meta,
RMeta,
Backspace,
Left,
Right,
Up,
Down,
None,
Enter,
Escape,
Del,
}
impl KeyStroke {
pub const fn from_keycode(key: KeyCode) -> Self {
match key {
KeyCode::LControl => Self::Ctrl,
KeyCode::RControl => Self::RCtrl,
KeyCode::LAlt => Self::Alt,
KeyCode::RAlt2 => Self::RAlt,
KeyCode::LShift => Self::Shift,
KeyCode::RShift => Self::RShift,
KeyCode::LWin => Self::Meta,
KeyCode::RWin => Self::RMeta,
KeyCode::Backspace => Self::Backspace,
KeyCode::ArrowLeft => Self::Left,
KeyCode::ArrowRight => Self::Right,
KeyCode::ArrowUp => Self::Up,
KeyCode::ArrowDown => Self::Down,
KeyCode::Return => Self::Enter,
KeyCode::Escape => Self::Escape,
KeyCode::Delete => Self::Del,
_ => Self::None,
}
}
}
impl TryFrom<u8> for KeyStroke {
type Error = ();
fn try_from(code: u8) -> Result<Self, Self::Error> {
let mut keyboard = KEYBOARD.lock();
let key = match keyboard.add_byte(code) {
Ok(Some(event)) => match keyboard.process_keyevent(event) {
Some(key) => key,
_ => return Err(()),
},
_ => return Err(()),
};
match key {
DecodedKey::Unicode(ch) => Ok(Self::Char(ch)),
DecodedKey::RawKey(key) => match Self::from_keycode(key) {
Self::None => Err(()),
key => Ok(key),
},
}
}
}
impl TryInto<char> for KeyStroke {
type Error = ();
fn try_into(self) -> Result<char, Self::Error> {
match self {
Self::Char(c) => Ok(c),
_ => Err(()),
}
}
}
impl core::fmt::Display for KeyStroke {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Char(c) => write!(f, "{}", c),
Self::Ctrl => write!(f, "CTRL"),
Self::RCtrl => write!(f, "RCtrl"),
Self::Alt => write!(f, "ALT"),
Self::RAlt => write!(f, "RAlt"),
Self::Shift => write!(f, "SHIFT"),
Self::RShift => write!(f, "RShift"),
Self::Meta => write!(f, "META"),
Self::RMeta => write!(f, "RMeta"),
Self::Backspace => write!(f, "BACKSPACE"),
Self::Left => write!(f, "LEFT"),
Self::Right => write!(f, "RIGHT"),
Self::Up => write!(f, "UP"),
Self::Down => write!(f, "DOWN"),
Self::Enter => write!(f, "ENTER"),
Self::Escape => write!(f, "ESCAPE"),
Self::None => write!(f, "NONE"),
Self::Del => write!(f, "DEL"),
}
}
}
+5
View File
@@ -0,0 +1,5 @@
pub mod ascii;
pub mod framebuffer;
pub mod keyboard;
pub mod port;
pub mod serial;
+29
View File
@@ -0,0 +1,29 @@
//! Functions for IO using ports.
use core::arch::asm;
#[inline]
pub fn inb(port: u16) -> u8 {
let value: u8;
unsafe {
asm!(
"in al, dx",
out("al") value,
in("dx") port,
options(nomem, nostack, preserves_flags)
);
}
value
}
#[inline]
pub fn outb(port: u16, value: u8) {
unsafe {
asm!(
"out dx, al",
in("dx") port,
in("al") value,
options(nomem, nostack, preserves_flags)
);
}
}
@@ -1,21 +1,66 @@
#![no_std] use core::{
fmt,
sync::atomic::{AtomicUsize, Ordering},
};
use spin::{Lazy, Mutex};
use core::{fmt, sync::atomic::{AtomicUsize, Ordering}}; #[macro_export]
use spin::Mutex; macro_rules! serial_print {
use lazy_static::lazy_static; ($($arg:tt)*) => ($crate::_serial_write(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! serial_println {
() => ($crate::serial_print!("\n"));
($($arg:tt)*) => (serial_print!("{}\n", format_args!($($arg)*)));
}
use crate::arch::x86_64::drivers::port::{inb, outb};
mod io;
use io::*;
use x86_64::instructions::interrupts; use x86_64::instructions::interrupts;
pub fn _serial_write(args: fmt::Arguments) {
use core::fmt::Write;
interrupts::without_interrupts(|| {
if let Some(writer) = WRITER.lock().as_mut() {
writer.write_fmt(args).unwrap();
}
})
}
pub fn serial_read() -> &'static str {
serial_println!("getting value!");
interrupts::without_interrupts(|| {
if let Some(reader) = READER.lock().as_mut() {
serial_println!("stuff happnin.");
reader.read_str_to_buffer();
} else {
serial_println!("failed to get writer");
}
});
serial_println!("eee");
let i = BUFFER_LEN.load(Ordering::SeqCst);
unsafe {
if i != 0 {
core::str::from_utf8(&BUFFER[..i - 1]).unwrap()
} else {
serial_println!("empty string");
""
}
}
}
static PORT: u16 = 0x3f8; static PORT: u16 = 0x3f8;
static mut BUFFER: [u8; 256] = [0; 256]; static mut BUFFER: [u8; 256] = [0; 256];
static BUFFER_LEN: AtomicUsize = AtomicUsize::new(0); static BUFFER_LEN: AtomicUsize = AtomicUsize::new(0);
lazy_static!{ static READER: Lazy<Mutex<Option<Reader>>> = Lazy::new(|| Mutex::new(None));
static ref READER: Mutex<Option<Reader>> = Mutex::new(None); static WRITER: Lazy<Mutex<Option<Writer>>> = Lazy::new(|| Mutex::new(None));
static ref WRITER: Mutex<Option<Writer>> = Mutex::new(None);
}
struct Reader; struct Reader;
@@ -35,17 +80,16 @@ impl Writer {
inb(PORT + 5) & 0x20 != 0 inb(PORT + 5) & 0x20 != 0
} }
pub fn write_byte(&self, data: u8) { unsafe { pub fn write_byte(&self, data: u8) {
while !self.write_success() {}; unsafe {
outb(PORT + 0, data); while !self.write_success() {}
}} outb(PORT, data);
}
}
} }
pub fn init() -> Result<(), &'static str> { pub fn init() -> Result<(), &'static str> {
if let Err(e) = test() { test()?;
return Err(e);
}
if READER.lock().is_none() { if READER.lock().is_none() {
*READER.lock() = Some(Reader); *READER.lock() = Some(Reader);
@@ -59,30 +103,29 @@ pub fn init() -> Result<(), &'static str> {
} }
pub fn test() -> Result<(), &'static str> { pub fn test() -> Result<(), &'static str> {
unsafe {
outb(PORT + 1, 0x00); // Disable all interrupts outb(PORT + 1, 0x00); // Disable all interrupts
outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud outb(PORT, 0x03); // Set divisor to 3 (lo byte) 38400 baud
outb(PORT + 1, 0x00); // (hi byte) outb(PORT + 1, 0x00); // (hi byte)
outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-bytethreshold outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-bytethreshold
outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte) outb(PORT, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)
if inb(PORT + 0) != 0xAE { if inb(PORT) != 0xAE {
return Err("serial test failed") return Err("serial test failed");
} }
outb(PORT + 4, 0x0F); outb(PORT + 4, 0x0F);
}
Ok(()) Ok(())
} }
impl Reader { impl Reader {
pub fn read_str_to_buffer(&mut self) { unsafe { pub fn read_str_to_buffer(&mut self) {
while !self.read_ready() {}; unsafe {
while !self.read_ready() {}
BUFFER_LEN.store(0, Ordering::SeqCst); BUFFER_LEN.store(0, Ordering::SeqCst);
@@ -96,62 +139,17 @@ impl Reader {
} }
serial_println!("returning") serial_println!("returning")
}} }
}
unsafe fn read_ready(&self) -> bool { unsafe fn read_ready(&self) -> bool {
inb(PORT + 5) & 1 != 0 inb(PORT + 5) & 1 != 0
} }
pub fn read(&self) -> u8 { unsafe { pub fn read(&self) -> u8 {
while !self.read_ready() {}; unsafe {
return inb(PORT + 0); while !self.read_ready() {}
}} inb(PORT)
}
pub fn _serial_write(args: fmt::Arguments) {
use core::fmt::Write;
interrupts::without_interrupts(|| {
if let Some(writer) = WRITER.lock().as_mut() {
writer.write_fmt(args).unwrap();
}
})
}
#[macro_export]
macro_rules! serial_println {
() => ($crate::serial_print!("\n"));
($($arg:tt)*) => ($crate::serial_print!("{}\n", format_args!($($arg)*)));
}
#[macro_export]
macro_rules! serial_print {
($($arg:tt)*) => ($crate::_serial_write(format_args!($($arg)*)));
}
pub fn serial_read() -> &'static str {
serial_println!("getting value!");
interrupts::without_interrupts(|| {
if let Some(reader) = READER.lock().as_mut() {
serial_println!("stuff happnin.");
reader.read_str_to_buffer();
} else {
serial_println!("failed to get writer");
}
});
serial_println!("eee");
let i = BUFFER_LEN.load(Ordering::SeqCst);
return unsafe {
if i != 0 {
core::str::from_utf8(&BUFFER[..i - 1]).unwrap()
} else {
serial_println!("empty string");
""
} }
} }
} }
+69
View File
@@ -0,0 +1,69 @@
use x86_64::{
VirtAddr,
instructions::tables::load_tss,
registers::segmentation::{CS, DS, ES, SS, Segment},
structures::{
gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector},
tss::TaskStateSegment,
},
};
use spin::Lazy;
pub const DOUBLE_FAULT_1ST_INDEX: u16 = 0;
static TSS: Lazy<TaskStateSegment> = Lazy::new(|| {
let mut tss = TaskStateSegment::new();
tss.interrupt_stack_table[DOUBLE_FAULT_1ST_INDEX as usize] = {
const STACK_SIZE: usize = 4096 * 8;
static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE];
let stack_start = VirtAddr::from_ptr(&raw const STACK);
stack_start + STACK_SIZE.try_into().unwrap()
};
tss
});
static GDT: Lazy<(GlobalDescriptorTable, Selectors)> = Lazy::new(|| {
let mut gdt = GlobalDescriptorTable::new();
let code_selector = gdt.append(Descriptor::kernel_code_segment());
let data_selector = gdt.append(Descriptor::kernel_data_segment());
let user_code_selector = gdt.append(Descriptor::user_code_segment());
let user_data_selector = gdt.append(Descriptor::user_data_segment());
let tss_selector = gdt.append(Descriptor::tss_segment(&TSS));
(
gdt,
Selectors {
code_selector,
data_selector,
_user_code_selector: user_code_selector,
_user_data_selector: user_data_selector,
tss_selector,
},
)
});
struct Selectors {
code_selector: SegmentSelector,
data_selector: SegmentSelector,
_user_code_selector: SegmentSelector,
_user_data_selector: SegmentSelector,
tss_selector: SegmentSelector,
}
pub fn init() {
GDT.0.load();
unsafe {
CS::set_reg(GDT.1.code_selector);
load_tss(GDT.1.tss_selector);
DS::set_reg(self::GDT.1.data_selector);
ES::set_reg(self::GDT.1.data_selector);
SS::set_reg(self::GDT.1.data_selector);
}
}
+156
View File
@@ -0,0 +1,156 @@
use crate::{debug, serial_print};
use pic8259::ChainedPics;
use x86_64::registers::control::Cr2;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};
use x86_64::structures::paging::mapper::MapperFlushAll;
use x86_64::structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB};
use super::gdt;
use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
use crate::{println_log, serial_println};
use spin::{Lazy, Mutex};
static IDT: Lazy<InterruptDescriptorTable> = Lazy::new(|| {
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_1ST_INDEX);
}
idt.general_protection_fault
.set_handler_fn(general_protection_fault_handler);
idt.page_fault.set_handler_fn(page_fault_handler);
idt[InterruptIndex::Timer.as_u8()].set_handler_fn(timer_interrupt_handler);
idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler);
idt
});
pub const PIC_1_OFFSET: u8 = 32;
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
pub static PICS: Mutex<ChainedPics> =
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 {
const fn as_u8(self) -> u8 {
self as u8
}
fn _as_usize(self) -> usize {
usize::from(self.as_u8())
}
}
pub fn init_idt() {
IDT.load();
}
pub fn enable_pic() {
unsafe {
PICS.lock().initialize();
PICS.lock().write_masks(0xfc, 0xff);
}
}
pub fn disable_pic() {
unsafe {
PICS.lock().disable();
}
}
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
serial_println!("Exception: Breakpoint\n{:#?}", stack_frame);
println_log!("Exception: Breakpoint\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn general_protection_fault_handler(
stack_frame: InterruptStackFrame,
_error_code: u64,
) {
serial_println!("Exception: General Protection Fault\n{:#?}", stack_frame);
panic!("Exception: General Protection Fault\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn double_fault_handler(
stack_frame: InterruptStackFrame,
_error_code: u64,
) -> ! {
serial_println!("Exception: Double Fault\n{:#?}", stack_frame);
panic!("Exception: Double Fault\n{:#?}", stack_frame);
}
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
use pc_keyboard::{HandleControl, Keyboard, ScancodeSet1, layouts};
// use pc_keyboard::DecodedKey;
use spin::Mutex;
use x86_64::instructions::port::Port;
static KEYBOARD: Lazy<Mutex<Keyboard<layouts::Uk105Key, ScancodeSet1>>> = Lazy::new(|| {
Mutex::new(Keyboard::new(
ScancodeSet1::new(),
layouts::Uk105Key,
HandleControl::Ignore,
))
});
let _keyboard = KEYBOARD.lock();
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
crate::arch::x86_64::drivers::keyboard::add_scancode(scancode);
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
}
}
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
debug!("Timer Interrupt");
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
}
}
extern "x86-interrupt" fn page_fault_handler(
_stack_frame: InterruptStackFrame,
_error_code: PageFaultErrorCode,
) {
serial_println!("Exception: Page Fault");
serial_println!("Accessed Address: {:?}", Cr2::read());
serial_println!("Error Code: {:?}", _error_code);
serial_println!("{:#?}", _stack_frame);
if let Some(frame_allocator) = FRAME_ALLOCATOR.get() {
let mut f = frame_allocator.lock();
let frame = f.allocate_frame().unwrap();
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
let page: Page<Size4KiB> =
Page::containing_address(Cr2::read().unwrap());
unsafe {
let mut mapper = OFFSET_PAGE_TABLE.get().unwrap().lock();
match mapper.map_to(page, frame, flags, &mut *f) {
Ok(_) => {}
Err(why) => panic!("failed to map page: {:?}", why),
}
}
MapperFlushAll::new().flush_all();
} else {
panic!("failed to get frame allocator");
}
}
@@ -0,0 +1,306 @@
use crate::{debugln, serial_print};
use core::alloc::{GlobalAlloc, Layout};
use core::ptr;
use spin::{Mutex, MutexGuard};
use x86_64::structures::paging::{Size4KiB, mapper::MapToError, PageTableFlags};
use x86_64::VirtAddr;
use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, HEAP_SIZE, HEAP_VIRTUAL_SPACE};
use crate::arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator;
use crate::arch::x86_64::memory::units::MemoryUnits;
use crate::serial_println;
/// We are currently using a linked list heap allocator which uses our underlying page allocator.
#[global_allocator]
/// This is now Rust's global allocator, so we can use stuff requiring heap allocations.
static ALLOCATOR: Locked<FoundryAllocator> = Locked::new(FoundryAllocator::new());
/// Initializes the heap.
///
/// This function must be called once, before any functions that
/// require a heap are called. It sets up the heap by allocating
/// the necessary frames from the page allocator. The heap is
/// then ready for use.
///
/// # Safety
///
/// This function must be called only once, and must be called before any types
/// are constructed that require dynamic memory allocation.
///
/// # Parameters
///
/// None
///
/// # Returns
///
/// Returns a `Result` containing a `MapToError` if the heap
/// could not be initialized, or `Ok` if the heap was
/// initialized successfully.
pub unsafe fn init_heap() -> Result<(), MapToError<Size4KiB>> {
unsafe {
// code to allocate frames is now done in the page fault interrupt handler!
ALLOCATOR.lock().init(HEAP_VIRTUAL_SPACE, HEAP_SIZE);
Ok(())
}
}
pub struct Locked<T> {
inner: Mutex<T>,
}
impl<T> Locked<T> {
pub const fn new(inner: T) -> Self {
Self {
inner: Mutex::new(inner),
}
}
pub fn lock(&self) -> MutexGuard<T> {
self.inner.lock()
}
}
const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
struct ListNode {
next: Option<&'static mut ListNode>,
}
pub struct FoundryAllocator {
list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()],
fallback: Locked<FoundryFallbackAllocator>,
}
impl Default for FoundryAllocator {
fn default() -> Self {
Self::new()
}
}
impl FoundryAllocator {
pub const fn new() -> Self {
const EMPTY: Option<&'static mut ListNode> = None;
Self {
list_heads: [EMPTY; BLOCK_SIZES.len()],
fallback: Locked::new(FoundryFallbackAllocator::new()),
}
}
/// Initializes the fallback allocator with the given heap start address and size.
///
/// # Safety
///
/// This function is unsafe because it does not check whether the given heap start address and size are valid.
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
debugln!(" => Start: {:?}", VirtAddr::new(heap_start as u64));
debugln!(" => Size: {}", MemoryUnits::from_bytes(heap_size));
unsafe {
self.fallback.lock().init(heap_start, heap_size);
}
}
unsafe fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 {
unsafe { self.fallback.alloc(layout) }
}
fn block_size(&self, layout: &Layout) -> Option<usize> {
let required_block_size = layout.size().max(layout.align());
BLOCK_SIZES.iter().position(|&s| s >= required_block_size)
}
}
unsafe impl GlobalAlloc for Locked<FoundryAllocator> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut allocator = self.lock();
match allocator.block_size(&layout) {
Some(index) => {
match allocator.list_heads[index].take() {
Some(node) => {
allocator.list_heads[index] = node.next.take();
let ptr = node as *mut ListNode as *mut u8;
ensure_mapped(VirtAddr::new(ptr as u64));
ptr
}
None => {
// no block exists in list => allocate new block
let block_size = BLOCK_SIZES[index];
// only works if all block sizes are a power of 2
let block_align = block_size;
let layout = Layout::from_size_align(block_size, block_align).unwrap();
unsafe { allocator.fallback_alloc(layout) }
}
}
}
None => unsafe { allocator.fallback_alloc(layout) },
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let mut allocator = self.lock();
match allocator.block_size(&layout) {
Some(idx) => {
let new_node = ListNode {
next: allocator.list_heads[idx].take(),
};
let new_ptr = ptr as *mut ListNode;
unsafe { new_ptr.write(new_node) };
allocator.list_heads[idx] = Some(unsafe { &mut *new_ptr });
}
None => {
unsafe { allocator.fallback.dealloc(ptr, layout) };
}
}
}
}
struct FallbackListNode {
size: usize,
next: Option<&'static mut FallbackListNode>,
}
impl FallbackListNode {
const fn new(size: usize) -> Self {
Self { size, next: None }
}
fn start_addr(&self) -> usize {
self as *const Self as usize
}
fn end_addr(&self) -> usize {
self.start_addr() + self.size
}
}
pub struct FoundryFallbackAllocator {
head: FallbackListNode,
}
impl Default for FoundryFallbackAllocator {
fn default() -> Self {
Self::new()
}
}
impl FoundryFallbackAllocator {
pub const fn new() -> Self {
Self {
head: FallbackListNode::new(0),
}
}
/// Initializes the allocator with a given heap start address and size.
///
/// # Safety
///
/// This function is unsafe because it does not check whether the given heap start address and size are valid.
pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) {
unsafe { self.add_region(heap_start, heap_size) };
}
unsafe fn add_region(&mut self, addr: usize, size: usize) {
unsafe {
let mut node = FallbackListNode::new(size);
node.next = self.head.next.take();
let virt_addr = VirtAddr::new(addr as u64);
ensure_mapped(virt_addr);
let node_ptr = addr as *mut FallbackListNode;
node_ptr.write(node);
self.head.next = Some(&mut *node_ptr);
}
}
fn find_region(
&mut self,
size: usize,
align: usize,
) -> Option<(&'static mut FallbackListNode, usize)> {
let mut current = &mut self.head;
// look for a large enough memory region in linked list
while let Some(ref mut region) = current.next {
if let Ok(alloc_start) = Self::alloc_from_region(region, size, align) {
// region suitable for allocation -> remove node from list
let next = region.next.take();
let ret = Some((current.next.take().unwrap(), alloc_start));
current.next = next;
return ret;
} else {
// region not suitable -> continue with next region
current = current.next.as_mut().unwrap();
}
}
None
}
const fn align_up(addr: usize, align: usize) -> usize {
(addr + align - 1) & !(align - 1)
}
fn size_align(layout: Layout) -> (usize, usize) {
let layout = layout
.align_to(align_of::<ListNode>())
.expect("adjusting alignment failed")
.pad_to_align();
let size = layout.size().max(size_of::<ListNode>());
(size, layout.align())
}
fn alloc_from_region(
region: &FallbackListNode,
size: usize,
align: usize,
) -> Result<usize, ()> {
let alloc_start = Self::align_up(region.start_addr(), align);
let alloc_end = alloc_start.checked_add(size).ok_or(())?;
if alloc_end > region.end_addr() {
return Err(());
}
let excess_size = region.end_addr() - alloc_end;
if excess_size > 0 && excess_size < size_of::<ListNode>() {
return Err(());
}
Ok(alloc_start)
}
}
unsafe impl GlobalAlloc for Locked<FoundryFallbackAllocator> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut allocator = self.lock();
// perform layout adjustments
let (size, align) = FoundryFallbackAllocator::size_align(layout);
if let Some((region, alloc_start)) = allocator.find_region(size, align) {
let alloc_end = alloc_start.checked_add(size).expect("overflow");
let excess_size = region.end_addr() - alloc_end;
if excess_size > 0 {
unsafe { allocator.add_region(alloc_end, excess_size) };
}
let ptr = alloc_start as *mut u8;
ensure_mapped(VirtAddr::new(ptr as u64));
ptr
} else {
ptr::null_mut()
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
let mut allocator = self.lock();
// perform layout adjustments
let (size, _) = FoundryFallbackAllocator::size_align(layout);
unsafe { allocator.add_region(ptr as usize, size) }
}
}
fn ensure_mapped(virt_addr: VirtAddr) {
if ! FoundryOSFrameAllocator::is_mapped(virt_addr) {
let mut foundry_alloc = FRAME_ALLOCATOR.get().unwrap().lock();
foundry_alloc.allocate_page(virt_addr, PageTableFlags::PRESENT | PageTableFlags::WRITABLE).unwrap();
}
}
@@ -0,0 +1,3 @@
pub mod heap_alloc;
pub(crate) mod page_alloc;
pub mod stack_alloc;
@@ -0,0 +1,89 @@
use x86_64::structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, PhysFrame, Size4KiB, Translate};
use limine::response::MemoryMapResponse;
use spin::Mutex;
use limine::memory_map::EntryType;
use x86_64::{PhysAddr, VirtAddr};
use x86_64::structures::paging::mapper::{MapToError, TranslateResult};
use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE};
pub struct FoundryOSFrameAllocator {
memory_map: &'static MemoryMapResponse,
next: usize,
}
impl FoundryOSFrameAllocator {
/// Creates a new `FoundryOSFrameAllocator` from a memory map.
///
/// This function takes a reference to a `MemoryMapResponse` and initializes
/// a `FoundryOSFrameAllocator` with it. The `next` field is set to 0,
/// indicating that the first frame to be allocated is the first frame
/// in the memory map.
pub fn init(memory_map: &'static MemoryMapResponse) {
FRAME_ALLOCATOR.call_once(|| {
Mutex::new(Self {
memory_map,
next: 0,
})
});
}
pub fn count_usable_frames(&self) -> u64 {
self.usable_frames().count() as u64
}
pub fn available_memory(&self) -> u64 {
self.memory_map
.entries()
.iter()
.map(|region| region.base..region.base + region.length)
.flat_map(|r| r.step_by(4096)).count() as u64 * 4096
}
/// An iterator over all usable frames in the memory map.
///
/// Yields one `PhysFrame` for each available 4KiB frame in the memory map.
///
/// This function is used to allocate frames for the page map.
fn usable_frames(&self) -> impl Iterator<Item = PhysFrame> + use<> {
let regions = self.memory_map.entries().iter();
let usable_regions = regions.filter(|region| region.entry_type == EntryType::USABLE);
let addr_ranges = usable_regions.map(|region| region.base..region.base + region.length);
let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096));
frame_addresses.map(|addr| PhysFrame::from_start_address(PhysAddr::new(addr)).unwrap())
}
pub(crate) fn is_mapped(virt_addr: VirtAddr) -> bool {
let mapper = OFFSET_PAGE_TABLE.get().unwrap().lock();
matches!(mapper.translate(virt_addr), TranslateResult::Mapped { .. })
}
pub(crate) fn allocate_page(&mut self, start_addr: VirtAddr, flags: PageTableFlags) -> Result<(), MapToError<Size4KiB>> {
let page = Page::containing_address(start_addr);
let frame = self.allocate_frame().ok_or(MapToError::<Size4KiB>::FrameAllocationFailed)?;
let mut mapper = OFFSET_PAGE_TABLE.get().unwrap().lock();
unsafe {
mapper.map_to(page, frame, flags, self)?
}.flush();
Ok(())
}
}
unsafe impl FrameAllocator<Size4KiB> for FoundryOSFrameAllocator {
/// Allocates a frame from the list of usable frames.
///
/// This function returns the next available `PhysFrame` from the memory
/// map, if one exists. Once a frame is allocated, the internal counter
/// is incremented to point to the next frame for future allocations.
///
/// # Returns
///
/// - `Some(PhysFrame)`: If a usable frame is available.
/// - `None`: If there are no more usable frames to allocate.
fn allocate_frame(&mut self) -> Option<PhysFrame> {
let frame = self.usable_frames().nth(self.next);
self.next += 1;
frame
}
}
@@ -0,0 +1,86 @@
use crate::arch::x86_64::memory::STACK_VIRTUAL_SPACE;
use x86_64::VirtAddr;
use x86_64::structures::paging::{
FrameAllocator, Mapper, Page, Size4KiB, mapper,
};
fn reserve_stack_memory(size_in_pages: u64) -> Page {
use core::sync::atomic::{AtomicU64, Ordering};
static STACK_ALLOC_NEXT: AtomicU64 =
AtomicU64::new(STACK_VIRTUAL_SPACE as u64);
let start_addr =
VirtAddr::new(STACK_ALLOC_NEXT.fetch_add(
size_in_pages * Page::<Size4KiB>::SIZE,
Ordering::Relaxed,
));
Page::from_start_address(start_addr)
.expect("`STACK_ALLOC_NEXT` not page aligned")
}
/// Allocates a stack in the virtual address space, mapped to physical pages.
///
/// This function allocates a stack in the virtual address space, mapped to
/// physical pages. The stack is allocated as a sequence of pages, with the
/// first page allocated as a guard page. The stack is then mapped to the
/// allocated physical frame.
///
/// The function takes the size of the stack in pages, a mutable reference to a
/// mapper, and a mutable reference to a frame allocator. It returns a `Result`
/// containing a `StackBounds` struct, which contains the start and end virtual
/// addresses of the allocated stack.
///
/// # Safety
///
/// This function is unsafe because it maps physical frames to virtual addresses
/// without any protection. This can lead to bugs if the physical frames are not
/// correctly allocated, or if the virtual addresses are not correctly aligned.
///
/// # Panics
///
/// This function will panic if the allocation of the physical frame fails.
pub unsafe fn alloc_stack(
size_in_pages: u64,
mapper: &mut impl Mapper<Size4KiB>,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) -> Result<StackBounds, mapper::MapToError<Size4KiB>> {
unsafe {
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::FrameAllocationFailed)?;
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(),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct StackBounds {
start: VirtAddr,
end: VirtAddr,
}
impl StackBounds {
pub const fn new(start: VirtAddr, end: VirtAddr) -> Self {
Self { start, end }
}
pub const fn start(&self) -> VirtAddr {
self.start
}
pub const fn end(&self) -> VirtAddr {
self.end
}
}
+56
View File
@@ -0,0 +1,56 @@
//! Sets up a memory map using Limine.
use limine::{
request::{HhdmRequest, KernelAddressRequest, MemoryMapRequest},
response::MemoryMapResponse,
};
use spin::Lazy;
#[used]
#[unsafe(link_section = ".requests")]
static MEMORY_MAP_REQUEST: MemoryMapRequest = MemoryMapRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static HIGHER_HALF_DIRECT_MAP_REQUEST: HhdmRequest = HhdmRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
pub static KERNEL_ADDRESS_REQUEST: KernelAddressRequest =
KernelAddressRequest::new();
/// ```rs
/// let virt_addr = phys_addr + offset;
/// let phys_addr = virt_addr - offset; // (given VA is in the HHDM). Do not use for executable code.
/// ```
pub static PHYSICAL_MEMORY_OFFSET: Lazy<u64> = Lazy::new(|| {
HIGHER_HALF_DIRECT_MAP_REQUEST
.get_response()
.unwrap()
.offset()
});
/// Converts virtual addresses in the kernel to a physical address like this:
/// ```rs
/// let phys_addr = virt_addr - virtual_base + physical_base;
/// ```
///
/// Returns (virtual_base, physical_base)
pub static _KERNEL_PHYSICAL_MEMORY_OFFSET: Lazy<(u64, u64)> = Lazy::new(|| {
let resp = KERNEL_ADDRESS_REQUEST.get_response().unwrap();
// These are base addresses, using Limine's built in page table.
(resp.virtual_base(), resp.physical_base())
});
/// Fetches the memory map from Limine.
///
/// # Panics
///
/// Panics if the memory map was not found in MEMORY_MAP_REQUEST.
pub fn get_memory_map() -> &'static MemoryMapResponse {
MEMORY_MAP_REQUEST.get_response().map_or_else(
|| unreachable!("Could not fetch memory map from Limine."),
|memory_map| memory_map,
)
}
+62
View File
@@ -0,0 +1,62 @@
pub mod allocation;
pub mod mapping;
pub mod units;
use allocation::page_alloc::FoundryOSFrameAllocator;
use spin::{Mutex, Once};
use units::MemoryUnits::*;
use x86_64::{
VirtAddr,
registers::control::Cr3,
structures::paging::{OffsetPageTable, PageTable},
};
pub const STACK_VIRTUAL_SPACE: usize = 0x5555_5555_0000; // start address of the memory space where we store allocated stacks
pub const HEAP_VIRTUAL_SPACE: usize = 0x4444_4444_0000; // start address of heap allocated memory
pub const HEAP_SIZE: usize = MiB(1).to_bytes();
pub static FRAME_ALLOCATOR: Once<Mutex<FoundryOSFrameAllocator>> = Once::new();
pub static OFFSET_PAGE_TABLE: Once<Mutex<OffsetPageTable>> = Once::new();
/// Returns a mutable reference to the current level 4 page table.
///
/// # Safety
///
/// The caller must ensure that the level 4 page table is not modified
/// simultaneously. The caller must also ensure that the physical memory offset
/// is correct, to ensure that the correct virtual address is constructed.
unsafe fn active_l4_table(
physical_memory_offset: VirtAddr,
) -> &'static mut PageTable {
let (level_4_frame, _) = Cr3::read();
let phys_addr = level_4_frame.start_address();
let virt = phys_addr.as_u64() + physical_memory_offset.as_u64();
unsafe { &mut *(virt as *mut PageTable) }
}
/// Initializes the `OffsetPageTable` for the current CPU architecture.
///
/// # Safety
///
/// This function must be called only once and should be called before any
/// memory operations are performed that rely on virtual memory management.
/// The provided `physical_memory_offset` must be accurate to ensure correct
/// translation of physical addresses.
///
/// # Parameters
///
/// - `physical_memory_offset`: The offset to convert physical addresses to
/// virtual addresses in the higher-half direct map.
///
/// # Returns
///
/// Returns an `OffsetPageTable` that allows for manipulation of the page
/// tables for the current CPU architecture.
pub fn init_page_table(physical_memory_offset: VirtAddr) {
unsafe {
let l4_table = active_l4_table(physical_memory_offset);
let offset_table =
OffsetPageTable::new(l4_table, physical_memory_offset);
OFFSET_PAGE_TABLE.call_once(|| Mutex::new(offset_table));
}
}
+49
View File
@@ -0,0 +1,49 @@
pub enum MemoryUnits {
B(usize),
KiB(usize),
MiB(usize),
GiB(usize),
}
impl MemoryUnits {
pub const fn to_bytes(&self) -> usize {
match self {
Self::B(b) => *b,
Self::KiB(kib) => *kib * 1024,
Self::MiB(mib) => *mib * 1024 * 1024,
Self::GiB(gib) => *gib * 1024 * 1024 * 1024,
}
}
pub const fn from_bytes(bytes: usize) -> Self {
if bytes < 1024 {
Self::B(bytes)
} else if bytes < 1024 * 1024 {
Self::KiB(bytes / 1024)
} else if bytes < 1024 * 1024 * 1024 {
Self::MiB(bytes / (1024 * 1024))
} else {
Self::GiB(bytes / (1024 * 1024 * 1024))
}
}
pub const fn convert(&mut self) {
match self {
Self::B(b) if *b > 1024 => *self = Self::KiB(*b / 1024),
Self::KiB(kib) if *kib > 1024 => *self = Self::MiB(*kib / 1024),
Self::MiB(mib) if *mib > 1024 => *self = Self::GiB(*mib / 1024),
_ => (),
}
}
}
impl core::fmt::Display for MemoryUnits {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::B(b) => write!(f, "{} B", b),
Self::KiB(kib) => write!(f, "{} KiB", kib),
Self::MiB(mib) => write!(f, "{} MiB", mib),
Self::GiB(gib) => write!(f, "{} GiB", gib),
}
}
}
+6
View File
@@ -0,0 +1,6 @@
pub mod cpu;
pub mod drivers;
pub mod gdt;
pub mod interrupts;
pub mod memory;
pub mod processing;
@@ -0,0 +1 @@
pub mod task;
@@ -0,0 +1,150 @@
//! Allows creation of asynchronous IO bound tasks.
//!
//! Written by @zxq5 for the most part with code from
//! [here](https://github.com/phil-opp/blog_os/).
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::sync::Arc;
use alloc::task::Wake;
use core::{
future::Future,
pin::Pin,
sync::atomic::AtomicU64,
task::{Context, Poll, Waker},
};
use crossbeam::queue::ArrayQueue;
use x86_64::instructions::interrupts::{self, enable_and_hlt};
pub struct Task {
id: TaskId,
future: Pin<Box<dyn Future<Output = ()>>>,
}
impl Task {
pub fn new(future: impl Future<Output = ()> + '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: AtomicU64 = AtomicU64::new(0);
Self(NEXT.fetch_add(1, core::sync::atomic::Ordering::Relaxed))
}
}
pub struct Executor {
tasks: BTreeMap<TaskId, Task>,
task_queue: Arc<ArrayQueue<TaskId>>,
waker_cache: BTreeMap<TaskId, Waker>,
}
impl Executor {
pub fn new() -> Self {
Self {
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!("task with same id already in tasks");
}
self.task_queue.push(task_id).expect("queue full");
}
fn run_ready_tasks(&mut self) {
// destructure `self` to avoid borrow checker errors
let Self {
tasks,
task_queue,
waker_cache,
} = self;
while let Some(task_id) = task_queue.pop() {
let task = match tasks.get_mut(&task_id) {
Some(task) => task,
None => continue, // task no longer exists
};
let waker = waker_cache.entry(task_id).or_insert_with(|| {
TaskWaker::new_waker(task_id, task_queue.clone())
});
let mut context = Context::from_waker(waker);
match task.poll(&mut context) {
Poll::Ready(()) => {
// task done -> remove it and its cached waker
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) {
interrupts::disable();
if self.task_queue.is_empty() {
enable_and_hlt();
} else {
interrupts::enable();
}
}
}
impl Default for Executor {
fn default() -> Self {
Self::new()
}
}
struct TaskWaker {
task_id: TaskId,
task_queue: Arc<ArrayQueue<TaskId>>,
}
impl TaskWaker {
fn wake_task(&self) {
self.task_queue.push(self.task_id).expect("task_queue full");
}
fn new_waker(
task_id: TaskId,
task_queue: Arc<ArrayQueue<TaskId>>,
) -> Waker {
Waker::from(Arc::new(Self {
task_id,
task_queue,
}))
}
}
impl Wake for TaskWaker {
fn wake(self: Arc<Self>) {
self.wake_task();
}
fn wake_by_ref(self: &Arc<Self>) {
self.wake_task();
}
}
+4
View File
@@ -0,0 +1,4 @@
#![expect(unused)]
pub mod async_io;
mod taskrunner;
pub mod threading;
@@ -0,0 +1,12 @@
use alloc::boxed::Box;
pub struct Task {
pid: usize,
task: Box<dyn Fn() + Send + 'static>,
}
impl Task {
// pub const fn new() {}
pub const fn run() {}
}
@@ -0,0 +1,116 @@
mod switch;
mod deprecated;
use core::arch::asm;
#[repr(C)]
pub struct Thread {
id: usize,
/// This shall be default before the program is interrupted, otherwise it will store
/// CPU registers etc to be restored on context switch.
ctx: ThreadContext,
}
/// CPU state to be saved on context switches.
#[repr(C)]
#[derive(Default)]
pub struct ThreadContext {
/// Accumulator register.
rax: u64,
/// Base register.
rbx: u64,
/// Counter register.
rcx: u64,
/// Data register.
rdx: u64,
/// Source index register.
rsi: u64,
/// Destination index register.
rdi: u64,
/// Base pointer register.
rbp: u64,
/// Stack pointer register.
rsp: u64,
/// An extended register.
r8: u64,
/// An extended register.
r9: u64,
/// An extended register.
r10: u64,
/// An extended register.
r11: u64,
/// An extended register.
r12: u64,
/// An extended register.
r13: u64,
/// An extended register.
r14: u64,
/// An extended register.
r15: u64,
/// The instruction pointer.
rip: u64,
/// RFLAGS register.
rflags: u64,
}
impl ThreadContext {
/// Saves the current registers of the CPU before a context switch
/// to be restored later.
///
/// # Notes
///
/// This function should ONLY be called in interrupt handlers such
/// as that of the timer. This will then save registers as required
///
///
/// # Safety
///
/// This function is unsafe because of the usage of inline ASM.
#[inline(always)]
pub unsafe fn save_registers() -> Self {
let mut context = Self::default();
unsafe {
asm!(
"mov {0}, rax",
"mov {1}, rbx",
"mov {2}, rcx",
"mov {3}, rdx",
"mov {4}, rsi",
"mov {5}, rdi",
"mov {6}, rbp",
"mov {7}, rsp",
"mov {8}, r8",
"mov {9}, r9",
"mov {10}, r10",
"mov {11}, r11",
"mov {12}, r12",
"mov {13}, r13",
"mov {14}, r14",
"mov {15}, r15",
"lea {16}, [rip]",
"pushf",
"pop {17}",
out(reg) context.rax,
out(reg) context.rbx,
out(reg) context.rcx,
out(reg) context.rdx,
out(reg) context.rsi,
out(reg) context.rdi,
out(reg) context.rbp,
out(reg) context.rsp,
out(reg) context.r8,
out(reg) context.r9,
out(reg) context.r10,
out(reg) context.r11,
out(reg) context.r12,
out(reg) context.r13,
out(reg) context.r14,
out(reg) context.r15,
out(reg) context.rip,
out(reg) context.rflags,
);
}
context
}
}
@@ -0,0 +1,26 @@
use crate::arch::x86_64::memory::allocation::stack_alloc::StackBounds;
use x86_64::VirtAddr;
mod switch;
#[derive(Debug)]
pub struct Thread {
id: ThreadId,
stack_ptr: Option<VirtAddr>,
stack_bounds: Option<StackBounds>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ThreadId(u64);
impl ThreadId {
pub const fn as_u64(&self) -> u64 {
self.0
}
fn new() -> Self {
use core::sync::atomic::{AtomicU64, Ordering};
static NEXT_THREAD_ID: AtomicU64 = AtomicU64::new(1);
Self(NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed))
}
}
@@ -0,0 +1,13 @@
.intel_syntax noprefix
switch_thread:
pushfq
mov rax, rsp
mov rsp, rdi
mov rdi, rax
call add_paused_thread
popfq
ret
@@ -0,0 +1 @@
+149
View File
@@ -0,0 +1,149 @@
#![no_std]
#![feature(abi_x86_interrupt)]
#![warn(
clippy::correctness,
clippy::nursery,
clippy::unnecessary_cast,
clippy::all,
clippy::suspicious,
clippy::perf,
rustdoc::missing_errors_doc,
rustdoc::missing_panics_doc
)]
extern crate alloc;
use crate::{arch::x86_64::memory::init_page_table, prelude::*};
use arch::x86_64::memory::allocation::heap_alloc::init_heap;
use arch::x86_64::memory::mapping;
use core::arch::asm;
use limine::BaseRevision;
use std::unwind;
use std::unwind::eh_info::ELF;
use x86_64::VirtAddr;
use arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator;
use crate::arch::x86_64::cpu::apic::enable_apic;
use crate::arch::x86_64::drivers::ascii::screensize_chars;
use crate::arch::x86_64::drivers::framebuffer::display::screensize_px;
use crate::arch::x86_64::memory::FRAME_ALLOCATOR;
use crate::arch::x86_64::memory::units::MemoryUnits;
pub mod arch;
mod panic;
pub mod resources;
#[allow(unused)] // We aren't using much of this right now.
pub mod std;
pub mod util;
pub mod prelude {
pub use crate::{
eprint, eprintln, print, print_log, println, println_log, serial_print,
serial_println,
debug, debugln,
std::io::{_print, _print_log, _serial_write, _print_err},
std::debug::_debug,
};
}
/// Sets the base revision to the latest revision supported by the crate.
/// See specification for further info.
/// Be sure to mark all limine requests with #[used], otherwise they may be
/// removed by the compiler.
#[used]
// The .requests section allows limine to find the requests faster and more
// safely.
#[unsafe(link_section = ".requests")]
static BASE_REVISION: BaseRevision = BaseRevision::new();
#[panic_handler]
fn rust_panic(_info: &core::panic::PanicInfo) -> ! {
println!("Kernel panic: {}", _info);
serial_println!("Kernel panic: {}", _info);
hcf();
}
pub fn hcf() -> ! {
loop {
unsafe {
#[cfg(target_arch = "x86_64")]
asm!("hlt");
}
}
}
pub fn boot() -> Result<(), &'static str> {
if !BASE_REVISION.is_supported() {
return Err("Base revision not supported");
}
use arch::x86_64::{gdt, interrupts};
let memory_map = mapping::get_memory_map();
print_log!(" Initialising Serial... ");
let res = arch::x86_64::drivers::serial::init();
serial_print!(" Initialising Serial... ");
if res.is_err() {
debugln!("[Not Detected]")
} else {
debugln!("[Success]");
}
debugln!(" Display...");
let dimensions = screensize_chars();
let dimensions2 = screensize_px();
debugln!(" => (px) : {}x{} ", dimensions2.0, dimensions2.1);
debugln!(" => (chars) : {}x{} ", dimensions.0, dimensions.1);
debugln!(" [Success]");
debug!(" Setting Up Global Descriptor Table... ");
gdt::init();
debugln!("[Success]");
debug!(" Setting Up Interrupt Descriptor Table... ");
interrupts::init_idt();
debugln!("[Success]");
debugln!(" Initialising Memory Subsystem... ");
let physical_memory_offset = VirtAddr::new(*mapping::PHYSICAL_MEMORY_OFFSET);
init_page_table(physical_memory_offset);
FoundryOSFrameAllocator::init(memory_map);
let available_bytes = FRAME_ALLOCATOR.get().unwrap().lock().available_memory();
debugln!(" => Available Memory: {}", MemoryUnits::from_bytes(available_bytes as usize));
debugln!("[Success]");
debugln!(" Initialising Heap... ");
if unsafe { init_heap() }.is_err() {
return Err("Failed to initialise heap: error");
}
debugln!(" [Success]");
// debug!(" Enabling PICs... ");
// interrupts::enable_pic();
// debugln!("[Success]");
debug!(" Disabling PICs... ");
interrupts::disable_pic();
debugln!("[Success]");
debug!(" Initialising APIC... ");
enable_apic();
debugln!("[Success]");
debug!(" Enabling Interrupts... ");
x86_64::instructions::interrupts::enable();
debugln!("[Success]");
debug!(" Setting up stack unwinder, panic handler... ");
// Setup stack traces and proper panic handler. TODO: Handle panics
// differently if not initialised.
let eh_frame_ptr = ELF
.get_section_addr(".eh_frame_hdr")
.expect("Could not get `.eh_frame_hdr` address.");
let _eh_info =
unsafe { unwind::eh_info::EhInfo::from_hdr_ptr(eh_frame_ptr) };
debugln!("[Success]");
Ok(())
}
+20 -80
View File
@@ -1,89 +1,29 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use core::arch::asm; extern crate alloc;
use foundry_os::arch::x86_64::drivers::ascii::screensize_chars;
use foundry_os::arch::x86_64::drivers::framebuffer::display::screensize_px;
use foundry_os::arch::x86_64::processing::async_io::task::{Executor, Task};
use foundry_os::util::shell::shell;
use foundry_os::{println, println_log};
use lib_serial::{serial_println, serial_read}; #[unsafe(no_mangle)]
use lib_ascii::{print, println, WRITER}; extern "C" fn kmain() -> ! {
println_log!(" [ Initialising Kernel Systems ] ");
use limine::request::{RequestsEndMarker, RequestsStartMarker}; if let Err(err) = foundry_os::boot() {
use limine::BaseRevision; panic!("{}", err);
use lib_framebuffer;
/// Sets the base revision to the latest revision supported by the crate.
/// See specification for further info.
/// Be sure to mark all limine requests with #[used], otherwise they may be removed by the compiler.
#[used]
// The .requests section allows limine to find the requests faster and more safely.
#[link_section = ".requests"]
static BASE_REVISION: BaseRevision = BaseRevision::new();
/// Define the stand and end markers for Limine requests.
#[used]
#[link_section = ".requests_start_marker"]
static _START_MARKER: RequestsStartMarker = RequestsStartMarker::new();
#[used]
#[link_section = ".requests_end_marker"]
static _END_MARKER: RequestsEndMarker = RequestsEndMarker::new();
#[no_mangle]
unsafe extern "C" fn kmain() -> ! {
// All limine requests must also be referenced in a called function, otherwise they may be
// removed by the linker.
assert!(BASE_REVISION.is_supported());
if let Err(_) = lib_serial::init() {
loop {}
} }
let dimensions = lib_ascii::screensize_chars(); println_log!(" [ Kernel Initialised Successfully ] ");
let dimensions2 = lib_framebuffer::screensize_px();
println!("Hello World!");
println!("Dimensions: {}x{} (px)", dimensions2.0, dimensions2.1);
println!("Dimensions: {}x{} (chars)", dimensions.0, dimensions.1);
println!(" // println!("TESTING :: Allocation");
$$$$$$$$\\ $$\\ // let somevec = vec![0; 1_000_000];
$$ _____| $$ | // println!("{:?}", somevec);
$$ | $$$$$$\\ $$\\ $$\\ $$$$$$$\\ $$$$$$$ | $$$$$$\\ $$\\ $$\\ // println!("{}", somevec.len());
$$$$$\\ $$ __$$\\ $$ | $$ |$$ __$$\\ $$ __$$ |$$ __$$\\ $$ | $$ | // println!("PASSED!");
$$ __|$$ / $$ |$$ | $$ |$$ | $$ |$$ / $$ |$$ | \\__|$$ | $$ |
$$ | $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ | $$ |
$$ | \\$$$$$$ |\\$$$$$$ |$$ | $$ |\\$$$$$$$ |$$ | \\$$$$$$$ |
\\__| \\______/ \\______/ \\__| \\__| \\_______|\\__| \\____$$ |
$$$$$$\\ $$$$$$\\ $$\\ $$\\ $$\\ $$\\ $$ |
$$ __$$\\ $$ __$$\\ $$ | $$ |$$$$ | \\$$$$$$ |
$$ / $$ |$$ / \\__| $$ | $$ |\\_$$ | \\______/
$$ | $$ |\\$$$$$$\\ \\$$\\ $$ | $$ |
$$ | $$ | \\____$$\\ \\$$\\$$ / $$ |
$$ | $$ |$$\\ $$ | \\$$$ / $$ |
$$$$$$ |\\$$$$$$ | \\$ / $$$$$$\\
\\______/ \\______/ \\_/ \\______|
");
for i in 0u8..128 { let mut executor = Executor::new();
print!("{}", i as char); executor.spawn(Task::new(shell()));
} executor.run()
hcf();
}
#[panic_handler]
fn rust_panic(_info: &core::panic::PanicInfo) -> ! {
hcf();
}
fn hcf() -> ! {
loop {
unsafe {
#[cfg(target_arch = "x86_64")]
asm!("hlt");
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
asm!("wfi");
#[cfg(target_arch = "loongarch64")]
asm!("idle 0");
}
}
} }
+75
View File
@@ -0,0 +1,75 @@
// //! Stack-unwinding code for the Kernel. This is a modified version of https://github.com/nbdd0121/unwinding/blob/trunk/src/panic_handler.rs
// //! which does not support environment variables for obvious reasons, however we
// //! may implement a cmdline similar to Linux in the near future.
// #![expect(
// clippy::empty_loop,
// reason = "We don't yet have working power management. This is OK for now."
// )]
// use crate::prelude::*;
// use alloc::boxed::Box;
// use core::any::Any;
// use core::cell::Cell;
// use core::ffi::c_void;
// use core::panic::{Location, PanicInfo};
// use unwinding::abi::*;
// use unwinding::panic::begin_panic;
// #[thread_local]
// static PANIC_COUNT: Cell<usize> = Cell::new(0);
// #[link(name = "c")]
// unsafe extern "C" {}
// fn stack_trace() {
// struct CallbackData {
// counter: usize,
// }
// extern "C" fn callback(
// unwind_ctx: &UnwindContext<'_>,
// arg: *mut c_void,
// ) -> UnwindReasonCode {
// let data = unsafe { &mut *(arg as *mut CallbackData) };
// data.counter += 1;
// eprintln!(
// "{:4}:{:#19x} - <unknown>",
// data.counter,
// _Unwind_GetIP(unwind_ctx)
// );
// UnwindReasonCode::NO_REASON
// }
// let mut data = CallbackData { counter: 0 };
// _Unwind_Backtrace(callback, &mut data as *mut _ as _);
// }
// fn do_panic(msg: Box<dyn Any + Send>) -> ! {
// if PANIC_COUNT.get() >= 1 {
// stack_trace();
// eprintln!("Thread panicked while processing panic. aborting.");
// loop {}
// }
// PANIC_COUNT.set(1);
// stack_trace();
// let code = begin_panic(Box::new(msg));
// eprintln!("Failed to initiate panic: error {}", code.0);
// loop {}
// }
// #[panic_handler]
// fn panic(info: &PanicInfo<'_>) -> ! {
// eprintln!("{}", info);
// struct NoPayload;
// do_panic(Box::new(NoPayload))
// }
// #[track_caller]
// #[expect(unused, reason = "May still be used in the future.")]
// pub fn panic_any<M: 'static + Any + Send>(msg: M) -> ! {
// eprintln!("Panicked at {}", Location::caller());
// do_panic(Box::new(msg))
// }
+48
View File
@@ -0,0 +1,48 @@
use libm::include_font;
pub const FONT_SPLEEN_8X16: Font =
Font::new(include_font!("../../../resources/font/spleen-8x16.psf"));
pub const FONT_CP850_8X16: Font =
Font::new(include_font!("../../../resources/font/cp850-8x16.psf"));
// pub struct Font(pub [[u8; 16]; 512]);
pub struct Font {
width: usize,
height: usize,
length: u16,
data: [[u8; 16]; 512],
}
impl Font {
pub const fn new(data: [[u8; 16]; 512]) -> Self {
Self {
width: 8,
height: 16,
length: data.len() as u16,
data,
}
}
pub const fn glyph_for(&self, c: u16) -> &[u8] {
if c > self.length {
return &self.data[0];
}
&self.data[c as usize]
}
pub const fn width(&self) -> usize {
self.width
}
pub const fn height(&self) -> usize {
self.height
}
}
impl Default for Font {
fn default() -> Self {
FONT_CP850_8X16
}
}
+1
View File
@@ -0,0 +1 @@
pub mod font;
+22
View File
@@ -0,0 +1,22 @@
use alloc::string::String;
use alloc::vec::Vec;
pub mod frame;
pub mod render;
pub mod window;
pub trait Application {
type Output;
fn run(
&mut self,
args: Vec<String>,
) -> impl Future<Output = Result<Self::Output, Error>> + Send;
}
#[derive(Debug)]
pub enum Error {
UnknownCommand(String),
ApplicationFailed(String),
KernelError(String),
}
+48
View File
@@ -0,0 +1,48 @@
use super::{render::RenderError, window::Window};
use crate::arch::x86_64::drivers::framebuffer::colour::Colour;
use crate::std::maths::geometry::Vec2;
use alloc::{vec, vec::Vec};
pub struct Frame<'f> {
data: Vec<Vec<Colour>>,
window: &'f Window,
}
impl<'a> Frame<'a> {
pub fn new(window: &'a Window) -> Self {
Self {
data: vec![
vec![Colour::Black; window.dimensions().x()];
window.dimensions().y()
],
window,
}
}
pub fn render(&self) -> Result<(), RenderError> {
let data: Vec<&[Colour]> =
self.data.iter().map(|v| v.as_slice()).collect::<Vec<_>>();
self.window
.render(data.as_slice())
.map_err(|_| RenderError::Generic)
}
pub fn write_pixel(
&mut self,
x: usize,
y: usize,
color: Colour,
) -> Result<(), RenderError> {
if x >= self.window.dimensions().x()
|| y >= self.window.dimensions().y()
{
return Err(RenderError::Generic);
}
self.data[y][x] = color;
Ok(())
}
pub const fn dimensions(&self) -> Vec2<usize> {
self.window.dimensions()
}
}
+22
View File
@@ -0,0 +1,22 @@
use crate::arch::x86_64::drivers::framebuffer::colour::Colour;
use core::fmt::Display;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RenderError {
Generic,
}
impl Display for RenderError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Generic => write!(f, "Generic render error"),
}
}
}
impl core::error::Error for RenderError {}
pub struct ColouredChar {
ch: u8,
colour: Colour,
}
+93
View File
@@ -0,0 +1,93 @@
use super::render::RenderError;
use crate::arch::x86_64::drivers::framebuffer::colour::Colour;
use crate::arch::x86_64::drivers::framebuffer::display::FRAMEBUFFER_WRITER;
use crate::std::maths::geometry::Vec2;
use alloc::string::String;
pub struct Window {
dimensions: Vec2<usize>,
position: Vec2<usize>,
bordered: bool,
opened: bool,
title: String,
}
impl Window {
pub const fn new() -> Self {
Self {
dimensions: Vec2::new(0, 0),
position: Vec2::new(0, 0),
bordered: true,
opened: false,
title: String::new(),
}
}
pub fn render(&self, _data: &[&[Colour]]) -> Result<(), RenderError> {
// TODO: error handling!! the kernel should return an error in some
// cases
if let Some(fb) = FRAMEBUFFER_WRITER.lock().as_mut() {
fb.render_frame(_data);
}
Ok(())
}
pub const fn is_bordered(&self) -> bool {
self.bordered
}
pub const fn is_open(&self) -> bool {
self.opened
}
pub const fn open(&mut self) {
self.opened = true;
}
pub const fn close(&mut self) {
self.opened = false;
}
// some basic getters and setters for utility.
pub fn title(&'static self) -> &'static str {
self.title.as_str()
}
pub const fn dimensions(&self) -> Vec2<usize> {
self.dimensions
}
pub const fn position(&self) -> Vec2<usize> {
self.position
}
pub fn set_title(&mut self, title: String) {
self.title = title;
}
pub fn move_window(&mut self, offset: Vec2<usize>) {
self.position += offset;
}
pub const fn set_position(&mut self, position: Vec2<usize>) {
self.position = position;
}
pub const fn set_dimensions(&mut self, dimensions: Vec2<usize>) {
self.dimensions = dimensions;
}
}
impl Default for Window {
fn default() -> Self {
Self::new()
}
}
impl Drop for Window {
fn drop(&mut self) {
if self.opened {
self.close();
}
}
}
+61
View File
@@ -0,0 +1,61 @@
use super::{
application::{frame::Frame, render::RenderError},
maths::geometry::Vec2,
};
use crate::arch::x86_64::drivers::framebuffer::colour::Colour;
use crate::resources::font::Font;
pub struct Writer<'a> {
font: &'a Font,
}
impl<'a> Writer<'a> {
pub const fn new(font: &'a Font) -> Self {
Self { font }
}
pub const fn set_font(&mut self, font: &'a Font) {
self.font = font;
}
pub const fn font_size(&self) -> Vec2<usize> {
Vec2::new(self.font.width(), self.font.height())
}
pub fn render_glyph(
&self,
frame: &mut Frame,
pos: Vec2<usize>,
c: u8,
scale: usize,
) -> Result<(), RenderError> {
// get a reference to the character glyph from the font.
let data: &[u8] = self.font.glyph_for(c as u16);
if pos.x() + self.font.width() * scale > frame.dimensions().x()
|| pos.y() + self.font.height() * scale > frame.dimensions().y()
{
return Err(RenderError::Generic);
}
for (row, line) in data.iter().enumerate().take(self.font.height()) {
for col in 0..self.font.width() {
let pixel_x: usize = pos.x() + col * scale;
let pixel_y: usize = pos.y() + row * scale;
if line & (0x80 >> col) != 0 {
for i in 0..scale {
for j in 0..scale {
frame.write_pixel(
pixel_x + i,
pixel_y + j,
Colour::White,
)?;
}
}
}
}
}
Ok(())
}
}
+22
View File
@@ -0,0 +1,22 @@
use core::fmt;
use x86_64::instructions::interrupts;
use crate::prelude::{_print_log, _serial_write};
#[macro_export]
macro_rules! debugln {
() => ($crate::print_log!("\n"));
($($arg:tt)*) => ($crate::debug!("{}\n", format_args!($($arg)*)));
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => ($crate::prelude::_debug(format_args!($($arg)*)));
}
pub fn _debug(args: fmt::Arguments) {
interrupts::without_interrupts(|| {
_print_log(args);
_serial_write(args);
});
}
+114
View File
@@ -0,0 +1,114 @@
//! Basic ELF parsing functionality using the `elf` crate.
//!
//! This may be extended in the future to support loading programs, however we
//! currently use this for getting the sizes of sections in our kernel ELF at
//! runtime.
//!
//! This is used for implementing stacktraces in std::unwind.
//!
//! # TODO
//!
//! * Add support for loading binary programs (this should probably be written
//! in a different module)
use alloc::format;
use elf::{
ElfBytes, ParseError,
endian::LittleEndian,
parse::{ParseAt, ParsingTable},
section::{SectionHeader, SectionHeaderTable},
string_table::StringTable,
};
use limine::request::KernelFileRequest;
use crate::prelude::*;
#[cfg(target_arch = "x86_64")]
/// The length of the ELF header in bytes.
pub const ELF_HEADER_LEN: usize = 64;
/// Information about our own ELF file to make ELF parsing easier, such as the
/// length of the file and a pointer to the contents.
#[used]
pub static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new();
/// A list of errors that may occur when parsing ELF files.
#[derive(Debug)]
pub enum ElfError {
/// Returned if a section did not exist in [ElfReader::get_section_size].
SectionNotExists,
/// Parse errors returned by the `elf` crate.
OtherParseError(elf::ParseError),
}
impl From<elf::ParseError> for ElfError {
fn from(err: elf::ParseError) -> Self {
Self::OtherParseError(err)
}
}
pub struct ElfReader {
/// Structure returned by the `elf` crate having parsed the ELF header.
elf: ElfBytes<'static, LittleEndian>,
}
impl ElfReader {
/// Parses the ELF file for the kernel, this uses data from Limine's Kernel
/// File Request to get a slice over the whole executable file.
///
/// # Safety
///
/// Assumes a properly formed ELF file, and that Limine returns a correct
/// pointer to the start of the file as well as a valid length in bytes.
///
/// Both of these should be satisfied, but this function is marked unsafe
/// just in case, because we are derefererencing arbitrary pointers.
pub unsafe fn new() -> Result<Self, ElfError> {
let response = KERNEL_FILE_REQUEST
.get_response()
.expect("Didn't get the kernel file from Limine. That's odd.");
// We fetch these from Limine and use them to parse our own ELF file.
let file = response.file();
let file_start_ptr = file.addr();
let file_size = file.size() as usize;
// Safety: This slice should contain the whole bytes of the ELF file.
let elf_hdr_slice =
unsafe { core::slice::from_raw_parts(file_start_ptr, file_size) };
let elf: ElfBytes<'static, LittleEndian> =
elf::ElfBytes::minimal_parse(elf_hdr_slice)?;
Ok(Self { elf })
}
/// Gets the section size of `section_name` in bytes.
pub fn get_section_size(
&self,
section_name: &'static str,
) -> Result<u64, ElfError> {
Ok(self.get_section_header(section_name)?.sh_size)
}
/// Gets the start address of the section `section_name` in memory.
pub fn get_section_addr(
&self,
section_name: &'static str,
) -> Result<*const u8, ElfError> {
Ok(self.get_section_header(section_name)?.sh_addr as *const u8)
}
/// Gets the section header of `section_name`.
pub fn get_section_header(
&self,
section_name: &'static str,
) -> Result<SectionHeader, ElfError> {
let section_hdr = self
.elf
.section_header_by_name(section_name)
.map_err(|_e| ElfError::SectionNotExists)?;
section_hdr.ok_or(ElfError::SectionNotExists)
}
}
+6
View File
@@ -0,0 +1,6 @@
pub use crate::arch::x86_64::drivers::{
ascii::{_print, _print_err, _print_log},
serial::_serial_write,
};
pub mod stdin;
+75
View File
@@ -0,0 +1,75 @@
use crate::arch::x86_64::drivers::{
ascii::WRITER,
keyboard::{KeyStroke, get_keystroke_async, get_keystroke_optional},
};
use alloc::string::String;
/// Reads a line of input from standard input asynchronously, returning a
/// `String` containing the input line. Does not include the newline
/// character at the end of the line.
///
/// If the user presses the abort key (usually Ctrl+C), the returned string
/// will be empty.
pub async fn read_line() -> String {
let mut writer = WRITER.lock();
let mut buff = String::new();
loop {
match get_keystroke_async().await {
KeyStroke::Char(c) => match c {
'\n' => {
writer.write_glyph(c as u8);
return buff;
}
'\r' => {
writer.write_glyph(c as u8);
return buff;
}
'\x08' => {
if !buff.is_empty() {
buff.pop();
writer.backspace();
}
}
c => {
writer.write_glyph(c as u8);
buff.push(c)
}
},
KeyStroke::Enter => {
writer.write_glyph(b'\n');
return buff;
}
KeyStroke::Backspace => {
if !buff.is_empty() {
buff.pop();
writer.backspace();
}
}
_ => continue,
}
}
}
/// Reads a character from standard input and blocks the current task until
/// a character is available.
///
/// # Note
///
/// This function is not yet implemented.
pub async fn async_keystroke() -> KeyStroke {
get_keystroke_async().await
}
/// Attempt to read a character from standard input without blocking the
/// current task.
///
/// If no character is available, returns `None`.
///
/// # Note
///
/// This function is not yet implemented.
pub fn keystroke() -> Option<KeyStroke> {
get_keystroke_optional()
}
+79
View File
@@ -0,0 +1,79 @@
use core::ops::{AddAssign, DivAssign, MulAssign, SubAssign};
pub trait Coordinate:
Copy + Clone + PartialEq + AddAssign + MulAssign + SubAssign + DivAssign
{
}
impl Coordinate for usize {}
impl Coordinate for isize {}
impl Coordinate for u8 {}
impl Coordinate for i8 {}
impl Coordinate for u16 {}
impl Coordinate for i16 {}
impl Coordinate for u32 {}
impl Coordinate for i32 {}
impl Coordinate for u64 {}
impl Coordinate for i64 {}
impl Coordinate for u128 {}
impl Coordinate for i128 {}
impl Coordinate for f32 {}
impl Coordinate for f64 {}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)]
pub struct Vec2<T: Coordinate> {
x: T,
y: T,
}
impl<T: Coordinate> Vec2<T> {
pub const fn new(x: T, y: T) -> Self {
Self { x, y }
}
pub fn into<S: Coordinate + From<T>>(&self) -> Vec2<S> {
Vec2::new(self.x.into(), self.y.into())
}
pub const fn x(&self) -> T {
self.x
}
pub const fn y(&self) -> T {
self.y
}
}
impl<T: Coordinate> AddAssign for Vec2<T> {
fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x;
self.y += rhs.y;
}
}
impl<T: Coordinate> SubAssign for Vec2<T> {
fn sub_assign(&mut self, rhs: Self) {
self.x -= rhs.x;
self.y -= rhs.y;
}
}
impl<T: Coordinate> MulAssign<T> for Vec2<T> {
fn mul_assign(&mut self, rhs: T) {
self.x *= rhs;
self.y *= rhs;
}
}
impl<T: Coordinate> DivAssign<T> for Vec2<T> {
fn div_assign(&mut self, rhs: T) {
self.x /= rhs;
self.y /= rhs;
}
}
impl<T: Coordinate> From<Vec2<T>> for (T, T) {
fn from(value: Vec2<T>) -> Self {
(value.x, value.y)
}
}
+1
View File
@@ -0,0 +1 @@
pub mod geometry;
+7
View File
@@ -0,0 +1,7 @@
pub mod application;
pub mod ascii;
pub mod elf;
pub mod io;
pub mod maths;
pub mod unwind;
pub mod debug;
+135
View File
@@ -0,0 +1,135 @@
//! Contains a [EhInfo] struct that contains the parsed DWARF exception header
//! data from the ELF .eh_frame and .eh_frame_hdr sections.
use alloc::{boxed::Box, slice};
use gimli::{
BaseAddresses, EhFrame, EhFrameHdr, EhHdrTable, EndianSlice, LittleEndian,
ParsedEhFrameHdr,
};
use spin::Lazy;
use crate::{println_log, std::elf::ElfReader};
/// Contains useful data parsed from the ELF file in question. In the kernel
/// this will be our own process.
///
/// We use this to implement stack traces and potential unwinding.
///
/// # Sources
///
/// This code is reproduced from [lesenchal.fr](https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way#h5.1-parsing-eh_frame-and-eh_frame_hdr-with-gimli)
/// and will later be extended as required.
pub struct EhInfo {
/// A set of base addresses used for relative addressing.
base_addrs: BaseAddresses,
/// The parsed `.eh_frame_hdr` section.
hdr: &'static ParsedEhFrameHdr<EndianSlice<'static, LittleEndian>>,
/// The lookup table in the parsed `.eh_frame_hdr` section.
/// This is a binary search table, it is optional but should be present as
/// we are linking with LLD(?) \[needs citation].
hdr_table: EhHdrTable<'static, EndianSlice<'static, LittleEndian>>,
/// The parsed `.eh_frame` containing the CFIs (call frame information).
eh_frame: EhFrame<EndianSlice<'static, LittleEndian>>,
}
/// Stores the [ElfReader] struct for this ELF file.
pub static ELF: Lazy<ElfReader> =
Lazy::new(|| unsafe { ElfReader::new().unwrap() });
impl EhInfo {
/// Gets the `.eh_frame_hdr` size in bytes.
///
/// # Panics
///
/// If we can't get the size of `.eh_frame_hdr`.
pub fn eh_frame_hdr_size() -> usize {
ELF.get_section_size(".eh_frame_hdr")
.expect("Cannot get size of `.eh_frame_hdr`.") as usize
}
/// Gets the `.eh_frame` size in bytes.
///
/// # Panics
///
/// If we can't get the size of `.eh_frame`.
pub fn eh_frame_size() -> usize {
ELF.get_section_size(".eh_frame")
.expect("Cannot get size of `.eh_frame`.") as usize
}
/// Constructs a [EhInfo] from the base address. This is defined for a
/// symbol in the linker script so we can initialise stack traces and -- in
/// the future -- unwinding.
///
/// # Safety
///
/// Assumes the `.eh_frame_hdr` pointer to be valid, as well as the sizes of
/// the containing sections. These sizes are computed using [ElfReader].
///
/// # Panics
///
/// This function panics if Gimli throws parsing errors -- for example due
/// to a malformed or corrupted binary, or because this is called in
/// release-mode, or on a stripped binary.
///
/// # TODOs
///
/// * Support external System.map files which list symbols and contain
/// debugging information.
pub unsafe fn from_hdr_ptr(eh_frame_hdr: *const u8) -> Self {
let mut base_addrs = BaseAddresses::default();
// We add the `.eh_frame_hdr` pointer to the set of base addresses which
// are used by Gimli for later parsing. This may be used to compute a
// pointer to `.eh_frame`.
base_addrs = base_addrs.set_eh_frame_hdr(eh_frame_hdr as u64);
// Leaking the Box gives us a reference with `'static` lifetime to use
// in Self.
let hdr = Box::leak(Box::new(
// We need to construct a slice as input for `EhFrameHdr::new`.
// This is sound if data pointer and length are known to be
// correct.
EhFrameHdr::new(
unsafe {
core::slice::from_raw_parts(
eh_frame_hdr,
Self::eh_frame_hdr_size(),
)
},
LittleEndian,
) // Parse the header using the base address we provided (virtual
// memory). Address size is how many bytes make an
// address (64 bits).
.parse(&base_addrs, 8)
.expect(
"Could not parse `.eh_frame_hdr`. The ELF must be malformed.",
),
));
// Create a pointer to the `.eh_frame` ready to parse it.
let eh_frame = match hdr.eh_frame_ptr() {
gimli::Pointer::Direct(addr) => addr as *mut u8,
_ => unimplemented!(),
};
// Add the `.eh_frame` address for addresses relative to this section.
base_addrs = base_addrs.set_eh_frame(eh_frame as u64);
// Finally parse the `.eh_frame` section of our ELF.
let eh_frame = EhFrame::new(
unsafe {
core::slice::from_raw_parts(eh_frame, Self::eh_frame_size())
},
LittleEndian,
);
Self {
base_addrs,
hdr,
hdr_table: hdr.table().expect(
"The CFI binary search table was not present in this binary, oh dear.",
),
eh_frame,
}
}
}
+1
View File
@@ -0,0 +1 @@
pub mod eh_info;
+248
View File
@@ -0,0 +1,248 @@
use crate::arch::x86_64::drivers::keyboard::{KeyStroke, get_keystroke_async};
use crate::resources::font::Font;
use crate::serial_print;
use crate::serial_println;
use crate::std::application::{
Application, Error, frame::Frame, render::RenderError, window::Window,
};
use crate::std::ascii::Writer;
use crate::std::maths::geometry::Vec2;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
pub struct Editor {
cursor_line: usize,
cursor_col: usize,
mode: Mode,
buffer: String,
window: Window,
}
impl Default for Editor {
fn default() -> Self {
Self::new()
}
}
impl<'a> Editor {
const PADDING: usize = 8;
pub const fn new() -> Self {
Self {
cursor_line: 0,
cursor_col: 0,
mode: Mode::Nor,
buffer: String::new(),
window: Window::new(),
}
}
fn render(&'a self) -> Result<Frame<'a>, RenderError> {
let mut frame = Frame::new(&self.window);
let font = Font::default();
let writer = Writer::new(&font);
let (width, height) = writer.font_size().into();
let mut col = 0;
let mut line = 0;
let mut scale = 1;
for ch in self.buffer.chars() {
if ch == '\n' {
line += scale;
col = 0;
scale = 1;
continue;
}
if width * (col + 1) > frame.dimensions().x() - 2 * Self::PADDING {
line += scale;
col = 0;
}
writer.render_glyph(
&mut frame,
Vec2::new(
col * width + Self::PADDING,
line * height + Self::PADDING,
),
ch as u8,
scale,
)?;
col += scale;
}
writer
.render_glyph(
&mut frame,
Vec2::new(
self.cursor_col * width + Self::PADDING,
self.cursor_line * height + Self::PADDING,
),
b'_',
scale,
)
.expect("TODO: panic message");
Ok(frame)
}
#[allow(unused_variables, dead_code, clippy::needless_pass_by_ref_mut)]
fn get_lines(&self) -> Vec<&str> {
self.buffer.split('\n').collect::<Vec<&str>>()
}
#[allow(unused_variables, dead_code, clippy::needless_pass_by_ref_mut)]
fn move_cursor(&mut self, x: i32, y: i32) {
self.cursor_line = self
.cursor_line
.checked_add_signed(y as isize)
.unwrap_or(self.cursor_line);
self.cursor_col = self
.cursor_col
.checked_add_signed(x as isize)
.unwrap_or(self.cursor_col);
}
#[allow(unused_variables, dead_code, clippy::needless_pass_by_ref_mut)]
fn delete_char(&mut self) {
let i = self.get_char_idx();
self.buffer.remove(i);
}
#[allow(unused_variables, dead_code, clippy::needless_pass_by_ref_mut)]
fn insert_char(&mut self, c: char) {
let i = self.get_char_idx();
self.buffer.insert(i, c);
}
#[allow(unused_variables, dead_code, clippy::needless_pass_by_ref_mut)]
fn splitline(&mut self) {
let i = self.get_char_idx();
self.buffer.insert(i, '\n');
}
fn get_char_idx(&self) -> usize {
let frame = Frame::new(&self.window);
let font = Font::default();
let writer = Writer::new(&font);
let (width, _height) = writer.font_size().into();
let mut col = 0;
let mut line = 0;
let mut scale = 1;
for (i, ch) in self.buffer.chars().enumerate() {
if ch == '\n' {
line += scale;
col = 0;
scale = 1;
continue;
}
if width * (col + 1) > frame.dimensions().x() - 2 * Self::PADDING {
line += scale;
col = 0;
}
if col == self.cursor_col && line == self.cursor_line {
return i;
}
col += scale;
}
0
}
}
impl Application for Editor {
type Output = ();
async fn run(
&mut self,
_args: Vec<alloc::string::String>,
) -> Result<Self::Output, Error> {
self.window.set_dimensions(Vec2::new(1280, 800));
self.window.set_position(Vec2::new(0, 0));
self.window.open();
self.buffer = "Hello world, this is a test init. idk test \n ewntuiewi gjk gfdfg gndf ngdfgnmdfg ndfgmndfg gdfndfnkg njkdgjkndfjnkg ngnjfgnfgnfg fgn fn gfj gnfg jnfgjfngjk fgnjfgnjk jnkdgjnkdfg gfnd njkgdfgjn d fjnkgjkndfgjkndfgjn gndfjnk njkgdfng jnkfgdjknd jnfkgnjk".to_string();
loop {
if let Err(_err) = self.render().and_then(|frame| frame.render()) {
// TODO: Handle error
return Err(Error::ApplicationFailed(
"Rendering failed".to_string(),
));
}
let keystroke = get_keystroke_async().await;
match self.mode {
Mode::Nor => match keystroke {
KeyStroke::Char('i') => self.mode = Mode::Ins,
KeyStroke::Char('`') => return Ok(()),
_ => {}
},
Mode::Ins => {
match keystroke {
KeyStroke::Char(c) => {
match c {
// escape
'\x1B' => self.mode = Mode::Nor,
// delete
'\x7F' => self.delete_char(),
// backspace
'\x08' => {
self.move_cursor(-1, 0);
self.delete_char();
}
// enter
'\n' => self.splitline(),
_ => {
self.insert_char(c);
self.move_cursor(1, 0);
}
}
}
KeyStroke::Left => {
serial_println!("Left\n");
self.move_cursor(-1, 0);
}
KeyStroke::Right => {
serial_println!("Right\n");
self.move_cursor(1, 0);
}
KeyStroke::Up => {
serial_println!("Up\n");
self.move_cursor(0, -1);
}
KeyStroke::Down => {
serial_println!("Down\n");
self.move_cursor(0, 1);
}
KeyStroke::None => {}
_ => {}
}
}
}
}
}
}
pub enum Mode {
Nor,
Ins,
}
impl core::fmt::Display for Mode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Nor => write!(f, "Normal"),
Self::Ins => write!(f, "Insert"),
}
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod editor;
pub mod shell;
+49
View File
@@ -0,0 +1,49 @@
// use x86_64::registers::rflags::read;
use crate::arch::x86_64::drivers::ascii::clear_screen;
use crate::prelude::*;
use crate::std::application::Application;
use crate::std::io::stdin::read_line;
use crate::util::editor::Editor;
use alloc::vec::Vec;
static FETCH: &str = "
$$$$$$$$\\ $$\\
$$ _____| $$ |
$$ | $$$$$$\\ $$\\ $$\\ $$$$$$$\\ $$$$$$$ | $$$$$$\\ $$\\ $$\\
$$$$$\\ $$ __$$\\ $$ | $$ |$$ __$$\\ $$ __$$ |$$ __$$\\ $$ | $$ |
$$ __|$$ / $$ |$$ | $$ |$$ | $$ |$$ / $$ |$$ | \\__|$$ | $$ |
$$ | $$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ | $$ |
$$ | \\$$$$$$ |\\$$$$$$ |$$ | $$ |\\$$$$$$$ |$$ | \\$$$$$$$ |
\\__| \\______/ \\______/ \\__| \\__| \\_______|\\__| \\____$$ |
$$$$$$\\ $$$$$$\\ $$\\ $$\\ $$\\ $$\\ $$ |
$$ __$$\\ $$ __$$\\ $$ | $$ |$$$$ | \\$$$$$$ |
$$ / $$ |$$ / \\__| $$ | $$ |\\_$$ | \\______/
$$ | $$ |\\$$$$$$\\ \\$$\\ $$ | $$ |
$$ | $$ | \\____$$\\ \\$$\\$$ / $$ |
$$ | $$ |$$\\ $$ | \\$$$ / $$ |
$$$$$$ |\\$$$$$$ | \\$ / $$$$$$\\
\\______/ \\______/ \\_/ \\______|
";
pub async fn shell() {
println!("{}", FETCH);
loop {
print!(" Shell> ");
let line = read_line().await;
match line.as_str() {
"fetch" => {
println!("{}", FETCH);
}
"editor" => {
let mut editor = Editor::new();
editor.run(Vec::new()).await.unwrap();
}
"clear" => clear_screen(),
_ => {
println!("Unknown command: {}", line);
}
}
}
}
-7
View File
@@ -1,7 +0,0 @@
[package]
name = "lib_application"
version.workspace = true
edition.workspace = true
[dependencies]
lib_ascii = { path = "../lib_ascii" }
-10
View File
@@ -1,10 +0,0 @@
pub trait Application {
fn run(&mut self, args: [&'static str; 5]) -> Result<(), Error>;
}
pub enum Error {
OsError(&'static str),
ApplicationError(&'static str),
}
-5
View File
@@ -1,5 +0,0 @@
#![no_std]
pub mod application;
pub mod frame;
pub mod window;
-14
View File
@@ -1,14 +0,0 @@
pub struct Window {
width: usize,
height: usize
}
impl Window {
pub fn new() -> Self {
Self {
width: 0,
height: 0
}
}
}
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "lib_ascii"
version.workspace = true
edition.workspace = true
[dependencies]
lazy_static = { version = "1.5.0", default-features = false, features = ["spin_no_std"] }
lib_framebuffer = { path = "../lib_framebuffer" }
spin = "0.9.8"
x86_64 = "0.15.2"
-203
View File
@@ -1,203 +0,0 @@
pub static FONT: [u8; 128 * 16] = [
// ASCII 0-31 (Control Characters) - Using simple box patterns
// NUL (0)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// SOH (1)
0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// STX (2)
0x18, 0x3C, 0x7E, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// ETX (3)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// EOT (4)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// ENQ (5)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// ACK (6)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// BEL (7)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18,
// BS (8)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18,
// HT (9)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18,
// LF (10)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18,
// VT (11)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18,
// FF (12)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C,
// CR (13)
0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E,
// SO (14)
0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// SI (15)
0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// DLE (16)
0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// DC1 (17)
0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// DC2 (18)
0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// DC3 (19)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// DC4 (20)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// NAK (21)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// SYN (22)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// ETB (23)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF, 0xFF,
// CAN (24)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF, 0xFF,
// EM (25)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF, 0xFF,
// SUB (26)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0xFF,
// ESC (27)
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18,
// FS (28)
0x18, 0x3C, 0x7E, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18,
// GS (29)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18,
// RS (30)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0x18, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// US (31)
0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Space (32)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ! (33)
0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// " (34)
0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// # (35)
0x00, 0x00, 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
// $ (36)
0x18, 0x18, 0x7C, 0xC6, 0xC2, 0xC0, 0x7C, 0x06, 0x86, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00, 0x00,
// % (37)
0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18, 0x30, 0x60, 0xC6, 0x86, 0x00, 0x00, 0x00, 0x00,
// & (38)
0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00,
// ' (39)
0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// ( (40)
0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
// ) (41)
0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
// * (42)
0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// + (43)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// , (44)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
// - (45)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// . (46)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// / (47)
0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
// 0-9 (48-57)
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xD6, 0xD6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0xFC, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xC6, 0x06, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x06, 0x0C, 0x78, 0x00, 0x00, 0x00, 0x00,
// : (58)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// ; (59)
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00,
// < (60)
0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00,
// = (61)
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// > (62)
0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00,
// ? (63)
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// @ (64)
0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, 0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00, 0x00,
// A-Z (65-90)
0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE, 0xC6, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE6, 0x66, 0x6C, 0x6C, 0x78, 0x78, 0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x0C, 0x0E, 0x00, 0x00,
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C, 0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xD6, 0xFE, 0xEE, 0x6C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x7C, 0x38, 0x38, 0x7C, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFE, 0xC6, 0x86, 0x0C, 0x18, 0x30, 0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// [ (91)
0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00,
// \ (92)
0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00,
// ] (93)
0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00,
// ^ (94)
0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// _ (95)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
// ` (96)
0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// a-z (97-122) - lowercase letters
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00,
0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xD6, 0xD6, 0xD6, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x60, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// { (123)
0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00,
// | (124)
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// } (125)
0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00,
// ~ (126)
0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// DEL (127)
0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
];
-201
View File
@@ -1,201 +0,0 @@
#![no_std]
use core::fmt;
use lazy_static::lazy_static;
use spin::{Lazy, Mutex};
use x86_64::instructions::interrupts;
use lib_framebuffer::{FRAMEBUFFER_WRITER, Color};
mod font;
use font::FONT;
static FONT_WIDTH: u32 = 8;
static FONT_HEIGHT: u32 = 16;
pub static WRITER: Lazy<Mutex<Writer>> = Lazy::new(|| Mutex::new(Writer::new()));
pub fn screensize_chars() -> (u32, u32) {
let writer = WRITER.lock();
(writer.screen_width, writer.screen_height)
}
pub struct Writer {
// these are measured in chars NOT pixels
screen_width: u32,
screen_height: u32,
text_line: u32, // 16 pixels tall
text_col: u32, // 8 pixels wide
fg_color: u32,
bg_color: u32
}
impl Writer {
pub fn new() -> Self {
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
Self {
screen_width: writer.width() as u32 / 8,
screen_height: writer.height() as u32 / 16,
text_line: 0,
text_col: 0,
fg_color: 0xFFFFFF,
bg_color: 0x000000
}
} else {
panic!("Framebuffer writer not initialized");
}
}
pub fn write_char(&mut self, mut c: u8) {
if c == b'\n' {
self.newline();
return;
}
if c < 32 || c > 126 {
c = '?' as u8;
}
// get the character data from the font array. -- each byte is a row of pixels
let data: &[u8] = &FONT[c as usize * 16..(c as usize + 1) * 16];
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
for row in 0..16 {
let line: u8 = data[row];
for col in 0..8 {
let pixel_x: u32 = self.text_col * FONT_WIDTH + col;
let pixel_y: u32 = self.text_line * FONT_HEIGHT + row as u32;
if line & (0x80 >> col) != 0 {
// write the foreground color
writer.write_pixel(pixel_x as usize, pixel_y as usize, Color::RGB(255, 0, 0));
} else {
// write the background color
writer.write_pixel(pixel_x as usize, pixel_y as usize, Color::HexARGB(self.bg_color));
}
}
}
}
// go to next position
if self.text_col + 1 >= self.screen_width {
self.newline();
} else {
self.text_col += 1;
}
}
pub fn dimensions(&self) -> (u32, u32) {
(self.screen_width, self.screen_height)
}
pub fn next_char(&mut self) {
self.text_col += 1;
}
pub fn newline(&mut self) {
self.text_col = 0;
if self.text_line + 1 >= self.screen_height {
self.text_line = 0;
} else {
self.text_line += 1;
}
}
pub fn write_string(&mut self, s: &str) {
for c in s.chars() {
self.write_char(c as u8);
}
}
pub fn set_colour(&mut self, col: (u32, u32)) {
self.fg_color = col.0;
self.bg_color = col.1;
}
pub fn reset_colour(&mut self) {
self.fg_color = 0xFFFFFF;
self.bg_color = 0x000000;
}
}
impl core::fmt::Write for Writer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.write_string(s);
Ok(())
}
}
fn write(args: fmt::Arguments, fg_color: u32, bg_color: u32) {
use core::fmt::Write;
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
writer.set_colour((fg_color, bg_color));
writer.write_fmt(args).unwrap();
writer.reset_colour();
});
}
pub fn _print(args: fmt::Arguments) {
write(args, 0xFFFFFF, 0x000000);
}
pub fn _printerr(args: fmt::Arguments) {
write(args, 0xFF8080, 0x000000);
}
pub fn _log(args: fmt::Arguments) {
write(args, 0xFFFF00, 0x000000);
}
pub fn clear_screen() {
interrupts::without_interrupts(|| {
let mut writer = WRITER.lock();
writer.text_line = 0;
writer.text_col = 0;
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
writer.clear();
}
});
}
#[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::_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::_print(format_args!($($arg)*)));
}
#[macro_export]
macro_rules! printlnerr {
() => ($crate::printerr!("\n"));
($($arg:tt)*) => ($crate::printerr!("{}\n", format_args!($($arg)*)));
}
#[macro_export]
macro_rules! printerr {
($($arg:tt)*) => ($crate::_printerr(format_args!($($arg)*)));
}
-8
View File
@@ -1,8 +0,0 @@
[package]
name = "lib_framebuffer"
version.workspace = true
edition.workspace = true
[dependencies]
limine = "0.3.1"
spin = "0.9.8"
-51
View File
@@ -1,51 +0,0 @@
#[repr(u32)]
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum Color {
ARGB(u8, u8, u8, u8),
RGB(u8, u8, u8),
HexARGB(u32),
Black = 0x000000FF,
Blue = 0x0000FFFF,
Green = 0x00FF00FF,
Cyan = 0x00FFFFFF,
Red = 0xFF0000FF,
Magenta = 0xFF00FFFF,
Yellow = 0xFFFF00FF,
White = 0xFFFFFFFF,
}
impl Into<u32> for Color {
fn into(self) -> u32 {
match self {
Color::ARGB(a, r, g, b) => (a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32),
Color::RGB(r, g, b) => (0u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32),
Color::HexARGB(hex) => hex,
Color::Black => 0xFF000000,
Color::Blue => 0xFF0000FF,
Color::Green => 0xFF00FF00,
Color::Cyan => 0xFF00FFFF,
Color::Red => 0xFFFF0000,
Color::Magenta => 0xFFFF00FF,
Color::Yellow => 0xFFFFFF00,
Color::White => 0xFFFFFFFF,
}
}
}
impl core::fmt::Display for Color {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Color::ARGB(r, g, b, a) => write!(f, "RGBA(#{:x}{:x}{:x}{:x})", r, g, b, a),
Color::RGB(r, g, b) => write!(f, "RGB(#{:x}{:x}{:x})", r, g, b),
Color::HexARGB(hex) => write!(f, "Hex(#{:x})", hex),
Color::Black => write!(f, "Black"),
Color::Blue => write!(f, "Blue"),
Color::Green => write!(f, "Green"),
Color::Cyan => write!(f, "Cyan"),
Color::Red => write!(f, "Red"),
Color::Magenta => write!(f, "Magenta"),
Color::Yellow => write!(f, "Yellow"),
Color::White => write!(f, "White"),
}
}
}
-67
View File
@@ -1,67 +0,0 @@
use core::panic;
use limine::framebuffer::Framebuffer;
use spin::{Mutex, Lazy};
use crate::{colour::Color, FRAMEBUFFER_REQUEST};
pub static FRAMEBUFFER_WRITER: Lazy<Mutex<Option<FramebufferWriter>>> = Lazy::new(|| Mutex::new(
if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() {
let framebuffer = framebuffer_response.framebuffers().next().unwrap();
Some(FramebufferWriter::new(framebuffer))
} else {
panic!("Framebuffer request failed");
}
));
pub struct FramebufferWriter<'a> {
framebuffer: Framebuffer<'a>,
}
unsafe impl<'a> Send for FramebufferWriter<'a> {}
unsafe impl<'a> Sync for FramebufferWriter<'a> {}
impl<'a> FramebufferWriter<'a> {
pub fn new(framebuffer: Framebuffer<'a>) -> Self {
Self {
framebuffer,
}
}
pub fn write_pixel(&self, x: usize, y: usize, color: Color) {
let pitch = self.framebuffer.pitch() as usize;
let bpp = (self.framebuffer.bpp() / 8) as usize;
let pixel_offset = y * pitch + x * bpp;
unsafe {
*(self.framebuffer.addr().add(pixel_offset) as *mut u32) = color.into();
}
}
pub fn render_frame(&self, buffer: &[Color; 1280 * 800]) {
for (y, row) in buffer.chunks(1280).enumerate() {
for (x, pixel) in row.iter().enumerate() {
self.write_pixel(x, y, *pixel);
}
}
}
pub fn width(&self) -> u32 {
self.framebuffer.width() as u32
}
pub fn height(&self) -> u32 {
self.framebuffer.height() as u32
}
pub fn clear(&self) {
let width = self.framebuffer.width() as usize;
let height = self.framebuffer.height() as usize;
for y in 0..height {
for x in 0..width {
self.write_pixel(x, y, Color::Black);
}
}
}
}
-20
View File
@@ -1,20 +0,0 @@
#![no_std]
use limine::request::FramebufferRequest;
#[used]
#[link_section = ".requests"]
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
mod framebuffer;
mod colour;
pub use framebuffer::FRAMEBUFFER_WRITER;
pub use colour::Color;
pub fn screensize_px() -> (u32, u32) {
if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() {
(writer.width(), writer.height())
} else {
(0, 0)
}
}
-10
View File
@@ -1,10 +0,0 @@
[package]
name = "lib_serial"
version.workspace = true
edition.workspace = true
[dependencies]
lazy_static = { version = "1.5.0", default-features = false, features = ["spin_no_std"] }
spin = "0.9.8"
x86_64 = "0.15.2"
-23
View File
@@ -1,23 +0,0 @@
use core::arch::asm;
#[inline]
pub unsafe fn inb(port: u16) -> u8 {
let value: u8;
asm!(
"in al, dx",
out("al") value,
in("dx") port,
options(nomem, nostack, preserves_flags)
);
value
}
#[inline]
pub unsafe fn outb(port: u16, value: u8) {
asm!(
"out dx, al",
in("dx") port,
in("al") value,
options(nomem, nostack, preserves_flags)
);
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "libm"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
darling = "0.20.10"
proc-macro2 = "1.0.93"
quote = "1.0.38"
syn = "2.0.98"
[lib]
proc-macro = true
+3
View File
@@ -0,0 +1,3 @@
[toolchain]
channel = "nightly"
targets = ["x86_64-linux-gnu"]
+129
View File
@@ -0,0 +1,129 @@
#![allow(dead_code)]
#![feature(proc_macro_span)]
#![warn(
clippy::correctness,
clippy::nursery,
clippy::unnecessary_cast,
clippy::all,
clippy::suspicious,
clippy::perf,
rustdoc::missing_errors_doc,
rustdoc::missing_panics_doc
)]
use proc_macro::{Span, TokenStream};
use quote::quote;
use std::path::PathBuf;
use syn::{LitStr, parse_macro_input};
extern crate proc_macro;
#[proc_macro]
/// Expects the file path to be relative to the current file so this works
/// similarly to the standard Rust include! macros.
pub fn include_font(item: TokenStream) -> TokenStream {
let span = Span::call_site();
let source_file = span.source_file();
if !source_file.is_real() {
panic!(
"We can't handle finding files if the source file does not exist. TODO: Can we?"
)
}
let filename = parse_macro_input!(item as LitStr);
let source_filepath: PathBuf = source_file.path();
let file_path = format!(
"{}/{}",
source_filepath.parent()
.unwrap_or_else(|| panic!("Expected to find the calling source file in a folder like src! Got: {}", source_filepath.display())).display(),
filename.value()
);
let font_bytes = match std::fs::read(file_path) {
Ok(bytes) => bytes,
Err(why) => panic!("{}", why),
};
let font_data = match FontBuilder::load(font_bytes) {
Ok(font) => font.data,
Err(why) => panic!("{}", why),
};
quote!(
[
#(
[#(#font_data),*]
),*
]
)
.into()
}
struct Font([[u8; 16]; 512]);
struct FontData {
width: u8,
height: u8,
length: u16,
pub data: [[u8; 16]; 512],
}
enum FontBuilder {
Psf1(FontData),
Psf2(FontData),
}
impl FontBuilder {
const PSF1_MAGIC: u16 = 0x3604;
const PSF2_MAGIC: u32 = 0x72b54a86;
fn revision(data: &[u8]) -> u8 {
if (data[0] as u16) << 8 | data[1] as u16 == Self::PSF1_MAGIC {
1
} else if (data[0] as u32) << 24
| (data[1] as u32) << 16
| (data[2] as u32) << 8
| data[3] as u32
== Self::PSF2_MAGIC
{
2
} else {
0
}
}
pub fn load(data: Vec<u8>) -> Result<FontData, &'static str> {
match Self::revision(&data[0..4]) {
1 => Self::parse_psf1(&data),
2 => Self::parse_psf2(&data),
_ => panic!("invalid font revision result"),
}
}
fn parse_psf1(data: &[u8]) -> Result<FontData, &'static str> {
let mode = data[2];
let size = data[3];
let has_512_glyphs = (mode & 0x01) != 0;
let mut glyphs = [[0; 16]; 512];
let glyph_count = if has_512_glyphs { 512 } else { 256 };
for i in 0..(glyph_count as usize) {
let mut buff: [u8; 16] = [0; 16];
for j in 0..(size as usize) {
buff[j] = data[4 + i * (size as usize) + j];
}
glyphs[i] = buff;
}
Ok(FontData {
width: 8,
height: size,
length: glyph_count,
data: glyphs,
})
}
const fn parse_psf2(_data: &[u8]) -> Result<FontData, &'static str> {
Err("PSF2 support is not implemented yet!")
}
}
+4
View File
@@ -0,0 +1,4 @@
wrap_comments = true
max_width = 80
comment_width = 80
format_code_in_doc_comments = true
+3 -80
View File
@@ -13,8 +13,9 @@ set -e
trap 'echo -e "${RED}${BOLD}error${NC}: build failed" >&2' ERR trap 'echo -e "${RED}${BOLD}error${NC}: build failed" >&2' ERR
# Get absolute path to project root # Get absolute path to project root
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
project_root=$(cd "$script_dir/.." &>/dev/null && pwd) project_root=$(cd "$script_dir/.." &>/dev/null && pwd)
build_dir="$project_root/build"
iso_root="$build_dir/iso_root"
# Logging functions # Logging functions
info() { info() {
@@ -47,13 +48,8 @@ copy_file() {
cp "$1" "$2" || error $3 cp "$1" "$2" || error $3
} }
build_dir="$project_root/build"
iso_root="$build_dir/iso_root"
# Check if we're running tests # Check if we're running tests
is_test=0
if [[ $1 == *"deps"* ]]; then if [[ $1 == *"deps"* ]]; then
is_test=1
kernel_path="$1" kernel_path="$1"
else else
# Build the kernel normally # Build the kernel normally
@@ -85,7 +81,7 @@ mkdir -p "$iso_root/EFI/BOOT"
if [ ! -d "$build_dir/limine" ]; then if [ ! -d "$build_dir/limine" ]; then
compiling "limine bootloader" compiling "limine bootloader"
cd "$build_dir" cd "$build_dir"
git clone https://github.com/limine-bootloader/limine.git --branch=v8.x-binary --depth=1 "$build_dir/limine" || error "failed to clone limine" git clone https://github.com/limine-bootloader/limine.git --branch=v9.x-binary --depth=1 "$build_dir/limine" || error "failed to clone limine"
make -C "$build_dir/limine" || error "failed to build limine" make -C "$build_dir/limine" || error "failed to build limine"
cd "$project_root" cd "$project_root"
fi fi
@@ -111,76 +107,3 @@ xorriso -as mkisofs -R -r -J -b boot/limine/limine-bios-cd.bin \
# Install Limine # Install Limine
info "Installing Limine bootloader" info "Installing Limine bootloader"
"$build_dir/limine/limine" bios-install "$build_dir/image.iso" || error "failed to install limine" "$build_dir/limine/limine" bios-install "$build_dir/image.iso" || error "failed to install limine"
# Check if KVM is available
if [ "${KVM_FLAG:-enable}" = "disable" ]; then
warning "KVM acceleration disabled by user"
kvm_flag=""
elif [ -c "/dev/kvm" ] && [ -w "/dev/kvm" ]; then
info "KVM acceleration enabled"
kvm_flag="-enable-kvm"
else
warning "KVM acceleration not available (is kvm module loaded?)"
kvm_flag=""
fi
# Check if we're running in debug mode
if [[ "$(cargo metadata --format-version=1 | jq -r '.workspace_members[0]' | cut -d' ' -f2)" == "(debug)" ]]; then
debug_flags="-s -S"
else
debug_flags=""
fi
# Set up test-specific flags
if [ $is_test -eq 1 ]; then
test_flags="-device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none"
serial_flags="-serial stdio"
else
test_flags=""
# serial_flags="-serial tcp:127.0.0.1:1234,server -monitor telnet:127.0.0.1:1235,server"
serial_flags="-serial stdio"
fi
# Run in QEMU
if [[ ${QEMU_FLAGS} == *-S* ]]; then
info "Running OS in QEMU with GDB debugging enabled"
info "To connect GDB, run: gdb"
info "At the GDB prompt, type: target remote localhost:1234"
else
info "Running OS in QEMU..."
fi
check_test_res() {
qemu_exit_code=$?
if [ $qemu_exit_code -eq 33 ]; then
# Success case (0x10 << 1) | 1 = 33
info "All tests passed"
exit 0
elif [ $qemu_exit_code -eq 35 ]; then
# Failure case (0x11 << 1) | 1 = 35
warning "Some tests failed"
exit 1
else
# Any other exit code is treated as a failure
warning "Some tests failed"
exit 1
fi
}
kvm_flag=""
trap 'check_test_res "tests completed"' ERR
cd "$project_root"
qemu-system-x86_64 -M q35 \
${kvm_flag} \
-cdrom "$build_dir/image.iso" \
-boot d \
-m 2G \
${serial_flags} \
-no-reboot \
${test_flags} \
${debug_flags} \
${QEMU_FLAGS:-}
+20
View File
@@ -0,0 +1,20 @@
#!/bin/bash
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
source scripts/build.sh
warning "This script will OVERWRITE whatever media you throw at it\nwith the built ISO."
info "sudo ./hardware.sh /dev/yourdisk"
if ! echo "$1" | grep -q "/dev"; then
error "invalid device"
fi
read -p "Confirm (y/n): " confirmation
if [ "$confirmation" != "y" ]; then
exit 0
fi
dd if=./build/image.iso of="$1"
+118
View File
@@ -0,0 +1,118 @@
#!/bin/bash
# Script originally written by zxq5, I added separate scripts to remove `jq` dependency.
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)
source $script_dir/build.sh
# Error handling
set -e
trap 'echo -e "${RED}${BOLD}error${NC}: build failed" >&2' ERR
echo -e "${GREEN}${BOLD} Running kernel in debug mode."
# Check if KVM is available
if [ "${KVM_FLAG:-enable}" = "disable" ]; then
warning "KVM acceleration disabled by user"
kvm_flag=""
elif [ -c "/dev/kvm" ] && [ -w "/dev/kvm" ]; then
info "KVM acceleration enabled"
kvm_flag="-enable-kvm"
else
warning "KVM acceleration not available (is kvm module loaded?)"
kvm_flag=""
fi
# Enable using GDB with $USE_GDB.
if [ $USE_GDB ]; then
debug_flags="-s -S"
else
debug_flags=""
fi
# Set up test-specific flags
if [ $is_test -eq 1 ]; then
test_flags="-device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none"
serial_flags="-serial stdio"
else
test_flags=""
# serial_flags="-serial tcp:127.0.0.1:1234,server -monitor telnet:127.0.0.1:1235,server"
serial_flags="-serial stdio"
fi
# Set up VM memory
if [ $VM_MEMORY ]; then
vm_memory_flag="-m $VM_MEMORY"
else
vm_memory_flag="-m 2G"
fi
# Set up boot flags
if [ $USE_LEGACY_BIOS ]; then
boot_flags=""
else
# Check for the presence of the OVMF firmware.
if [ ! -f $build_dir/RELEASEX64_OVMF_CODE.fd ]; then
info "Downloading OVMF UEFI firmware for QEMU"
info "To disable this, set USE_LEGACY_BIOS=1."
pushd $build_dir
curl https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF_CODE.fd -LO || error "failed to download OVMF firmware for UEFI"
curl https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF_VARS.fd -LO || error "failed to download OVMF firmware for UEFI"
popd
fi
boot_flags="-drive if=pflash,format=raw,readonly=on,file=$build_dir/RELEASEX64_OVMF_CODE.fd \
-drive if=pflash,format=raw,file=$build_dir/RELEASEX64_OVMF_VARS.fd"
fi
# Run in QEMU
if [[ ${QEMU_FLAGS} == *-S* ]]; then
info "Running OS in QEMU with GDB debugging enabled"
info "To connect GDB, run: gdb"
info "At the GDB prompt, type: target remote localhost:1234"
else
info "Running OS in QEMU..."
fi
check_test_res() {
qemu_exit_code=$?
if [ $qemu_exit_code -eq 33 ]; then
# Success case (0x10 << 1) | 1 = 33
info "All tests passed"
exit 0
elif [ $qemu_exit_code -eq 35 ]; then
# Failure case (0x11 << 1) | 1 = 35
warning "Some tests failed"
exit 1
else
# Any other exit code is treated as a failure
warning "Some tests failed"
exit 1
fi
}
trap 'check_test_res "tests completed"' ERR
# NOTE(ali): For some reason the values in `boot_flags` were causing issues
# on WSL, so they've been eradicated.
systemInfo=$(uname -r)
if [[ $systemInfo == *"WSL"* ]]; then
echo "Running on WSL2"
boot_flags=""
fi
cd "$project_root"
qemu-system-x86_64 -M q35 \
-cdrom "$build_dir/image.iso" \
${kvm_flag} \
-boot d \
${vm_memory_flag} \
${serial_flags} \
-no-reboot \
${test_flags} \
${debug_flags} \
${boot_flags} \
${QEMU_FLAGS:-}

Some files were not shown because too many files have changed in this diff Show More