From 4c2236bf5d04cf3adcb7367b978276b6ce3588a5 Mon Sep 17 00:00:00 2001 From: johnruina Date: Thu, 4 Jun 2026 09:33:24 -0400 Subject: [PATCH] minimum viable product --- .cargo/config.toml | 12 +- Cargo.lock | 737 +++++++++++++++++++++++++++++++- Cargo.toml | 15 +- builder/Cargo.toml | 7 + builder/src/main.rs | 51 +++ kernel/Cargo.toml | 10 + kernel/src/font.rs | 59 +++ {src => kernel/src}/game.rs | 0 {src => kernel/src}/keyboard.rs | 88 +++- kernel/src/main.rs | 94 ++++ {src => kernel/src}/rng.rs | 0 kernel/src/vga_buffer.rs | 228 ++++++++++ src/linker.ld | 51 --- src/main.rs | 133 ------ src/vga_buffer.rs | 98 ----- 15 files changed, 1274 insertions(+), 309 deletions(-) create mode 100644 builder/Cargo.toml create mode 100644 builder/src/main.rs create mode 100644 kernel/Cargo.toml create mode 100644 kernel/src/font.rs rename {src => kernel/src}/game.rs (100%) rename {src => kernel/src}/keyboard.rs (67%) create mode 100644 kernel/src/main.rs rename {src => kernel/src}/rng.rs (100%) create mode 100644 kernel/src/vga_buffer.rs delete mode 100644 src/linker.ld delete mode 100644 src/main.rs delete mode 100644 src/vga_buffer.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 21c1437..22c8343 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,3 @@ -[unstable] -build-std = ["core", "compiler_builtins"] -build-std-features = ["compiler-builtins-mem"] - -[build] -target = "x86_64-unknown-none" - -[target.x86_64-unknown-none] -rustflags = ["-Clink-arg=-Tsrc/linker.ld", "-Crelocation-model=static"] +[alias] +build-kernel = "build -p kernel --target x86_64-unknown-none -Z build-std=core,compiler_builtins -Z build-std-features=compiler-builtins-mem" +run-kernel = "run -p kernel --target x86_64-unknown-none -Z build-std=core,compiler_builtins -Z build-std-features=compiler-builtins-mem" diff --git a/Cargo.lock b/Cargo.lock index f1b9b0e..be8460d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,33 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit_field" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.12.1" @@ -15,10 +36,360 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" [[package]] -name = "bootloader" -version = "0.9.34" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f6a8a495d2f93fe3d6eb3a224f9aa749a63cfd746ed03eb5ddcbd00ade7d8f" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "bootloader" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5fb69232f250c0d5b74eeb285702b4b0479f574980f45dc9bef97b2c65e2021" +dependencies = [ + "anyhow", + "bootloader-boot-config", + "fatfs", + "gpt", + "llvm-tools", + "mbrman", + "serde_json", + "tempfile", +] + +[[package]] +name = "bootloader-boot-config" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a5e475c13d3d988e105b76b92aeea94614c12590c33495165c02b16e895375" +dependencies = [ + "serde", +] + +[[package]] +name = "bootloader_api" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c074a3d1075b26ed77e502d6af8ab1263abbdc3d026c166e46480f973287293" + +[[package]] +name = "builder" +version = "0.1.0" +dependencies = [ + "bootloader", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fatfs" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05669f8e7e2d7badc545c513710f0eba09c2fbef683eb859fd79c46c355048e0" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "log", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gpt" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8283e7331b8c93b9756e0cfdbcfb90312852f953c6faf9bf741e684cc3b6ad69" +dependencies = [ + "bitflags 2.12.1", + "crc", + "log", + "uuid", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "x86_64", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "mbrman" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fc3bff63c208d4a14301c6cb807af2d1a0760052584ce3f9a737b55fb85498" +dependencies = [ + "bincode", + "bitvec", + "serde", + "serde-big-array", + "thiserror", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.12.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] [[package]] name = "rustversion" @@ -26,6 +397,143 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "volatile" version = "0.4.6" @@ -33,11 +541,218 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" [[package]] -name = "wordleos" -version = "0.1.0" +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "bootloader", - "x86_64", + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.12.1", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.12.1", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", ] [[package]] @@ -47,7 +762,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" dependencies = [ "bit_field", - "bitflags", + "bitflags 2.12.1", "rustversion", "volatile", ] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 4208f29..1c8a237 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ -[package] -name = "wordleos" -version = "0.1.0" -edition = "2021" +[workspace] +members = ["kernel", "builder"] +resolver = "2" -[dependencies] -bootloader = "0.9" -x86_64 = "0.14" \ No newline at end of file +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" diff --git a/builder/Cargo.toml b/builder/Cargo.toml new file mode 100644 index 0000000..ed1cc00 --- /dev/null +++ b/builder/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "builder" +version = "0.1.0" +edition = "2021" + +[dependencies] +bootloader = "0.11" diff --git a/builder/src/main.rs b/builder/src/main.rs new file mode 100644 index 0000000..b55ee44 --- /dev/null +++ b/builder/src/main.rs @@ -0,0 +1,51 @@ +use std::path::Path; + +fn main() { + let args: Vec = std::env::args().collect(); + let kernel_path = if args.len() > 1 { + args[1].clone() + } else { + let target = std::env::var("TARGET").unwrap_or_else(|_| "x86_64-unknown-none".to_string()); + let profile = std::env::var("PROFILE").unwrap_or_else(|_| "debug".to_string()); + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR") + .unwrap_or_else(|_| ".".to_string()); + // Builder is at workspace/builder, kernel is at workspace/kernel + let workspace = Path::new(&manifest_dir).parent().unwrap(); + format!("{}/target/{}/{}/kernel", workspace.display(), target, profile) + }; + + let kernel_path = Path::new(&kernel_path); + if !kernel_path.exists() { + eprintln!("Kernel not found at: {}", kernel_path.display()); + eprintln!("Usage: builder "); + std::process::exit(1); + } + + let out_dir = std::env::var("OUT_DIR").unwrap_or_else(|_| { + std::env::var("CARGO_MANIFEST_DIR") + .map(|d| format!("{}/../target", d)) + .unwrap_or_else(|_| "target".to_string()) + }); + + // Disable bootloader's verbose framebuffer logging (default: trace-level yellow text) + let mut boot_config = bootloader::BootConfig::default(); + boot_config.frame_buffer_logging = false; + + // Create UEFI bootable image + println!("Creating UEFI boot image from {}", kernel_path.display()); + let mut uefi_img = bootloader::UefiBoot::new(&kernel_path); + uefi_img.set_boot_config(&boot_config); + let uefi_path = Path::new(&out_dir).join("wordleos-uefi.img"); + uefi_img.create_disk_image(&uefi_path) + .expect("failed to write UEFI image"); + println!("UEFI image written to: {}", uefi_path.display()); + + // Also create BIOS bootable image for compatibility + println!("Creating BIOS boot image from {}", kernel_path.display()); + let mut bios_img = bootloader::BiosBoot::new(&kernel_path); + bios_img.set_boot_config(&boot_config); + let bios_path = Path::new(&out_dir).join("wordleos-bios.img"); + bios_img.create_disk_image(&bios_path) + .expect("failed to write BIOS image"); + println!("BIOS image written to: {}", bios_path.display()); +} diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml new file mode 100644 index 0000000..33cc949 --- /dev/null +++ b/kernel/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "kernel" +version = "0.1.0" +edition = "2021" + +[dependencies] +bootloader_api = "0.11" +x86_64 = "0.14" + + diff --git a/kernel/src/font.rs b/kernel/src/font.rs new file mode 100644 index 0000000..67f70f4 --- /dev/null +++ b/kernel/src/font.rs @@ -0,0 +1,59 @@ +pub const FONT_5X7_W: usize = 5; +pub const FONT_5X7_H: usize = 7; + +pub static FONT5X7: [[u8; 7]; 26] = [ + [0b01110, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001], // A + [0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110], // B + [0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110], // C + [0b11110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b11110], // D + [0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111], // E + [0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000], // F + [0b01110, 0b10001, 0b10000, 0b10111, 0b10001, 0b10001, 0b01110], // G + [0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001], // H + [0b01110, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110], // I + [0b00111, 0b00010, 0b00010, 0b00010, 0b00010, 0b10010, 0b01100], // J + [0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001], // K + [0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111], // L + [0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001], // M + [0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001, 0b10001], // N + [0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110], // O + [0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000], // P + [0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101], // Q + [0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001], // R + [0b01110, 0b10001, 0b10000, 0b01110, 0b00001, 0b10001, 0b01110], // S + [0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100], // T + [0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110], // U + [0b10001, 0b10001, 0b10001, 0b01010, 0b01010, 0b00100, 0b00100], // V + [0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b11011, 0b10001], // W + [0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001], // X + [0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100, 0b00100], // Y + [0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111], // Z +]; + +pub fn get_5x7(ch: u8) -> Option<&'static [u8; 7]> { + if ch.is_ascii_uppercase() { + Some(&FONT5X7[(ch - b'A') as usize]) + } else { + None + } +} + +pub static PUNCT5X7: &[(u8, [u8; 7])] = &[ + (b'!', [0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000, 0b00100]), + (b'\'', [0b00100, 0b00100, 0b00100, 0b00000, 0b00000, 0b00000, 0b00000]), + (b',', [0b00000, 0b00000, 0b00000, 0b00000, 0b00100, 0b00100, 0b01000]), + (b'-', [0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000]), + (b'.', [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00100, 0b00100]), + (b':', [0b00000, 0b00100, 0b00100, 0b00000, 0b00100, 0b00100, 0b00000]), + (b'_', [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111]), + (b' ', [0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000]), +]; + +pub fn get_punct(ch: u8) -> Option<&'static [u8; 7]> { + for &(c, ref glyph) in PUNCT5X7 { + if c == ch { + return Some(glyph); + } + } + None +} diff --git a/src/game.rs b/kernel/src/game.rs similarity index 100% rename from src/game.rs rename to kernel/src/game.rs diff --git a/src/keyboard.rs b/kernel/src/keyboard.rs similarity index 67% rename from src/keyboard.rs rename to kernel/src/keyboard.rs index 4e345bd..e5b1cfe 100644 --- a/src/keyboard.rs +++ b/kernel/src/keyboard.rs @@ -1,5 +1,78 @@ +use core::sync::atomic::{AtomicBool, Ordering}; use x86_64::instructions::port::Port; +const TIMEOUT: u32 = 1_000_000; + +static KEYBOARD_READY: AtomicBool = AtomicBool::new(false); + +/// Initialises the PS/2 controller and keyboard. +/// +/// UEFI firmware often leaves the PS/2 controller uninitialised, so we must +/// perform the full startup sequence ourselves before the keyboard will work. +/// If no PS/2 controller is present (common on modern UEFI systems), this +/// returns gracefully without hanging. +pub fn init() { + // Disable both PS/2 ports + if !try_write_command(0xAD) { return; } + if !try_write_command(0xA7) { return; } + + // Flush output buffer + let _ = try_read_data(); + + // Read and update configuration byte + if !try_write_command(0x20) { return; } + let config = match try_read_data() { + Some(val) => val & !0x07, + None => return, + }; + if !try_write_command(0x60) { return; } + if !try_write_data(config) { return; } + + // Controller self-test + if !try_write_command(0xAA) { return; } + if try_read_data() != Some(0x55) { return; } + + // Enable keyboard port + if !try_write_command(0xAE) { return; } + + // Reset keyboard + if !try_write_data(0xFF) { return; } + if try_read_data() != Some(0xFA) { return; } // ACK + if try_read_data() != Some(0xAA) { return; } // self-test passed + + KEYBOARD_READY.store(true, Ordering::SeqCst); +} + +fn try_write_command(cmd: u8) -> bool { + if !poll_write_ready() { return false; } + unsafe { Port::new(0x64).write(cmd) } + true +} + +fn try_write_data(data: u8) -> bool { + if !poll_write_ready() { return false; } + unsafe { Port::new(0x60).write(data) } + true +} + +fn poll_write_ready() -> bool { + for _ in 0..TIMEOUT { + if read_status() & 2 == 0 { + return true; + } + } + false +} + +fn try_read_data() -> Option { + for _ in 0..TIMEOUT { + if read_status() & 1 != 0 { + return Some(unsafe { Port::new(0x60).read() }); + } + } + None +} + /// Actions produced by the PS/2 keyboard decoder. /// /// Only A–Z letters, Enter, and Backspace are relevant for the Wordle game. @@ -45,16 +118,25 @@ fn data_available() -> bool { /// is pending, `None` is returned and the caller should briefly spin-wait /// before retrying. /// -/// ## Scan code handling (Set 1) -/// The PS/2 controller is typically in "translated" mode, converting native -/// Set 2 scancodes to Set 1 on the fly. In Set 1: +/// ## Scan code handling +/// The PS/2 keyboard defaults to Scan Code Set 2. Most PS/2 controllers +/// run in "translated" mode, converting Set 2 scancodes to Set 1 on the +/// fly. In Set 1: /// - Make codes (key pressed): 0x01 – 0x7F /// - Break codes (key released): 0x81 – 0xFF (= make_code | 0x80) /// - Extended keys (arrows, etc.): prefixed with 0xE0 /// +/// We do NOT explicitly change the keyboard's scan code set (command 0xF0) +/// because doing so would corrupt the controller's translation — the +/// keyboard stays in its default Set 2, and the controller handles the +/// conversion to Set 1 transparently. +/// /// We ignore break codes and extended codes since we only care about when /// a letter key is pressed. pub fn poll_key() -> Option { + if !KEYBOARD_READY.load(Ordering::SeqCst) { + return None; + } if !data_available() { return None; } diff --git a/kernel/src/main.rs b/kernel/src/main.rs new file mode 100644 index 0000000..cea4529 --- /dev/null +++ b/kernel/src/main.rs @@ -0,0 +1,94 @@ +#![no_std] +#![no_main] + +mod font; +mod vga_buffer; +mod keyboard; +mod rng; +mod game; + +use game::{GameState, WordleGame, WORDS}; +use keyboard::KeyAction; +use rng::Lcg; +use vga_buffer::{Color, ColorCode}; + +use bootloader_api::{entry_point, BootInfo}; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + let fb = boot_info.framebuffer.take() + .expect("no framebuffer provided by bootloader"); + vga_buffer::init(fb); + + vga_buffer::clear_screen(Color::Black); + keyboard::init(); + + loop { + let mut rng = Lcg::from_tsc(); + let secret = WORDS[rng.next_range(WORDS.len())]; + + let mut game = WordleGame::new(secret); + game.render_all(); + + loop { + if let Some(action) = keyboard::poll_key() { + match action { + KeyAction::Letter(ch) => game.add_letter(ch), + KeyAction::Backspace => game.delete_letter(), + KeyAction::Enter => { + if game.submit_guess().is_none() {} + } + } + game.render_all(); + + if game.state == GameState::Won || game.state == GameState::Lost { + wait_for_keypress(); + break; + } + } + + spin_delay(); + } + } +} + +fn wait_for_keypress() { + loop { + if keyboard::poll_key().is_some() { + return; + } + spin_delay(); + } +} + +fn spin_delay() { + for _ in 0..10000 { + core::hint::spin_loop(); + } +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + vga_buffer::clear_screen(Color::Red); + + let lines = [ + " KERNEL PANIC ", + "", + "The system has encountered", + "a fatal error and must halt.", + ]; + for (i, &line) in lines.iter().enumerate() { + let col = (vga_buffer::WIDTH - line.len()) / 2; + vga_buffer::write_str(10 + i, col, line, + ColorCode::new(Color::White, Color::Red)); + } + + if let Some(loc) = info.location() { + let _ = (loc.file(), loc.line()); + } + + loop { + x86_64::instructions::hlt(); + } +} diff --git a/src/rng.rs b/kernel/src/rng.rs similarity index 100% rename from src/rng.rs rename to kernel/src/rng.rs diff --git a/kernel/src/vga_buffer.rs b/kernel/src/vga_buffer.rs new file mode 100644 index 0000000..873af23 --- /dev/null +++ b/kernel/src/vga_buffer.rs @@ -0,0 +1,228 @@ +use crate::font; +use bootloader_api::info::{FrameBuffer, PixelFormat}; + +pub const WIDTH: usize = 80; +pub const HEIGHT: usize = 25; + +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +fn color_to_rgb(color: Color) -> (u8, u8, u8) { + match color { + Color::Black => (0, 0, 0), + Color::Blue => (0, 0, 170), + Color::Green => (0, 170, 0), + Color::Cyan => (0, 170, 170), + Color::Red => (170, 0, 0), + Color::Magenta => (170, 0, 170), + Color::Brown => (170, 85, 0), + Color::LightGray => (170, 170, 170), + Color::DarkGray => (85, 85, 85), + Color::LightBlue => (85, 85, 255), + Color::LightGreen => (85, 255, 85), + Color::LightCyan => (85, 255, 255), + Color::LightRed => (255, 85, 85), + Color::Pink => (255, 85, 255), + Color::Yellow => (255, 255, 85), + Color::White => (255, 255, 255), + } +} + +#[derive(Clone, Copy)] +pub struct ColorCode { + fg: Color, + bg: Color, +} + +impl ColorCode { + pub const fn new(fg: Color, bg: Color) -> ColorCode { + ColorCode { fg, bg } + } +} + +struct FbState { + ptr: *mut u8, + width: usize, + height: usize, + stride: usize, + bpp: usize, + is_bgr: bool, + x_off: usize, + y_off: usize, + cell_w: usize, + cell_h: usize, +} + +unsafe impl Send for FbState {} + +static mut FB: Option = None; + +pub fn init(fb: FrameBuffer) { + let info = fb.info(); + let buf = fb.into_buffer(); + let ptr = buf.as_mut_ptr(); + let is_bgr = matches!(info.pixel_format, PixelFormat::Bgr); + let cell_w = (info.width / WIDTH).max(1); + let cell_h = (info.height / HEIGHT).max(1); + let x_off = (info.width - cell_w * WIDTH) / 2; + let y_off = (info.height - cell_h * HEIGHT) / 2; + unsafe { + FB = Some(FbState { + ptr, + width: info.width, + height: info.height, + stride: info.stride, + bpp: info.bytes_per_pixel, + is_bgr, + x_off, + y_off, + cell_w, + cell_h, + }); + } +} + +fn with_fb(f: F) -> R +where + F: FnOnce(&mut FbState) -> R, +{ + unsafe { + let fb = &mut *core::ptr::addr_of_mut!(FB); + f(fb.as_mut().expect("Framebuffer not initialized")) + } +} + +fn write_pixel(state: &mut FbState, x: usize, y: usize, r: u8, g: u8, b: u8) { + if x >= state.width || y >= state.height { + return; + } + let offset = (y * state.stride + x) * state.bpp; + unsafe { + if state.is_bgr { + *state.ptr.add(offset) = b; + *state.ptr.add(offset + 1) = g; + *state.ptr.add(offset + 2) = r; + } else { + *state.ptr.add(offset) = r; + *state.ptr.add(offset + 1) = g; + *state.ptr.add(offset + 2) = b; + } + } +} + +fn fill_rect(state: &mut FbState, x: usize, y: usize, w: usize, h: usize, r: u8, g: u8, b: u8) { + let x_end = (x + w).min(state.width); + let y_end = (y + h).min(state.height); + for py in y..y_end { + for px in x..x_end { + write_pixel(state, px, py, r, g, b); + } + } +} + +fn get_glyph(ch: u8) -> Option<&'static [u8; 7]> { + if let Some(g) = font::get_5x7(ch) { + return Some(g); + } + font::get_punct(ch) +} + +fn draw_glyph_at( + state: &mut FbState, + cell_x: usize, cell_y: usize, cell_w: usize, cell_h: usize, + ch: u8, + fg_r: u8, fg_g: u8, fg_b: u8, + bg_r: u8, bg_g: u8, bg_b: u8, +) { + fill_rect(state, cell_x, cell_y, cell_w, cell_h, bg_r, bg_g, bg_b); + + let glyph = match get_glyph(ch) { + Some(g) => g, + None => return, + }; + + if cell_w < 3 || cell_h < 3 { + return; + } + + let scale_h = (cell_w.saturating_sub(2)) / font::FONT_5X7_W; + let scale_v = (cell_h.saturating_sub(2)) / font::FONT_5X7_H; + let scale = scale_h.min(scale_v).min(8); + + if scale == 0 { + return; + } + + let glw = font::FONT_5X7_W * scale; + let glh = font::FONT_5X7_H * scale; + let off_x = (cell_w - glw) / 2; + let off_y = (cell_h - glh) / 2; + + for row in 0..font::FONT_5X7_H { + for col in 0..font::FONT_5X7_W { + if glyph[row] & (1 << (4 - col)) != 0 { + fill_rect( + state, + cell_x + off_x + col * scale, + cell_y + off_y + row * scale, + scale, scale, + fg_r, fg_g, fg_b, + ); + } + } + } +} + +pub fn clear_screen(bg: Color) { + let (r, g, b) = color_to_rgb(bg); + with_fb(|state| { + fill_rect(state, 0, 0, state.width, state.height, r, g, b); + }); +} + +pub fn write_char(row: usize, col: usize, ch: u8, color: ColorCode) { + if row >= HEIGHT || col >= WIDTH { + return; + } + let (fg_r, fg_g, fg_b) = color_to_rgb(color.fg); + let (bg_r, bg_g, bg_b) = color_to_rgb(color.bg); + with_fb(|state| { + let cell_x = state.x_off + col * state.cell_w; + let cell_y = state.y_off + row * state.cell_h; + draw_glyph_at( + state, + cell_x, cell_y, state.cell_w, state.cell_h, + ch, + fg_r, fg_g, fg_b, + bg_r, bg_g, bg_b, + ); + }); +} + +pub fn write_str(row: usize, col: usize, s: &str, color: ColorCode) { + for (i, &ch) in s.as_bytes().iter().enumerate() { + if col + i < WIDTH { + write_char(row, col + i, ch, color); + } else { + break; + } + } +} diff --git a/src/linker.ld b/src/linker.ld deleted file mode 100644 index 30370de..0000000 --- a/src/linker.ld +++ /dev/null @@ -1,51 +0,0 @@ -ENTRY(_start); - -SECTIONS -{ - . = 0x200000; - - .rodata : ALIGN(4K) - { - *(.rodata .rodata.*) - } - - .text : ALIGN(4K) - { - *(.text .text.*) - } - - .data.rel.ro : ALIGN(4K) - { - *(.data.rel.ro .data.rel.ro.*) - } - - .data : ALIGN(4K) - { - *(.data .data.*) - } - - .got : ALIGN(4K) - { - *(.got) - } - - .bss : ALIGN(4K) - { - *(.bss .bss.*) - } - - /DISCARD/ : - { - *(.comment) - *(.debug_abbrev) - *(.debug_info) - *(.debug_aranges) - *(.debug_ranges) - *(.debug_str) - *(.debug_frame) - *(.debug_line) - *(.debug_loc) - *(.symtab) - *(.strtab) - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 6447794..0000000 --- a/src/main.rs +++ /dev/null @@ -1,133 +0,0 @@ -#![no_std] -#![no_main] - -mod vga_buffer; -mod keyboard; -mod rng; -mod game; - -use game::{GameState, WordleGame, WORDS}; -use keyboard::KeyAction; -use rng::Lcg; -use vga_buffer::{Color, ColorCode}; - -use bootloader::{entry_point, BootInfo}; - -entry_point!(kernel_main); - -// Called by the bootloader after it has set up 64-bit long mode, page tables, -// and a stack. The `boot_info` parameter provides information about the -// memory map, framebuffer, etc. — but for our VGA-text-mode display we -// don't need it. -// -// How the bootloader works: -// 1. BIOS/UEFI loads the bootloader (from `bootloader` crate). -// 2. Bootloader transitions CPU from real mode → protected mode → long mode, -// sets up page tables with VGA framebuffer at 0xB8000 identity-mapped, -// loads our ELF, and jumps to kernel_main. -// 3. We arrive with interrupts disabled (RFLAGS.IF = 0), at CPL 0 (ring 0). - -fn kernel_main(_boot_info: &'static BootInfo) -> ! { - // ── Initialisation ───────────────────────────────────────────────── - vga_buffer::clear_screen(Color::Black); - - // ── Main game loop (runs forever, restarting after each game) ────── - loop { - // Seed the RNG from the CPU timestamp counter so every boot picks - // a different secret word (assuming the machine has been on for a - // different amount of time). - let mut rng = Lcg::from_tsc(); - let secret = WORDS[rng.next_range(WORDS.len())]; - - let mut game = WordleGame::new(secret); - game.render_all(); - - // ── Per-game input loop ──────────────────────────────────────── - loop { - if let Some(action) = keyboard::poll_key() { - match action { - KeyAction::Letter(ch) => game.add_letter(ch), - KeyAction::Backspace => game.delete_letter(), - KeyAction::Enter => { - if game.submit_guess().is_none() { - // Incomplete guess — the game shows nothing - // for this; we just re-render as-is. - } - } - } - game.render_all(); - - if game.state == GameState::Won || game.state == GameState::Lost { - // Wait for any key press, then restart the game. - wait_for_keypress(); - break; - } - } - - // Brief spin delay to keep the CPU from saturating at 100 % - // while polling. 10k iterations of `pause` is roughly 10–50 µs - // on modern CPUs — invisible to human input but vastly reduces - // power draw. - spin_delay(); - } - } -} - -/// Blocks until any key is pressed (used between games). -fn wait_for_keypress() { - loop { - if keyboard::poll_key().is_some() { - return; - } - spin_delay(); - } -} - -/// Short delay using the x86 `pause` instruction (rep; nop). -/// -/// The `pause` instruction hints to the CPU that this is a spin-wait loop, -/// allowing it to reduce power consumption and improve hyper-thread -/// performance. It behaves as a no-op architecturally. -fn spin_delay() { - for _ in 0..10000 { - core::hint::spin_loop(); - } -} - -/// Panic handler — invoked on unrecoverable errors. -/// -/// Displays a red crash screen with a message, then halts forever. -#[panic_handler] -fn panic(info: &core::panic::PanicInfo) -> ! { - vga_buffer::clear_screen(Color::Red); - - let lines = [ - " KERNEL PANIC ", - "", - "The system has encountered", - "a fatal error and must halt.", - ]; - for (i, &line) in lines.iter().enumerate() { - let col = (vga_buffer::WIDTH - line.len()) / 2; - vga_buffer::write_str(10 + i, col, line, - ColorCode::new(Color::White, Color::Red)); - } - - // The panic message payload is the `Arguments` type. We could format - // it with a custom `core::fmt::Write` implementation, but since we're - // already in a crash state, we keep it simple: just display the line - // number if available. - if let Some(loc) = info.location() { - let file = loc.file(); - let line_num = loc.line(); - // Truncated display for the panic location. - let file_start = if file.len() > 30 { &file[file.len() - 30..] } else { file }; - // Print at a fixed offset; we can't do dynamic formatting in no_std - // without a formatter. - let _ = (file_start, line_num); - } - - loop { - x86_64::instructions::hlt(); - } -} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs deleted file mode 100644 index aefc844..0000000 --- a/src/vga_buffer.rs +++ /dev/null @@ -1,98 +0,0 @@ -use core::ptr::write_volatile; - -/// VGA text mode display dimensions. -/// -/// The standard VGA text mode provides an 80×25 grid of character cells. -/// Each cell is 2 bytes: [ASCII byte] [attribute byte]. -/// The buffer starts at physical address 0xB8000 and is memory-mapped into -/// the CPU's address space. The x86_64 bootloader identity-maps this region, -/// so we can write to it with regular memory stores — but we must use volatile -/// writes to prevent the compiler from optimizing away MMIO operations. -pub const VGA_ADDR: *mut u8 = 0xB8000 as *mut u8; -pub const WIDTH: usize = 80; -pub const HEIGHT: usize = 25; - -/// Standard VGA text-mode colors. -/// -/// The attribute byte layout: -/// bits 0-3: foreground color (0 = black, 15 = white) -/// bits 4-6: background color (0-7, hardware limit; bit 7 = blink) -#[allow(dead_code)] -#[repr(u8)] -#[derive(Clone, Copy, Debug)] -pub enum Color { - Black = 0, - Blue = 1, - Green = 2, - Cyan = 3, - Red = 4, - Magenta = 5, - Brown = 6, - LightGray = 7, - DarkGray = 8, - LightBlue = 9, - LightGreen = 10, - LightCyan = 11, - LightRed = 12, - Pink = 13, - Yellow = 14, - White = 15, -} - -/// A packed attribute byte encoding foreground + background color. -#[derive(Clone, Copy)] -pub struct ColorCode(u8); - -impl ColorCode { - /// Constructs a color code from foreground and background colors. - /// - /// ## Hardware note - /// The VGA attribute byte stores the background color in bits 4-6 (3 bits, - /// limiting backgrounds to 0-7). Bit 7 is the blink enable bit, which we - /// keep cleared. The foreground color uses bits 0-3 (including intensity - /// on bit 3), giving 16 possible foreground colors. - pub const fn new(fg: Color, bg: Color) -> ColorCode { - ColorCode(((bg as u8) << 4) | (fg as u8)) - } -} - -/// Clears the entire VGA screen to the given background color. -/// -/// Writes a space character (0x20) with the appropriate attribute byte -/// to every cell on the screen. -pub fn clear_screen(bg: Color) { - let color = ColorCode::new(Color::White, bg); - for offset in (0..(WIDTH * HEIGHT * 2)).step_by(2) { - unsafe { - write_volatile(VGA_ADDR.add(offset), b' '); - write_volatile(VGA_ADDR.add(offset + 1), color.0); - } - } -} - -/// Writes a single character at position (row, col) with the given color. -/// -/// The position is bounds-checked against the 80×25 screen. Writes outside -/// the visible area are silently ignored. -pub fn write_char(row: usize, col: usize, ch: u8, color: ColorCode) { - if row < HEIGHT && col < WIDTH { - let offset = (row * WIDTH + col) * 2; - unsafe { - write_volatile(VGA_ADDR.add(offset), ch); - write_volatile(VGA_ADDR.add(offset + 1), color.0); - } - } -} - -/// Writes a string starting at position (row, col) with the given color. -/// -/// Stops at the right edge of the screen to avoid wrapping. -pub fn write_str(row: usize, col: usize, s: &str, color: ColorCode) { - for (i, &ch) in s.as_bytes().iter().enumerate() { - if col + i < WIDTH { - write_char(row, col + i, ch, color); - } else { - break; - } - } -}