diff --git a/Cargo.lock b/Cargo.lock index 13db5ac..22bdab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,38 @@ dependencies = [ "regex", ] +[[package]] +name = "abi" +version = "0.1.0" +dependencies = [ + "abi_sys", + "embassy-time 0.5.0", + "embedded-graphics 0.8.1", + "shared", + "spin", + "talc", +] + +[[package]] +name = "abi_sys" +version = "0.1.0" +dependencies = [ + "embedded-graphics 0.8.1", + "shared", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -45,6 +77,12 @@ dependencies = [ "term 1.1.0", ] +[[package]] +name = "assign-resources" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "840ad5d907de7448d88a3f22b4a7b5d326c6eb3deeb9f94cfaaec7354a80b305" + [[package]] name = "atomic-polyfill" version = "1.0.3" @@ -75,21 +113,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "bisync" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5020822f6d6f23196ccaf55e228db36f9de1cf788052b37992e17cbc96ec41a7" -dependencies = [ - "bisync_macros", -] - -[[package]] -name = "bisync_macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21f40d350a700f6aa107e45fb26448cf489d34794b2ba4522181dc9f1173af6" - [[package]] name = "bit-set" version = "0.5.3" @@ -126,6 +149,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" @@ -166,7 +195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f377753756ec12e76b52d2dd657437be0448cc9736402ffadd0b8b8b9602c8a1" dependencies = [ "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embedded-io", "embedded-io-async", "futures-intrusive", @@ -192,6 +221,14 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "calculator" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics 0.8.1", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -215,7 +252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", - "bitfield", + "bitfield 0.13.2", "embedded-hal 0.2.7", "volatile-register", ] @@ -292,7 +329,7 @@ dependencies = [ "embassy-futures", "embassy-net-driver-channel", "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embedded-hal 1.0.0", "embedded-io-async", "futures", @@ -465,8 +502,25 @@ checksum = "8578db196d74db92efdd5ebc546736dac1685499ee245b22eff92fa5e4b57945" dependencies = [ "embassy-futures", "embassy-hal-internal 0.3.0", - "embassy-sync 0.7.0", - "embassy-time", + "embassy-sync 0.7.2", + "embassy-time 0.4.0", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +dependencies = [ + "embassy-futures", + "embassy-hal-internal 0.3.0", + "embassy-sync 0.7.2", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -477,22 +531,23 @@ dependencies = [ [[package]] name = "embassy-executor" -version = "0.7.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90327bcc66333a507f89ecc4e2d911b265c45f5c9bc241f98eee076752d35ac6" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" dependencies = [ "cortex-m", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "document-features", "embassy-executor-macros", + "embassy-executor-timer-queue", ] [[package]] name = "embassy-executor-macros" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" dependencies = [ "darling", "proc-macro2", @@ -501,10 +556,16 @@ dependencies = [ ] [[package]] -name = "embassy-futures" -version = "0.1.1" +name = "embassy-executor-timer-queue" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" [[package]] name = "embassy-hal-internal" @@ -514,7 +575,6 @@ checksum = "0ef3bac31ec146321248a169e9c7b5799f1e0b3829c7a9b324cb4600a7438f59" dependencies = [ "cortex-m", "critical-section", - "defmt 0.3.100", "num-traits", ] @@ -524,6 +584,9 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ + "cortex-m", + "critical-section", + "defmt 1.0.1", "num-traits", ] @@ -535,13 +598,13 @@ checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" [[package]] name = "embassy-net-driver-channel" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a567ab50319d866ad5e6c583ed665ba9b07865389644d3d82e45bf1497c934" +checksum = "b7b2739fbcf6cd206ae08779c7d709087b16577d255f2ea4a45bc4bbbf305b3f" dependencies = [ "embassy-futures", "embassy-net-driver", - "embassy-sync 0.7.0", + "embassy-sync 0.7.2", ] [[package]] @@ -556,12 +619,12 @@ dependencies = [ "cortex-m-rt", "critical-section", "document-features", - "embassy-embedded-hal", + "embassy-embedded-hal 0.3.1", "embassy-futures", "embassy-hal-internal 0.2.0", "embassy-sync 0.6.2", - "embassy-time", - "embassy-usb-driver", + "embassy-time 0.4.0", + "embassy-usb-driver 0.1.1", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -574,7 +637,7 @@ dependencies = [ "nb 1.1.0", "pio 0.2.1", "pio-proc 0.2.2", - "rand_core", + "rand_core 0.6.4", "rp-pac", "rp2040-boot2", "sha2-const-stable", @@ -583,25 +646,25 @@ dependencies = [ [[package]] name = "embassy-rp" -version = "0.4.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a063d8baccdc5c7752840f4c7931f17bcd7de1ffe1efa2109e68113fe42612" +checksum = "1a284935af0a869de3fa14af74b5f932389dd66d7048012f1083b06f38d05399" dependencies = [ "atomic-polyfill", "cfg-if", "cortex-m", "cortex-m-rt", "critical-section", - "defmt 0.3.100", + "defmt 1.0.1", "document-features", - "embassy-embedded-hal", + "embassy-embedded-hal 0.5.0", "embassy-futures", - "embassy-hal-internal 0.2.0", - "embassy-sync 0.6.2", - "embassy-time", + "embassy-hal-internal 0.3.0", + "embassy-sync 0.7.2", + "embassy-time 0.5.0", "embassy-time-driver", "embassy-time-queue-utils", - "embassy-usb-driver", + "embassy-usb-driver 0.2.0", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -613,8 +676,8 @@ dependencies = [ "fixed", "nb 1.1.0", "pio 0.3.0", - "rand_core", - "rp-binary-info", + "rand_core 0.6.4", + "rand_core 0.9.3", "rp-pac", "rp2040-boot2", "sha2-const-stable", @@ -637,16 +700,16 @@ dependencies = [ [[package]] name = "embassy-sync" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef1a8a1ea892f9b656de0295532ac5d8067e9830d49ec75076291fd6066b136" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" dependencies = [ "cfg-if", "critical-section", "defmt 1.0.1", "embedded-io-async", + "futures-core", "futures-sink", - "futures-util", "heapless", ] @@ -658,7 +721,6 @@ checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8" dependencies = [ "cfg-if", "critical-section", - "defmt 0.3.100", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -668,34 +730,90 @@ dependencies = [ ] [[package]] -name = "embassy-time-driver" -version = "0.2.0" +name = "embassy-time" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d45f5d833b6d98bd2aab0c2de70b18bfaa10faf661a1578fd8e5dfb15eb7eba" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 1.0.1", + "document-features", + "embassy-time-driver", + "embassy-time-queue-utils", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" dependencies = [ "document-features", ] [[package]] name = "embassy-time-queue-utils" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc55c748d16908a65b166d09ce976575fb8852cf60ccd06174092b41064d8f83" +checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ - "embassy-executor", + "embassy-executor-timer-queue", "heapless", ] +[[package]] +name = "embassy-usb" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4462e48b19a4f401a11901bdd981aab80c6a826608016a0bdc73cbbab31954" +dependencies = [ + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync 0.7.2", + "embassy-usb-driver 0.2.0", + "embedded-io-async", + "heapless", + "ssmarshal", + "usbd-hid", +] + [[package]] name = "embassy-usb-driver" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76" +dependencies = [ + "embedded-io-async", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17119855ccc2d1f7470a39756b12068454ae27a3eabb037d940b5c03d9c77b7a" dependencies = [ "defmt 1.0.1", "embedded-io-async", ] +[[package]] +name = "embedded-graphics" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "750082c65094fbcc4baf9ba31583ce9a8bb7f52cadfb96f6164b1bc7f922f32b" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core 0.3.3", + "float-cmp 0.8.0", + "micromath 1.1.1", +] + [[package]] name = "embedded-graphics" version = "0.8.1" @@ -705,9 +823,19 @@ dependencies = [ "az", "byteorder", "defmt 0.3.100", - "embedded-graphics-core", - "float-cmp", - "micromath", + "embedded-graphics-core 0.4.0", + "float-cmp 0.9.0", + "micromath 2.1.0", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b1239db5f3eeb7e33e35bd10bd014e7b2537b17e071f726a09351431337cfa" +dependencies = [ + "az", + "byteorder", ] [[package]] @@ -783,21 +911,49 @@ dependencies = [ ] [[package]] -name = "embedded-sdmmc" -version = "0.8.0" -source = "git+https://github.com/Be-ing/embedded-sdmmc-rs?branch=bisync#835b2e4f9d3482b6287f674d7ecf6ae5d0618c18" +name = "embedded-layout" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a90553247f2b05c59ac7894ea13d830636c2b1203fa03bff400eddbd1fa9f52" +dependencies = [ + "embedded-graphics 0.8.1", + "embedded-layout-macros", +] + +[[package]] +name = "embedded-layout-macros" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f6e621fe4c7e05b695274b722dc0a60bacd1c8696b58191baa0154713d52400" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "embedded-sdmmc" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3c7f9ea039eeafc4a49597b7bd5ae3a1c8e51b2803a381cb0f29ce90fe1ec6" dependencies = [ - "bisync", "byteorder", "defmt 0.3.100", - "embassy-futures", "embedded-hal 1.0.0", - "embedded-hal-async", "embedded-io", - "embedded-io-async", "heapless", ] +[[package]] +name = "embedded-snake" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af598ad20b839c26b95be615a94619a069303ec776a58aef6dc0e86cf7eabbb8" +dependencies = [ + "embedded-graphics 0.7.1", + "rand_core 0.6.4", +] + [[package]] name = "embedded-storage" version = "0.3.1" @@ -813,6 +969,17 @@ dependencies = [ "embedded-storage", ] +[[package]] +name = "embedded-text" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005680edc0d075af5e02d5788ca291737bd9aba7fc404ae031cc9dfa715e5f7d" +dependencies = [ + "az", + "embedded-graphics 0.8.1", + "object-chain", +] + [[package]] name = "ena" version = "0.14.3" @@ -822,6 +989,12 @@ dependencies = [ "log", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.2" @@ -852,6 +1025,15 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + [[package]] name = "float-cmp" version = "0.9.0" @@ -977,6 +1159,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "goblin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e961b33649994dcf69303af6b3a332c1228549e604d455d61ec5d2ab5e68d3a" +dependencies = [ + "plain", + "scroll", +] + [[package]] name = "half" version = "2.6.0" @@ -996,6 +1188,15 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.4" @@ -1031,7 +1232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] @@ -1082,6 +1283,48 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "kernel" +version = "0.1.0" +dependencies = [ + "abi_sys", + "assign-resources", + "bitflags 2.9.1", + "bt-hci", + "cortex-m", + "cortex-m-rt", + "cyw43", + "cyw43-pio", + "defmt 0.3.100", + "defmt-rtt", + "embassy-embedded-hal 0.3.1", + "embassy-executor", + "embassy-futures", + "embassy-rp 0.8.0", + "embassy-sync 0.7.2", + "embassy-time 0.5.0", + "embassy-usb", + "embedded-graphics 0.8.1", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-bus", + "embedded-layout", + "embedded-sdmmc", + "embedded-text", + "goblin", + "heapless", + "num_enum 0.7.4", + "panic-probe", + "portable-atomic", + "shared", + "spin", + "st7365p-lcd", + "static_cell", + "talc", + "trouble-host", +] + [[package]] name = "lalrpop" version = "0.19.12" @@ -1159,9 +1402,9 @@ checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libredox" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ "bitflags 2.9.1", "libc", @@ -1195,6 +1438,12 @@ version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +[[package]] +name = "micromath" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4010833aea396656c2f91ee704d51a6f1329ec2ab56ffd00bfd56f7481ea94" + [[package]] name = "micromath" version = "2.1.0" @@ -1272,6 +1521,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "object-chain" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41af26158b0f5530f7b79955006c2727cd23d0d8e7c3109dc316db0a919784dd" + [[package]] name = "once_cell" version = "1.21.3" @@ -1352,39 +1607,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" -[[package]] -name = "picocalc-os-rs" -version = "0.1.0" -dependencies = [ - "bitflags 2.9.1", - "bt-hci", - "cortex-m", - "cortex-m-rt", - "cyw43", - "cyw43-pio", - "defmt 0.3.100", - "defmt-rtt", - "embassy-embedded-hal", - "embassy-executor", - "embassy-futures", - "embassy-rp 0.4.0", - "embassy-sync 0.7.0", - "embassy-time", - "embedded-graphics", - "embedded-hal 0.2.7", - "embedded-hal-async", - "embedded-hal-bus", - "embedded-sdmmc", - "heapless", - "panic-probe", - "portable-atomic", - "spin", - "st7365p-lcd", - "static_cell", - "talc", - "trouble-host", -] - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -1485,6 +1707,12 @@ dependencies = [ "syn 2.0.104", ] +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -1576,6 +1804,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + [[package]] name = "redox_syscall" version = "0.5.13" @@ -1640,12 +1874,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "rp-binary-info" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ed2051a0bf2c726df01cfce378ed8a367be2a6e402fc183857f429a346d429" - [[package]] name = "rp-pac" version = "7.0.0" @@ -1695,6 +1923,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" + [[package]] name = "semver" version = "0.9.0" @@ -1710,6 +1944,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "sha2-const-stable" version = "0.1.0" @@ -1726,6 +1980,14 @@ dependencies = [ "keccak", ] +[[package]] +name = "shared" +version = "0.1.0" +dependencies = [ + "bitflags 2.9.1", + "defmt 1.0.1", +] + [[package]] name = "siphasher" version = "1.0.1" @@ -1756,6 +2018,16 @@ dependencies = [ "rgb", ] +[[package]] +name = "snake" +version = "0.1.0" +dependencies = [ + "abi", + "embedded-graphics 0.8.1", + "embedded-snake", + "rand_core 0.6.4", +] + [[package]] name = "spin" version = "0.10.0" @@ -1765,13 +2037,23 @@ dependencies = [ "lock_api", ] +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + [[package]] name = "st7365p-lcd" version = "0.11.0" -source = "git+https://github.com/legitcamper/st7365p-lcd-rs?branch=async#87abf450404865dcb535292e9e1a6a2457fd4599" +source = "git+https://github.com/legitcamper/st7365p-lcd-rs?rev=1d15123929fa7ef73d5d6aead7faf1bba50ce915#1d15123929fa7ef73d5d6aead7faf1bba50ce915" dependencies = [ "bitvec", - "embedded-graphics-core", + "embedded-graphics-core 0.4.0", "embedded-hal 1.0.0", "embedded-hal-async", "heapless", @@ -1935,11 +2217,11 @@ dependencies = [ "bt-hci", "embassy-futures", "embassy-sync 0.6.2", - "embassy-time", + "embassy-time 0.4.0", "embedded-io", "futures", "heapless", - "rand_core", + "rand_core 0.6.4", "static_cell", "trouble-host-macros", "zerocopy", @@ -1983,6 +2265,53 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + [[package]] name = "uuid" version = "1.17.0" diff --git a/Cargo.toml b/Cargo.toml index 6477884..067cbbe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,80 +1,19 @@ -[package] -name = "picocalc-os-rs" -version = "0.1.0" -edition = "2024" +[workspace] +resolver = "3" +members = ["kernel", "abi", "shared", "user-apps/calculator"] [profile.release] -debug = 2 +debug = true +opt-level = "z" +lto = true +codegen-units = 1 + +[profile.release-binary] +inherits = "release" +lto = true +debug = false +opt-level = "s" [profile.dev] lto = true opt-level = "z" - -[features] -default = ["rp235x", "defmt"] -rp2040 = ["embassy-rp/rp2040"] -rp235x = ["embassy-rp/rp235xb"] -trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] -defmt = [ - "dep:defmt", - "panic-probe/print-defmt", - "embassy-executor/defmt", - "embassy-time/defmt", - "embassy-time/defmt-timestamp-uptime", - "embassy-rp/defmt", - "embassy-sync/defmt", - "embedded-graphics/defmt", - "embedded-sdmmc/defmt-log", - # "bt-hci/defmt", - # "cyw43/defmt", - # "cyw43-pio/defmt", -] - -[dependencies] -embassy-executor = { version = "0.7", features = [ - "arch-cortex-m", - "executor-interrupt", - "executor-thread", - "nightly", -] } -embassy-rp = { version = "0.4.0", features = [ - "critical-section-impl", - "unstable-pac", - "time-driver", - "binary-info", -] } -embassy-futures = "0.1.1" -embassy-time = "0.4.0" -embassy-embedded-hal = "0.3.0" -embassy-sync = { version = "0.7" } -trouble-host = { version = "0.1", features = [ - "derive", - "scan", -], optional = true } -bt-hci = { version = "0.2", default-features = false, optional = true } -cyw43 = { version = "0.3.0", features = [ - "firmware-logs", - "bluetooth", -], optional = true } -cyw43-pio = { version = "0.3.0", optional = true } - -embedded-hal-bus = { version = "0.3.0", features = ["async"] } -embedded-hal = "0.2.7" -embedded-hal-async = "1.0.0" -cortex-m = { version = "0.7.7" } -cortex-m-rt = "0.7.5" -panic-probe = "0.3" -portable-atomic = { version = "1.11", features = ["critical-section"] } - -defmt = { version = "0.3", optional = true } -defmt-rtt = "0.4.2" - -embedded-graphics = { version = "0.8.1" } -embedded-sdmmc = { git = "https://github.com/Be-ing/embedded-sdmmc-rs", branch = "bisync", default-features = false } -st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", branch = "async" } - -static_cell = "2.1.1" -bitflags = "2.9.1" -talc = "4.4.3" -spin = "0.10.0" -heapless = "0.8.0" diff --git a/abi/Cargo.toml b/abi/Cargo.toml new file mode 100644 index 0000000..8a9af49 --- /dev/null +++ b/abi/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "abi" +version = "0.1.0" +edition = "2024" + +[dependencies] +embedded-graphics = "0.8.1" +shared = { path = "../shared" } +abi_sys = { path = "../abi_sys" } +talc = "4.4.3" +spin = "0.10.0" +embassy-time = "0.5.0" diff --git a/abi/src/lib.rs b/abi/src/lib.rs new file mode 100644 index 0000000..559c81f --- /dev/null +++ b/abi/src/lib.rs @@ -0,0 +1,74 @@ +#![no_std] + +use abi_sys::draw_iter; +pub use abi_sys::{get_key, print, sleep}; +pub use embassy_time; +pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; +use talc::*; + +static mut ARENA: [u8; 10000] = [0; 10000]; + +#[global_allocator] +static ALLOCATOR: Talck, ClaimOnOom> = + Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) + .lock(); + +pub mod display { + use crate::draw_iter; + use embedded_graphics::{ + Pixel, + geometry::{Dimensions, Point}, + pixelcolor::{Rgb565, RgbColor}, + prelude::{DrawTarget, Size}, + primitives::Rectangle, + }; + + pub const SCREEN_WIDTH: usize = 320; + pub const SCREEN_HEIGHT: usize = 320; + + pub type Pixel565 = Pixel; + + pub struct Display; + + impl Dimensions for Display { + fn bounding_box(&self) -> Rectangle { + Rectangle { + top_left: Point { x: 0, y: 0 }, + size: Size { + width: SCREEN_WIDTH as u32, + height: SCREEN_HEIGHT as u32, + }, + } + } + } + + impl DrawTarget for Display { + type Color = Rgb565; + type Error = (); + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + const BUF_SIZE: usize = 1024; // tune this for performance + let mut buf: [Pixel565; BUF_SIZE] = [Pixel(Point::new(0, 0), Rgb565::BLACK); BUF_SIZE]; + + let mut count = 0; + for p in pixels { + buf[count] = p; + count += 1; + + if count == BUF_SIZE { + draw_iter(&buf[..count]); + count = 0; + } + } + + if count > 0 { + draw_iter(&buf[..count]); + } + + Ok(()) + } + } +} diff --git a/abi_sys/Cargo.toml b/abi_sys/Cargo.toml new file mode 100644 index 0000000..e460dea --- /dev/null +++ b/abi_sys/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "abi_sys" +version = "0.1.0" +edition = "2024" + +[dependencies] +embedded-graphics = "0.8.1" +shared = { path = "../shared" } diff --git a/abi_sys/src/lib.rs b/abi_sys/src/lib.rs new file mode 100644 index 0000000..60e7131 --- /dev/null +++ b/abi_sys/src/lib.rs @@ -0,0 +1,71 @@ +#![no_std] + +extern crate alloc; +use alloc::boxed::Box; + +use core::pin::Pin; +pub use embedded_graphics::{ + Pixel, + geometry::Point, + pixelcolor::{Rgb565, RgbColor}, +}; +use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; + +pub type EntryFn = fn() -> Pin>>; + +#[unsafe(no_mangle)] +#[unsafe(link_section = ".user_reloc")] +pub static mut CALL_ABI_TABLE: [usize; CallAbiTable::COUNT] = [0; CallAbiTable::COUNT]; + +#[repr(usize)] +#[derive(Clone, Copy)] +pub enum CallAbiTable { + Print = 0, + Sleep = 1, + DrawIter = 2, + GetKey = 3, +} + +impl CallAbiTable { + pub const COUNT: usize = 4; +} + +pub type PrintAbi = extern "Rust" fn(msg: &str); + +pub fn print(msg: &str) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::Print as usize]; + let f: PrintAbi = core::mem::transmute(ptr); + f(msg); + } +} + +pub type SleepAbi = extern "Rust" fn(ms: u64); + +pub fn sleep(ms: u64) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::Sleep as usize]; + let f: SleepAbi = core::mem::transmute(ptr); + f(ms); + } +} + +pub type DrawIterAbi = extern "Rust" fn(pixels: &[Pixel]); + +pub fn draw_iter(pixels: &[Pixel]) { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::DrawIter as usize]; + let f: DrawIterAbi = core::mem::transmute(ptr); + f(pixels); + } +} + +pub type GetKeyAbi = extern "Rust" fn() -> Option; + +pub fn get_key() -> Option { + unsafe { + let ptr = CALL_ABI_TABLE[CallAbiTable::GetKey as usize]; + let f: GetKeyAbi = core::mem::transmute(ptr); + f() + } +} diff --git a/justfile b/justfile new file mode 100644 index 0000000..6b97834 --- /dev/null +++ b/justfile @@ -0,0 +1,4 @@ +kernel: calculator + cargo run --bin kernel +calculator: + RUSTFLAGS="-C link-arg=--noinhibit-exec" cargo build --bin calculator --profile release-binary diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml new file mode 100644 index 0000000..a7fe247 --- /dev/null +++ b/kernel/Cargo.toml @@ -0,0 +1,89 @@ +[package] +name = "kernel" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "kernel" +path = "src/main.rs" +test = false +doctest = false +bench = false + +[features] +default = ["rp235x", "defmt"] +rp2040 = ["embassy-rp/rp2040"] +rp235x = ["embassy-rp/rp235xb"] +trouble = ["dep:bt-hci", "dep:cyw43", "dep:cyw43-pio", "dep:trouble-host"] +defmt = [ + "dep:defmt", + "panic-probe/print-defmt", + "embassy-executor/defmt", + "embassy-time/defmt", + "embassy-time/defmt-timestamp-uptime", + "embassy-rp/defmt", + "embassy-sync/defmt", + "embedded-graphics/defmt", + "embedded-sdmmc/defmt-log", + # "bt-hci/defmt", + # "cyw43/defmt", + # "cyw43-pio/defmt", +] + +[dependencies] +embassy-executor = { version = "0.9", features = [ + "arch-cortex-m", + "executor-interrupt", + "executor-thread", + "nightly", +] } +embassy-rp = { version = "0.8.0", features = [ + "critical-section-impl", + "unstable-pac", + "time-driver", +] } +embassy-usb = "0.5.1" +embassy-futures = "0.1.2" +embassy-time = { version = "0.5.0", features = ["generic-queue-8"] } +embassy-embedded-hal = "0.3.1" +embassy-sync = { version = "0.7" } +trouble-host = { version = "0.1", features = [ + "derive", + "scan", +], optional = true } +bt-hci = { version = "0.2", default-features = false, optional = true } +cyw43 = { version = "0.3.0", features = [ + "firmware-logs", + "bluetooth", +], optional = true } +cyw43-pio = { version = "0.3.0", optional = true } + +embedded-hal-bus = { version = "0.3.0", features = ["async"] } +embedded-hal = "0.2.7" +embedded-hal_2 = { package = "embedded-hal", version = "1.0.0" } +embedded-hal-async = "1.0.0" +cortex-m = { version = "0.7.7" } +cortex-m-rt = "0.7.5" +panic-probe = "0.3" +portable-atomic = { version = "1.11", features = ["critical-section"] } +assign-resources = "0.5.0" + +defmt = { version = "0.3", optional = true } +defmt-rtt = "0.4.2" + +embedded-sdmmc = { version = "0.9", default-features = false } +st7365p-lcd = { git = "https://github.com/legitcamper/st7365p-lcd-rs", rev = "1d15123929fa7ef73d5d6aead7faf1bba50ce915" } # async branch +embedded-graphics = { version = "0.8.1" } +embedded-text = "0.7.2" +embedded-layout = "0.4.2" + +static_cell = "2.1.1" +bitflags = "2.9.1" +heapless = "0.8.0" +num_enum = { version = "0.7.4", default-features = false } +goblin = { version = "0.10.0", default-features = false, features = ["elf32"] } +talc = "4.4.3" +spin = "0.10.0" + +shared = { path = "../shared" } +abi_sys = { path = "../abi_sys" } diff --git a/build.rs b/kernel/build.rs similarity index 100% rename from build.rs rename to kernel/build.rs diff --git a/memory.x b/kernel/memory.x similarity index 73% rename from memory.x rename to kernel/memory.x index 5bdbb38..fd22299 100644 --- a/memory.x +++ b/kernel/memory.x @@ -1,22 +1,10 @@ MEMORY { - /* - * The RP2350 has either external or internal flash. - * - * 2 MiB is a safe default here, although a Pico 2 has 4 MiB. - */ FLASH : ORIGIN = 0x10000000, LENGTH = 4096K - /* - * RAM consists of 8 banks, SRAM0-SRAM7, with a striped mapping. - * This is usually good for performance, as it distributes load on - * those banks evenly. - */ RAM : ORIGIN = 0x20000000, LENGTH = 512K - /* - * RAM banks 8 and 9 use a direct mapping. They can be used to have - * memory areas dedicated for some specific job, improving predictability - * of access times. - * Example: Separate stacks for core0 and core1. - */ + + /* Reserve a block of RAM for the user app */ + USERAPP : ORIGIN = 0x20010000, LENGTH = 192K + SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K } @@ -73,3 +61,11 @@ SECTIONS { PROVIDE(start_to_end = __end_block_addr - __start_block_addr); PROVIDE(end_to_start = __start_block_addr - __end_block_addr); + +SECTIONS { + .userapp (NOLOAD) : + { + __userapp_start__ = ORIGIN(USERAPP); + __userapp_end__ = ORIGIN(USERAPP) + LENGTH(USERAPP); + } > USERAPP +} diff --git a/kernel/src/abi.rs b/kernel/src/abi.rs new file mode 100644 index 0000000..8451ffd --- /dev/null +++ b/kernel/src/abi.rs @@ -0,0 +1,46 @@ +use core::{pin::Pin, time::Duration}; + +use abi_sys::{DrawIterAbi, GetKeyAbi, Pixel, PrintAbi, SleepAbi}; +use alloc::boxed::Box; +use defmt::info; +use embassy_futures::block_on; +use embassy_rp::clocks::clk_sys_freq; +use embassy_time::Timer; +use embedded_graphics::{ + Drawable, + draw_target::DrawTarget, + pixelcolor::Rgb565, + prelude::{Point, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle, StyledDrawable}, +}; +use shared::keyboard::KeyEvent; + +use crate::{KEY_CACHE, display::FRAMEBUFFER}; + +// ensure the abi and the kernel fn signatures are the same +const _: PrintAbi = print; +const _: SleepAbi = sleep; +const _: DrawIterAbi = draw_iter; +const _: GetKeyAbi = get_key; + +pub extern "Rust" fn print(msg: &str) { + defmt::info!("{:?}", msg); +} + +pub extern "Rust" fn sleep(ms: u64) { + let cycles_per_ms = clk_sys_freq() / 1000; + for _ in 0..ms { + for _ in 0..cycles_per_ms { + cortex_m::asm::nop(); + } + } +} + +// TODO: maybe return result +pub extern "Rust" fn draw_iter(pixels: &[Pixel]) { + unsafe { FRAMEBUFFER.draw_iter(pixels.iter().copied()).unwrap() } +} + +pub extern "Rust" fn get_key() -> Option { + unsafe { KEY_CACHE.dequeue() } +} diff --git a/kernel/src/display.rs b/kernel/src/display.rs new file mode 100644 index 0000000..14698b2 --- /dev/null +++ b/kernel/src/display.rs @@ -0,0 +1,70 @@ +use crate::framebuffer::AtomicFrameBuffer; +use embassy_rp::{ + Peri, + gpio::{Level, Output}, + peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, + spi::{Async, Spi}, +}; +use embassy_time::{Delay, Timer}; +use embedded_graphics::{ + draw_target::DrawTarget, + pixelcolor::{Rgb565, RgbColor}, + prelude::Dimensions, +}; +use embedded_hal_bus::spi::ExclusiveDevice; +use st7365p_lcd::ST7365P; + +type DISPLAY = ST7365P< + ExclusiveDevice, Output<'static>, Delay>, + Output<'static>, + Output<'static>, + Delay, +>; + +pub const SCREEN_WIDTH: usize = 320; +pub const SCREEN_HEIGHT: usize = 320; + +pub static mut FRAMEBUFFER: AtomicFrameBuffer = AtomicFrameBuffer::new(); + +pub async fn init_display( + spi: Spi<'static, SPI1, Async>, + cs: Peri<'static, PIN_13>, + data: Peri<'static, PIN_14>, + reset: Peri<'static, PIN_15>, +) -> DISPLAY { + let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); + let mut display = ST7365P::new( + spi_device, + Output::new(data, Level::Low), + Some(Output::new(reset, Level::High)), + false, + true, + Delay, + ); + display.init().await.unwrap(); + display.set_custom_orientation(0x40).await.unwrap(); + unsafe { FRAMEBUFFER.draw(&mut display).await.unwrap() } + display.set_on().await.unwrap(); + + display +} + +pub fn clear_fb() { + let bounds = unsafe { FRAMEBUFFER.bounding_box() }; + unsafe { + let _ = FRAMEBUFFER.fill_solid(&bounds, Rgb565::BLACK); + } +} + +pub async fn display_handler(mut display: DISPLAY) { + loop { + unsafe { + FRAMEBUFFER + .partial_draw_batched(&mut display) + .await + .unwrap() + } + + Timer::after_millis(32).await; // 30 fps + } +} diff --git a/kernel/src/elf.rs b/kernel/src/elf.rs new file mode 100644 index 0000000..e7e6c2b --- /dev/null +++ b/kernel/src/elf.rs @@ -0,0 +1,205 @@ +#![allow(static_mut_refs)] + +use crate::{ + abi, + storage::{File, SDCARD}, +}; +use abi_sys::{CallAbiTable, EntryFn}; +use alloc::{vec, vec::Vec}; +use embedded_sdmmc::ShortFileName; +use goblin::{ + elf::{ + header::header32::Header, + program_header::program_header32::{PT_LOAD, ProgramHeader}, + section_header::SHT_SYMTAB, + }, + elf32::{section_header::SectionHeader, sym::Sym}, +}; + +const ELF32_HDR_SIZE: usize = 52; + +// userland ram region defined in memory.x +unsafe extern "C" { + static __userapp_start__: u8; + static __userapp_end__: u8; +} + +pub async unsafe fn load_binary(name: &ShortFileName) -> Result { + let mut sd_lock = SDCARD.get().lock().await; + let sd = sd_lock.as_mut().unwrap(); + + let mut error = ""; + let mut entry = 0; + + let mut header_buf = [0; ELF32_HDR_SIZE]; + + sd.read_file(name, |mut file| { + file.read(&mut header_buf).unwrap(); + let elf_header = Header::from_bytes(&header_buf); + + let mut program_headers_buf = vec![0_u8; elf_header.e_phentsize as usize]; + for i in 1..=elf_header.e_phnum { + file.seek_from_start(elf_header.e_phoff + (elf_header.e_phentsize * i) as u32) + .unwrap(); + file.read(&mut program_headers_buf).unwrap(); + + let ph = cast_phdr(&program_headers_buf); + + if ph.p_type == PT_LOAD { + load_segment(&mut file, &ph).unwrap() + } + } + + // MUST MATCH ABI EXACTLY + let entries: &[(CallAbiTable, usize)] = &[ + (CallAbiTable::Print, abi::print as usize), + (CallAbiTable::Sleep, abi::sleep as usize), + (CallAbiTable::DrawIter, abi::draw_iter as usize), + (CallAbiTable::GetKey, abi::get_key as usize), + ]; + assert!(entries.len() == CallAbiTable::COUNT); + + patch_abi(entries, &elf_header, &mut file).unwrap(); + + // TODO: dynamically search for abi table + + entry = elf_header.e_entry as u32; + }) + .await + .unwrap(); + + if entry != 0 { + Ok(unsafe { core::mem::transmute(entry) }) + } else { + Err(error) + } +} + +fn patch_abi( + entries: &[(CallAbiTable, usize)], + elf_header: &Header, + file: &mut File, +) -> Result<(), ()> { + for i in 1..=elf_header.e_shnum { + let sh = read_section(file, &elf_header, i.into()); + + // find the symbol table + if sh.sh_type == SHT_SYMTAB { + let mut symtab_buf = vec![0u8; sh.sh_size as usize]; + file.seek_from_start(sh.sh_offset).unwrap(); + file.read(&mut symtab_buf).unwrap(); + + // Cast buffer into symbols + let sym_count = sh.sh_size as usize / sh.sh_entsize as usize; + for i in 0..sym_count { + let sym_bytes = + &symtab_buf[i * sh.sh_entsize as usize..(i + 1) * sh.sh_entsize as usize]; + let sym = cast_sym(sym_bytes); + + let str_sh = read_section(file, &elf_header, sh.sh_link); + + let mut name = Vec::new(); + file.seek_from_start(str_sh.sh_offset + sym.st_name) + .unwrap(); + + loop { + let mut byte = [0u8; 1]; + file.read(&mut byte).unwrap(); + if byte[0] == 0 { + break; + } + name.push(byte[0]); + } + + let symbol_name = core::str::from_utf8(&name).unwrap(); + if symbol_name == "CALL_ABI_TABLE" { + let table_base = sym.st_value as *mut usize; + + for &(abi_idx, func_ptr) in entries { + unsafe { + table_base.add(abi_idx as usize).write(func_ptr); + } + } + return Ok(()); + } + } + } + } + Err(()) +} + +fn read_section(file: &mut File, elf_header: &Header, section: u32) -> SectionHeader { + let mut section_header_buf = vec![0_u8; elf_header.e_shentsize as usize]; + + file.seek_from_start(elf_header.e_shoff + (elf_header.e_shentsize as u32 * section)) + .unwrap(); + file.read(&mut section_header_buf).unwrap(); + + cast_shdr(§ion_header_buf) +} + +fn load_segment(file: &mut File, ph: &ProgramHeader) -> Result<(), ()> { + let dst_start = ph.p_vaddr as *mut u8; + let filesz = ph.p_filesz as usize; + let memsz = ph.p_memsz as usize; + let vaddr = ph.p_vaddr as usize; + let mut remaining = filesz; + let mut dst_ptr = dst_start; + let mut file_offset = ph.p_offset; + + let seg_start = vaddr; + let seg_end = vaddr + memsz; + + // Bounds check: make sure segment fits inside payload region + let user_start = unsafe { &__userapp_start__ as *const u8 as usize }; + let user_end = unsafe { &__userapp_end__ as *const u8 as usize }; + if seg_start < user_start || seg_end > user_end { + panic!( + "Segment out of bounds: {:x}..{:x} not within {:x}..{:x}", + seg_start, seg_end, user_start, user_end + ); + } + + // Buffer for chunked reads (512 bytes is typical SD sector size) + let mut buf = [0u8; 512]; + + while remaining > 0 { + let to_read = core::cmp::min(remaining, buf.len()); + // Read chunk from file + file.seek_from_start(file_offset).unwrap(); + file.read(&mut buf[..to_read]).unwrap(); + + unsafe { + // Copy chunk directly into destination memory + core::ptr::copy_nonoverlapping(buf.as_ptr(), dst_ptr, to_read); + dst_ptr = dst_ptr.add(to_read); + } + + remaining -= to_read; + file_offset += to_read as u32; + } + + // Zero BSS (memsz - filesz) + if memsz > filesz { + unsafe { + core::ptr::write_bytes(dst_ptr, 0, memsz - filesz); + } + } + + Ok(()) +} + +fn cast_phdr(buf: &[u8]) -> ProgramHeader { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const ProgramHeader) } +} + +fn cast_shdr(buf: &[u8]) -> SectionHeader { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const SectionHeader) } +} + +fn cast_sym(buf: &[u8]) -> Sym { + assert!(buf.len() >= core::mem::size_of::()); + unsafe { core::ptr::read(buf.as_ptr() as *const Sym) } +} diff --git a/kernel/src/framebuffer.rs b/kernel/src/framebuffer.rs new file mode 100644 index 0000000..402551c --- /dev/null +++ b/kernel/src/framebuffer.rs @@ -0,0 +1,432 @@ +use crate::display::{SCREEN_HEIGHT, SCREEN_WIDTH}; +use core::sync::atomic::{AtomicBool, Ordering}; +use embassy_sync::lazy_lock::LazyLock; +use embedded_graphics::{ + draw_target::DrawTarget, + pixelcolor::{ + Rgb565, + raw::{RawData, RawU16}, + }, + prelude::*, + primitives::Rectangle, +}; +use embedded_hal_2::digital::OutputPin; +use embedded_hal_async::{delay::DelayNs, spi::SpiDevice}; +use heapless::Vec; +use st7365p_lcd::{FrameBuffer, ST7365P}; + +pub const TILE_SIZE: usize = 16; // 16x16 tile +pub const TILE_COUNT: usize = (SCREEN_WIDTH / TILE_SIZE) * (SCREEN_HEIGHT / TILE_SIZE); // 400 tiles + +// Group of tiles for batching +pub const MAX_META_TILES: usize = SCREEN_WIDTH / TILE_SIZE; // max number of meta tiles in buffer +type MetaTileVec = heapless::Vec; + +const SIZE: usize = SCREEN_HEIGHT * SCREEN_WIDTH; + +static mut BUFFER: [u16; SIZE] = [0; SIZE]; + +static mut DIRTY_TILES: LazyLock> = LazyLock::new(|| { + let mut tiles = Vec::new(); + for _ in 0..TILE_COUNT { + tiles.push(AtomicBool::new(true)).unwrap(); + } + tiles +}); + +#[allow(dead_code)] +pub struct AtomicFrameBuffer; + +impl AtomicFrameBuffer { + pub const fn new() -> Self { + Self + } + + fn mark_tiles_dirty(&mut self, rect: Rectangle) { + let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let start_tx = (rect.top_left.x as usize) / TILE_SIZE; + let end_tx = ((rect.top_left.x + rect.size.width as i32 - 1) as usize) / TILE_SIZE; + let start_ty = (rect.top_left.y as usize) / TILE_SIZE; + let end_ty = ((rect.top_left.y + rect.size.height as i32 - 1) as usize) / TILE_SIZE; + + for ty in start_ty..=end_ty { + for tx in start_tx..=end_tx { + let tile_idx = ty * tiles_x + tx; + unsafe { DIRTY_TILES.get_mut()[tile_idx].store(true, Ordering::Relaxed) }; + } + } + } + + fn set_pixel(&mut self, x: u16, y: u16, color: u16) -> Result<(), ()> { + unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; + + Ok(()) + } + + fn set_pixels_buffered>( + &mut self, + sx: u16, + sy: u16, + ex: u16, + ey: u16, + colors: P, + ) -> Result<(), ()> { + if sx >= self.size().width as u16 - 1 + || ex >= self.size().width as u16 - 1 + || sy >= self.size().height as u16 - 1 + || ey >= self.size().height as u16 - 1 + { + return Err(()); // Bounds check + } + + let mut color_iter = colors.into_iter(); + + for y in sy..=ey { + for x in sx..=ex { + if let Some(color) = color_iter.next() { + unsafe { BUFFER[(y as usize * SCREEN_WIDTH) + x as usize] = color }; + } else { + return Err(()); // Not enough data + } + } + } + + // Optional: check that we consumed *exactly* the right amount + if color_iter.next().is_some() { + return Err(()); // Too much data + } + + Ok(()) + } + + // walk the dirty tiles and mark groups of tiles(meta-tiles) for batched updates + fn find_meta_tiles(&mut self, tiles_x: usize, tiles_y: usize) -> MetaTileVec { + let mut meta_tiles: MetaTileVec = Vec::new(); + + for ty in 0..tiles_y { + let mut tx = 0; + while tx < tiles_x { + let idx = ty * tiles_x + tx; + if !unsafe { DIRTY_TILES.get()[idx].load(Ordering::Acquire) } { + tx += 1; + continue; + } + + // Start meta-tile at this tile + let mut width_tiles = 1; + let height_tiles = 1; + + // Grow horizontally, but keep under MAX_TILES_PER_METATILE + while tx + width_tiles < tiles_x + && unsafe { + DIRTY_TILES.get()[ty * tiles_x + tx + width_tiles].load(Ordering::Relaxed) + } + && (width_tiles + height_tiles) <= MAX_META_TILES + { + width_tiles += 1; + } + + // TODO: for simplicity, skipped vertical growth + + for x_off in 0..width_tiles { + unsafe { + DIRTY_TILES.get()[ty * tiles_x + tx + x_off] + .store(false, Ordering::Release); + }; + } + + // new meta-tile pos + let rect = Rectangle::new( + Point::new((tx * TILE_SIZE) as i32, (ty * TILE_SIZE) as i32), + Size::new( + (width_tiles * TILE_SIZE) as u32, + (height_tiles * TILE_SIZE) as u32, + ), + ); + + if meta_tiles.push(rect).is_err() { + return meta_tiles; + }; + + tx += width_tiles; + } + } + + meta_tiles + } + + /// Sends the entire framebuffer to the display + pub async fn draw( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + { + display + .set_pixels_buffered( + 0, + 0, + self.size().width as u16 - 1, + self.size().height as u16 - 1, + unsafe { &BUFFER }, + ) + .await?; + + unsafe { + for tile in DIRTY_TILES.get_mut().iter() { + tile.store(false, Ordering::Release); + } + }; + + Ok(()) + } + + /// Sends only dirty tiles (16x16px) individually to the display without batching + pub async fn partial_draw( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + { + if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } { + let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; + + let mut tile_buffer = [0u16; TILE_SIZE * TILE_SIZE]; + + for ty in 0..tiles_y { + for tx in 0..tiles_x { + if unsafe { !DIRTY_TILES.get()[ty * tiles_x + tx].load(Ordering::Acquire) } { + continue; + } + + let x = tx * TILE_SIZE; + let y = ty * TILE_SIZE; + + // Copy pixels for the tile into tile_buffer + for row in 0..TILE_SIZE { + for col in 0..TILE_SIZE { + let actual_x = x + col; + let actual_y = y + row; + + if actual_x < SCREEN_WIDTH && actual_y < SCREEN_HEIGHT { + let idx = actual_y * SCREEN_WIDTH + actual_x; + tile_buffer[row * TILE_SIZE + col] = unsafe { BUFFER[idx] }; + } else { + // Out of bounds, fill with zero (or background) + tile_buffer[row * TILE_SIZE + col] = 0; + } + } + } + + // Send the tile's pixel data to the display + display + .set_pixels_buffered( + x as u16, + y as u16, + (x + TILE_SIZE - 1).min(SCREEN_WIDTH - 1) as u16, + (y + TILE_SIZE - 1).min(SCREEN_HEIGHT - 1) as u16, + &tile_buffer, + ) + .await?; + + // Mark tile as clean + unsafe { + DIRTY_TILES.get_mut()[ty * tiles_x + tx].store(false, Ordering::Release) + }; + } + } + } + + Ok(()) + } + + /// Sends only dirty tiles (16x16px) in batches to the display + pub async fn partial_draw_batched( + &mut self, + display: &mut ST7365P, + ) -> Result<(), ()> + where + SPI: SpiDevice, + DC: OutputPin, + RST: OutputPin, + DELAY: DelayNs, + { + if unsafe { DIRTY_TILES.get().iter().any(|p| p.load(Ordering::Acquire)) } { + let tiles_x = (SCREEN_WIDTH + TILE_SIZE - 1) / TILE_SIZE; + let tiles_y = (SCREEN_HEIGHT + TILE_SIZE - 1) / TILE_SIZE; + + let meta_tiles = self.find_meta_tiles(tiles_x, tiles_y); + + // buffer for copying meta tiles before sending to display + let mut pixel_buffer: heapless::Vec = + Vec::new(); + + for rect in meta_tiles { + let rect_width = rect.size.width as usize; + let rect_height = rect.size.height as usize; + let rect_x = rect.top_left.x as usize; + let rect_y = rect.top_left.y as usize; + + pixel_buffer.clear(); + + for row in 0..rect_height { + let y = rect_y + row; + let start = y * SCREEN_WIDTH + rect_x; + let end = start + rect_width; + + // Safe: we guarantee buffer will not exceed MAX_META_TILE_PIXELS + pixel_buffer + .extend_from_slice(unsafe { &BUFFER[start..end] }) + .unwrap(); + } + + display + .set_pixels_buffered( + rect_x as u16, + rect_y as u16, + (rect_x + rect_width - 1) as u16, + (rect_y + rect_height - 1) as u16, + &pixel_buffer, + ) + .await?; + + // walk the meta-tile and set as clean + let start_tx = rect_x / TILE_SIZE; + let start_ty = rect_y / TILE_SIZE; + let end_tx = (rect_x + rect_width - 1) / TILE_SIZE; + let end_ty = (rect_y + rect_height - 1) / TILE_SIZE; + + for ty in start_ty..=end_ty { + for tx in start_tx..=end_tx { + let tile_idx = ty * tiles_x + tx; + unsafe { DIRTY_TILES.get_mut()[tile_idx].store(false, Ordering::Release) }; + } + } + } + } + + Ok(()) + } +} + +impl DrawTarget for AtomicFrameBuffer { + type Error = (); + type Color = Rgb565; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + let mut dirty_rect: Option = None; + + for Pixel(coord, color) in pixels { + if coord.x >= 0 && coord.y >= 0 { + let x = coord.x as i32; + let y = coord.y as i32; + + if (x as usize) < SCREEN_WIDTH && (y as usize) < SCREEN_HEIGHT { + unsafe { + BUFFER[(y as usize) * SCREEN_WIDTH + (x as usize)] = + RawU16::from(color).into_inner() + }; + + if let Some(ref mut rect) = dirty_rect { + rect.top_left.x = rect.top_left.x.min(x); + rect.top_left.y = rect.top_left.y.min(y); + let max_x = (rect.top_left.x + rect.size.width as i32 - 1).max(x); + let max_y = (rect.top_left.y + rect.size.height as i32 - 1).max(y); + rect.size.width = (max_x - rect.top_left.x + 1) as u32; + rect.size.height = (max_y - rect.top_left.y + 1) as u32; + } else { + dirty_rect = Some(Rectangle::new(Point::new(x, y), Size::new(1, 1))); + } + } + } + } + + if let Some(rect) = dirty_rect { + self.mark_tiles_dirty(rect); + } + + Ok(()) + } + + fn fill_contiguous(&mut self, area: &Rectangle, colors: I) -> Result<(), Self::Error> + where + I: IntoIterator, + { + let drawable_area = area.intersection(&Rectangle::new(Point::zero(), self.size())); + + if drawable_area.size != Size::zero() { + // We assume that `colors` iterator is in row-major order for the original `area` + // So we must skip rows/pixels that are clipped + let area_width = area.size.width; + let area_height = area.size.height; + let mut colors = colors.into_iter(); + + for y in 0..area_height { + for x in 0..area_width { + let p = area.top_left + Point::new(x as i32, y as i32); + + if drawable_area.contains(p) { + if let Some(color) = colors.next() { + self.set_pixel( + p.x as u16, + p.y as u16, + RawU16::from(color).into_inner(), + )?; + } else { + break; + } + } else { + // Still need to consume the color even if not used! + let _ = colors.next(); + } + } + } + + self.mark_tiles_dirty(*area); + } + + Ok(()) + } + + fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> { + self.fill_contiguous( + area, + core::iter::repeat(color).take((self.size().width * self.size().height) as usize), + ) + } + + fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> { + self.set_pixels_buffered( + 0, + 0, + self.size().width as u16 - 1, + self.size().height as u16 - 1, + core::iter::repeat(RawU16::from(color).into_inner()) + .take((self.size().width * self.size().height) as usize), + )?; + + unsafe { + for tile in DIRTY_TILES.get_mut().iter() { + tile.store(true, Ordering::Relaxed); + } + } + + Ok(()) + } +} + +impl OriginDimensions for AtomicFrameBuffer { + fn size(&self) -> Size { + Size::new(SCREEN_WIDTH as u32, SCREEN_HEIGHT as u32) + } +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs new file mode 100644 index 0000000..7f208a8 --- /dev/null +++ b/kernel/src/main.rs @@ -0,0 +1,268 @@ +#![feature(impl_trait_in_assoc_type)] +#![feature(type_alias_impl_trait)] +#![feature(ascii_char)] +#![cfg_attr(not(test), no_std)] +#![cfg_attr(not(test), no_main)] +#![allow(static_mut_refs)] + +extern crate alloc; + +mod abi; +mod display; +mod elf; +mod framebuffer; +mod peripherals; +mod scsi; +mod storage; +mod ui; +mod usb; +mod utils; + +use crate::{ + display::{FRAMEBUFFER, clear_fb, display_handler, init_display}, + elf::load_binary, + peripherals::{ + conf_peripherals, + keyboard::{KeyCode, KeyState, read_keyboard_fifo}, + }, + storage::{SDCARD, SdCard}, + ui::{SELECTIONS, ui_handler}, + usb::usb_handler, +}; +use abi_sys::EntryFn; + +use {defmt_rtt as _, panic_probe as _}; + +use assign_resources::assign_resources; +use defmt::unwrap; +use embassy_executor::{Executor, Spawner}; +use embassy_futures::join::{join, join3, join4, join5}; +use embassy_rp::{ + Peri, + gpio::{Input, Level, Output, Pull}, + i2c::{self, I2c}, + multicore::{Stack, spawn_core1}, + peripherals::{ + DMA_CH0, DMA_CH1, I2C1, PIN_6, PIN_7, PIN_10, PIN_11, PIN_12, PIN_13, PIN_14, PIN_15, + PIN_16, PIN_17, PIN_18, PIN_19, PIN_22, SPI0, SPI1, USB, + }, + spi::{self, Spi}, + usb as embassy_rp_usb, +}; +use embassy_sync::{ + blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel, mutex::Mutex, signal::Signal, +}; +use embassy_time::{Delay, Timer}; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::SdCard as SdmmcSdCard; +use heapless::spsc::Queue; +use shared::keyboard::KeyEvent; +use static_cell::StaticCell; +use talc::*; + +embassy_rp::bind_interrupts!(struct Irqs { + I2C1_IRQ => i2c::InterruptHandler; + USBCTRL_IRQ => embassy_rp_usb::InterruptHandler; +}); + +static mut CORE1_STACK: Stack<16384> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); + +static mut ARENA: [u8; 10 * 1024] = [0; 10 * 1024]; + +#[global_allocator] +static ALLOCATOR: Talck, ClaimOnOom> = + Talc::new(unsafe { ClaimOnOom::new(Span::from_array(core::ptr::addr_of!(ARENA).cast_mut())) }) + .lock(); + +static TASK_STATE: Mutex = Mutex::new(TaskState::Ui); +static TASK_STATE_CHANGED: Signal = Signal::new(); + +#[derive(Copy, Clone, PartialEq)] +enum TaskState { + Ui, + Kernel, +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + spawn_core1( + p.CORE1, + unsafe { &mut *core::ptr::addr_of_mut!(CORE1_STACK) }, + move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(userland_task()))); + }, + ); + + let display = Display { + spi: p.SPI1, + clk: p.PIN_10, + mosi: p.PIN_11, + miso: p.PIN_12, + dma1: p.DMA_CH0, + dma2: p.DMA_CH1, + cs: p.PIN_13, + data: p.PIN_14, + reset: p.PIN_15, + }; + let sd = Sd { + spi: p.SPI0, + clk: p.PIN_18, + mosi: p.PIN_19, + miso: p.PIN_16, + cs: p.PIN_17, + det: p.PIN_22, + }; + let mcu = Mcu { + i2c: p.I2C1, + clk: p.PIN_7, + data: p.PIN_6, + }; + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(kernel_task(display, sd, mcu, p.USB)))); +} + +// One-slot channel to pass EntryFn from core1 +static BINARY_CH: Channel = Channel::new(); + +// runs dynamically loaded elf files +#[embassy_executor::task] +async fn userland_task() { + let recv = BINARY_CH.receiver(); + loop { + let entry = recv.receive().await; + defmt::info!("Got Entry"); + + // disable kernel ui + { + let mut state = TASK_STATE.lock().await; + *state = TaskState::Kernel; + } + + // clear_fb(); + + defmt::info!("Executing Binary"); + entry().await; + + // enable kernel ui + { + let mut state = TASK_STATE.lock().await; + *state = TaskState::Ui; + } + } +} + +struct Display { + spi: Peri<'static, SPI1>, + clk: Peri<'static, PIN_10>, + mosi: Peri<'static, PIN_11>, + miso: Peri<'static, PIN_12>, + dma1: Peri<'static, DMA_CH0>, + dma2: Peri<'static, DMA_CH1>, + cs: Peri<'static, PIN_13>, + data: Peri<'static, PIN_14>, + reset: Peri<'static, PIN_15>, +} +struct Sd { + spi: Peri<'static, SPI0>, + clk: Peri<'static, PIN_18>, + mosi: Peri<'static, PIN_19>, + miso: Peri<'static, PIN_16>, + cs: Peri<'static, PIN_17>, + det: Peri<'static, PIN_22>, +} +struct Mcu { + i2c: Peri<'static, I2C1>, + clk: Peri<'static, PIN_7>, + data: Peri<'static, PIN_6>, +} + +#[embassy_executor::task] +async fn kernel_task(display: Display, sd: Sd, mcu: Mcu, usb: Peri<'static, USB>) { + // MCU i2c bus for peripherals + let mut config = i2c::Config::default(); + config.frequency = 400_000; + let i2c1 = I2c::new_async(mcu.i2c, mcu.clk, mcu.data, Irqs, config); + conf_peripherals(i2c1).await; + + Timer::after_millis(250).await; + + let mut config = spi::Config::default(); + config.frequency = 16_000_000; + let spi = Spi::new( + display.spi, + display.clk, + display.mosi, + display.miso, + display.dma1, + display.dma2, + config, + ); + let display = init_display(spi, display.cs, display.data, display.reset).await; + + let display_fut = display_handler(display); + + let ui_fut = ui_handler(); + + let binary_search_fut = async { + loop { + { + let mut guard = SDCARD.get().lock().await; + + if let Some(sd) = guard.as_mut() { + let files = sd.list_files_by_extension(".bin").unwrap(); + let mut select = SELECTIONS.lock().await; + + if select.selections != files { + select.selections = files; + select.reset(); + } + } + } + Timer::after_secs(5).await; + } + }; + + { + let mut config = spi::Config::default(); + config.frequency = 400_000; + let spi = Spi::new_blocking(sd.spi, sd.clk, sd.mosi, sd.miso, config.clone()); + let cs = Output::new(sd.cs, Level::High); + let det = Input::new(sd.det, Pull::None); + + let device = ExclusiveDevice::new(spi, cs, Delay).unwrap(); + let sdcard = SdmmcSdCard::new(device, Delay); + + config.frequency = 32_000_000; + sdcard.spi(|dev| dev.bus_mut().set_config(&config)); + SDCARD.get().lock().await.replace(SdCard::new(sdcard, det)); + }; + + let usb = embassy_rp_usb::Driver::new(usb, Irqs); + let usb_fut = usb_handler(usb); + + let key_abi_fut = async { + loop { + Timer::after_millis(100).await; + get_keys().await + } + }; + + join5(display_fut, ui_fut, usb_fut, binary_search_fut, key_abi_fut).await; +} + +static mut KEY_CACHE: Queue = Queue::new(); + +async fn get_keys() { + if let Some(event) = read_keyboard_fifo().await { + if let KeyState::Pressed = event.state { + unsafe { + let _ = KEY_CACHE.enqueue(event); + } + } + } +} diff --git a/kernel/src/peripherals/keyboard.rs b/kernel/src/peripherals/keyboard.rs new file mode 100644 index 0000000..a9ef605 --- /dev/null +++ b/kernel/src/peripherals/keyboard.rs @@ -0,0 +1,59 @@ +use crate::peripherals::PERIPHERAL_BUS; +pub use shared::keyboard::{KeyCode, KeyEvent, KeyState, Modifiers}; + +const REG_ID_KEY: u8 = 0x04; +const REG_ID_FIF: u8 = 0x09; + +const KEY_CAPSLOCK: u8 = 1 << 5; +const KEY_NUMLOCK: u8 = 1 << 6; +const KEY_COUNT_MASK: u8 = 0x1F; // 0x1F == 31 + +pub async fn read_keyboard_fifo() -> Option { + let mut i2c = PERIPHERAL_BUS.get().lock().await; + let i2c = i2c.as_mut().unwrap(); + + let mut key_status = [0_u8; 1]; + + if i2c + .write_read_async(super::MCU_ADDR, [REG_ID_KEY], &mut key_status) + .await + .is_ok() + { + let _caps = key_status[0] & KEY_CAPSLOCK == KEY_CAPSLOCK; + let _num = key_status[0] & KEY_NUMLOCK == KEY_NUMLOCK; + let fifo_count = key_status[0] & KEY_COUNT_MASK; + + if fifo_count >= 1 { + let mut event = [0_u8; 2]; + + if i2c + .write_read_async(super::MCU_ADDR, [REG_ID_FIF], &mut event) + .await + .is_ok() + { + return Some(KeyEvent { + state: KeyState::from(event[0]), + key: KeyCode::from(event[1]), + mods: Modifiers::NONE, + }); + } + } + } + None +} + +const REG_ID_DEB: u8 = 0x06; +const REG_ID_FRQ: u8 = 0x07; + +pub async fn configure_keyboard(debounce: u8, poll_freq: u8) { + let mut i2c = PERIPHERAL_BUS.get().lock().await; + let i2c = i2c.as_mut().unwrap(); + + let _ = i2c + .write_read_async(super::MCU_ADDR, [REG_ID_DEB], &mut [debounce]) + .await; + + let _ = i2c + .write_read_async(super::MCU_ADDR, [REG_ID_FRQ], &mut [poll_freq]) + .await; +} diff --git a/src/peripherals/mod.rs b/kernel/src/peripherals/mod.rs similarity index 92% rename from src/peripherals/mod.rs rename to kernel/src/peripherals/mod.rs index d00c7ec..25a436e 100644 --- a/src/peripherals/mod.rs +++ b/kernel/src/peripherals/mod.rs @@ -5,7 +5,9 @@ use embassy_rp::{ i2c::{Async, I2c}, peripherals::I2C1, }; -use embassy_sync::{blocking_mutex::raw::NoopRawMutex, lazy_lock::LazyLock, mutex::Mutex}; +use embassy_sync::{ + blocking_mutex::raw::CriticalSectionRawMutex, lazy_lock::LazyLock, mutex::Mutex, +}; use embassy_time::Timer; pub mod keyboard; @@ -15,7 +17,7 @@ use crate::peripherals::keyboard::{configure_keyboard, read_keyboard_fifo}; const MCU_ADDR: u8 = 0x1F; type I2CBUS = I2c<'static, I2C1, Async>; -pub static PERIPHERAL_BUS: LazyLock>> = +pub static PERIPHERAL_BUS: LazyLock>> = LazyLock::new(|| Mutex::new(None)); const REG_ID_VER: u8 = 0x01; diff --git a/kernel/src/scsi/mod.rs b/kernel/src/scsi/mod.rs new file mode 100644 index 0000000..e92fdeb --- /dev/null +++ b/kernel/src/scsi/mod.rs @@ -0,0 +1,320 @@ +use crate::format; +use embassy_usb::driver::{Driver, EndpointIn, EndpointOut}; +use embassy_usb::types::StringIndex; +use embassy_usb::{Builder, Config}; +use embedded_sdmmc::{Block, BlockIdx}; +use heapless::Vec; + +mod scsi_types; +use scsi_types::*; + +use crate::storage::{SDCARD, SdCard}; + +const BULK_ENDPOINT_PACKET_SIZE: usize = 64; + +pub struct MassStorageClass<'d, D: Driver<'d>> { + bulk_out: D::EndpointOut, + bulk_in: D::EndpointIn, +} + +impl<'d, 's, D: Driver<'d>> MassStorageClass<'d, D> { + pub fn new(builder: &mut Builder<'d, D>) -> Self { + let mut function = builder.function(0x08, SUBCLASS_SCSI, 0x50); // Mass Storage class + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0x08, SUBCLASS_SCSI, 0x50, None); + + let bulk_out = alt.endpoint_bulk_out(None, BULK_ENDPOINT_PACKET_SIZE as u16); + let bulk_in = alt.endpoint_bulk_in(None, BULK_ENDPOINT_PACKET_SIZE as u16); + + Self { bulk_out, bulk_in } + } + + pub async fn poll(&mut self) { + loop { + let mut cbw_buf = [0u8; 31]; + if let Ok(n) = self.bulk_out.read(&mut cbw_buf).await { + if n == 31 { + if let Some(cbw) = CommandBlockWrapper::parse(&cbw_buf[..n]) { + // TODO: validate cbw + if self.handle_command(&cbw.CBWCB).await.is_ok() { + self.send_csw_success(cbw.dCBWTag).await + } else { + self.send_csw_fail(cbw.dCBWTag).await + } + } + } + } + } + } + + async fn handle_command(&mut self, cbw: &[u8]) -> Result<(), ()> { + let mut response: Vec = Vec::new(); + let mut block = [Block::new(); 1]; + + match parse_cb(cbw) { + ScsiCommand::Unknown => { + #[cfg(feature = "defmt")] + defmt::warn!("Got unexpected scsi command: {}", cbw); + Err(()) + } + ScsiCommand::Inquiry { + evpd, + page_code, + alloc_len, + } => { + if !evpd { + response.push(0x00).map_err(|_| ())?; // Direct-access block device + response.push(0x80).map_err(|_| ())?; // Removable + response.push(0x05).map_err(|_| ())?; // SPC-3 compliance + response.push(0x02).map_err(|_| ())?; // Response data format + response.push(0x00).map_err(|_| ())?; // Additional length - edited later + response.push(0x00).map_err(|_| ())?; // FLAGS + response.push(0x00).map_err(|_| ())?; // FLAGS + response.push(0).map_err(|_| ())?; // FLAGS + assert!(response.len() == 8); + + let vendor = b"LEGTCMPR"; + assert!(vendor.len() == 8); + response.extend_from_slice(vendor)?; + + let product = b"Pico Calc Sdcard"; + assert!(product.len() == 16); + response.extend_from_slice(product)?; + + let version = b"1.00"; + assert!(version.len() == 4); + response.extend_from_slice(version)?; // 4-byte firmware version + + let addl_len = response.len() - 5; + response[4] = addl_len as u8; + assert!(response.len() == 36); + } else { + match page_code { + 0x00 => { + response + .extend_from_slice(&[ + 0x00, // Peripheral Qualifier + Peripheral Device Type (0x00 = Direct-access block device) + 0x00, // Page Code (same as requested: 0x00) + 0x00, 0x03, // Page Length: 3 bytes follow + 0x00, // Supported VPD Page: 0x00 (this one — the "Supported VPD Pages" page itself) + 0x80, // Supported VPD Page: 0x80 (Unit Serial Number) + 0x83, // Supported VPD Page: 0x83 (Device Identification) + ]) + .map_err(|_| ())? + } + 0x80 => { + let serial = b"Pico Calc"; + response.extend_from_slice(&[ + 0x00, // Peripheral Qualifier & Device Type + 0x80, // Page Code = 0x80 (Unit Serial Number) + 0x00, // Reserved + serial.len() as u8, + ])?; + response.extend_from_slice(serial)?; + } + 0x83 => { + let id = b"SdCard"; + response.extend_from_slice(&[ + 0x00, + 0x83, // Page code + 0x00, + (4 + id.len()) as u8, // Length + 0x02, // ASCII identifier + 0x01, // Identifier type + 0x00, // Reserved + id.len() as u8, + ])?; + response.extend_from_slice(id)?; + } + _ => (), + } + }; + + let len = core::cmp::min(alloc_len as usize, response.len()); + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::TestUnitReady => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + if sdcard.is_attached() { + Ok(()) + } else { + Err(()) + } + } + ScsiCommand::RequestSense { desc, alloc_len } => Ok(()), + ScsiCommand::ModeSense6 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => { + // DBD=0, no block descriptors; total length = 4 + let response = [ + 0x03, // Mode data length (excluding this byte): 3 + 0x00, // Medium type + 0x00, // Device-specific parameter + 0x00, // Block descriptor length = 0 (DBD = 1) + ]; + + let len = alloc_len.min(response.len() as u8) as usize; + + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::ModeSense10 { + dbd, + page_control, + page_code, + subpage_code, + alloc_len, + } => { + let response = [ + 0x00, 0x06, // Mode data length = 6 + 0x00, // Medium type + 0x00, // Device-specific parameter + 0x00, 0x00, // Reserved + 0x00, 0x00, // Block descriptor length = 0 + ]; + + let len = alloc_len.min(response.len() as u16) as usize; + + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::ReadCapacity10 => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + + let block_size = SdCard::BLOCK_SIZE as u64; + let total_blocks = sdcard.size() / block_size; + + let last_lba = total_blocks.checked_sub(1).unwrap_or(0); + + response.extend_from_slice(&(last_lba as u32).to_be_bytes())?; + response.extend_from_slice(&(block_size as u32).to_be_bytes())?; + + self.bulk_in.write(&response).await.map_err(|_| ()) + } + ScsiCommand::ReadCapacity16 { alloc_len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + + let block_size = SdCard::BLOCK_SIZE as u64; + let total_blocks = sdcard.size() / block_size; + + let last_lba = total_blocks.checked_sub(1).unwrap_or(0); + + response.extend_from_slice(&last_lba.to_be_bytes())?; // 8 bytes last LBA + response.extend_from_slice(&(block_size as u32).to_be_bytes())?; // 4 bytes block length + response.extend_from_slice(&[0u8; 20])?; // 20 reserved bytes zeroed + + let len = alloc_len.min(response.len() as u32) as usize; + self.bulk_in.write(&response[..len]).await.map_err(|_| ()) + } + ScsiCommand::Read { lba, len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + + for i in 0..len { + let block_idx = BlockIdx(lba as u32 + i as u32); + sdcard.read_blocks(&mut block, block_idx)?; + for chunk in block[0].contents.chunks(BULK_ENDPOINT_PACKET_SIZE.into()) { + self.bulk_in.write(chunk).await.map_err(|_| ())?; + } + } + Ok(()) + } + ScsiCommand::Write { lba, len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + + for i in 0..len { + let block_idx = BlockIdx(lba as u32 + i as u32); + for chunk in block[0] + .contents + .chunks_mut(BULK_ENDPOINT_PACKET_SIZE.into()) + { + self.bulk_out.read(chunk).await.map_err(|_| ())?; + } + sdcard.write_blocks(&mut block, block_idx)?; + } + Ok(()) + } + ScsiCommand::ReadFormatCapacities { alloc_len } => { + let guard = SDCARD.get().lock().await; + let sdcard = guard.as_ref().unwrap(); + + let block_size = SdCard::BLOCK_SIZE as u32; + let num_blocks = (sdcard.size() / block_size as u64) as u32; + + let mut response = [0u8; 12]; + + // Capacity List Length (8 bytes follows) + response[3] = 8; + + // Descriptor + response[4..8].copy_from_slice(&num_blocks.to_be_bytes()); + response[8] = 0x03; // formatted media + response[9..12].copy_from_slice(&block_size.to_be_bytes()[1..4]); // only 3 bytes + + let response_len = alloc_len.min(response.len() as u16) as usize; + self.bulk_in + .write(&response[..response_len]) + .await + .map_err(|_| ()) + } + ScsiCommand::PreventAllowMediumRemoval { prevent: _prevent } => Ok(()), + ScsiCommand::StartStopUnit { start, load_eject } => Ok(()), + } + } + + pub async fn send_csw_success(&mut self, tag: u32) { + self.send_csw(tag, 0x00, 0).await; + } + + pub async fn send_csw_fail(&mut self, tag: u32) { + defmt::error!("Command Failed"); + self.send_csw(tag, 0x01, 0).await; // 0x01 = Command Failed + } + + pub async fn send_csw(&mut self, tag: u32, status: u8, residue: u32) { + let mut csw = [0u8; 13]; + csw[0..4].copy_from_slice(&0x53425355u32.to_le_bytes()); // Signature "USBS" + csw[4..8].copy_from_slice(&tag.to_le_bytes()); + csw[8..12].copy_from_slice(&residue.to_le_bytes()); + csw[12] = status; + let _ = self.bulk_in.write(&csw).await; + } +} + +#[repr(C, packed)] +struct CommandBlockWrapper { + dCBWSignature: u32, + dCBWTag: u32, + dCBWDataTransferLength: u32, + bmCBWFlags: u8, + bCBWLUN: u8, + bCBWCBLength: u8, + CBWCB: [u8; 16], +} + +impl CommandBlockWrapper { + fn parse(buf: &[u8]) -> Option { + if buf.len() < 31 { + return None; + } + let dCBWSignature = u32::from_le_bytes(buf[0..4].try_into().ok()?); + if dCBWSignature != 0x43425355 { + return None; // invalid signature + } + Some(Self { + dCBWSignature, + dCBWTag: u32::from_le_bytes(buf[4..8].try_into().ok()?), + dCBWDataTransferLength: u32::from_le_bytes(buf[8..12].try_into().ok()?), + bmCBWFlags: buf[12], + bCBWLUN: buf[13], + bCBWCBLength: buf[14], + CBWCB: buf[15..31].try_into().ok()?, + }) + } +} diff --git a/kernel/src/scsi/scsi_types.rs b/kernel/src/scsi/scsi_types.rs new file mode 100644 index 0000000..dbd27dd --- /dev/null +++ b/kernel/src/scsi/scsi_types.rs @@ -0,0 +1,164 @@ +use num_enum::TryFromPrimitive; + +#[derive(Debug, Clone, Copy)] +pub enum ScsiError { + NotReady, +} + +/// THE CODE BELOW ORIGINATES FROM: https://github.com/apohrebniak/usbd-storage/blob/master/usbd-storage/src/subclass/scsi.rs + +/// SCSI device subclass code +pub const SUBCLASS_SCSI: u8 = 0x06; // SCSI Transparent command set + +/* SCSI codes */ + +/* SPC */ +const TEST_UNIT_READY: u8 = 0x00; +const REQUEST_SENSE: u8 = 0x03; +const INQUIRY: u8 = 0x12; +const MODE_SENSE_6: u8 = 0x1A; +const MODE_SENSE_10: u8 = 0x5A; + +/* SBC */ +const READ_10: u8 = 0x28; +const READ_16: u8 = 0x88; +const READ_CAPACITY_10: u8 = 0x25; +const READ_CAPACITY_16: u8 = 0x9E; +const WRITE_10: u8 = 0x2A; + +/* MMC */ +const READ_FORMAT_CAPACITIES: u8 = 0x23; + +const PREVENT_ALLOW_MEDIUM_REMOVAL: u8 = 0x1E; +const START_STOP_UNIT: u8 = 0x1B; + +/// SCSI command +/// +/// Refer to specifications (SPC,SAM,SBC,MMC,etc.) +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ScsiCommand { + Unknown, + + /* SPC */ + Inquiry { + evpd: bool, + page_code: u8, + alloc_len: u16, + }, + TestUnitReady, + RequestSense { + desc: bool, + alloc_len: u8, + }, + ModeSense6 { + dbd: bool, + page_control: PageControl, + page_code: u8, + subpage_code: u8, + alloc_len: u8, + }, + ModeSense10 { + dbd: bool, + page_control: PageControl, + page_code: u8, + subpage_code: u8, + alloc_len: u16, + }, + + /* SBC */ + ReadCapacity10, + ReadCapacity16 { + alloc_len: u32, + }, + Read { + lba: u64, + len: u64, + }, + Write { + lba: u64, + len: u64, + }, + + /* MMC */ + ReadFormatCapacities { + alloc_len: u16, + }, + + PreventAllowMediumRemoval { + prevent: bool, + }, + + StartStopUnit { + start: bool, + load_eject: bool, + }, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, TryFromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PageControl { + CurrentValues = 0b00, + ChangeableValues = 0b01, + DefaultValues = 0b10, + SavedValues = 0b11, +} + +#[allow(dead_code)] +pub fn parse_cb(cb: &[u8]) -> ScsiCommand { + match cb[0] { + TEST_UNIT_READY => ScsiCommand::TestUnitReady, + INQUIRY => ScsiCommand::Inquiry { + evpd: (cb[1] & 0b00000001) != 0, + page_code: cb[2], + alloc_len: u16::from_be_bytes([cb[3], cb[4]]), + }, + REQUEST_SENSE => ScsiCommand::RequestSense { + desc: (cb[1] & 0b00000001) != 0, + alloc_len: cb[4], + }, + READ_CAPACITY_10 => ScsiCommand::ReadCapacity10, + READ_CAPACITY_16 => ScsiCommand::ReadCapacity16 { + alloc_len: u32::from_be_bytes([cb[10], cb[11], cb[12], cb[13]]), + }, + READ_10 => ScsiCommand::Read { + lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64, + len: u16::from_be_bytes([cb[7], cb[8]]) as u64, + }, + READ_16 => ScsiCommand::Read { + lba: u64::from_be_bytes((&cb[2..10]).try_into().unwrap()), + len: u32::from_be_bytes((&cb[10..14]).try_into().unwrap()) as u64, + }, + WRITE_10 => ScsiCommand::Write { + lba: u32::from_be_bytes([cb[2], cb[3], cb[4], cb[5]]) as u64, + len: u16::from_be_bytes([cb[7], cb[8]]) as u64, + }, + MODE_SENSE_6 => ScsiCommand::ModeSense6 { + dbd: (cb[1] & 0b00001000) != 0, + page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(), + page_code: cb[2] & 0b00111111, + subpage_code: cb[3], + alloc_len: cb[4], + }, + MODE_SENSE_10 => ScsiCommand::ModeSense10 { + dbd: (cb[1] & 0b00001000) != 0, + page_control: PageControl::try_from_primitive(cb[2] >> 6).unwrap(), + page_code: cb[2] & 0b00111111, + subpage_code: cb[3], + alloc_len: u16::from_be_bytes([cb[7], cb[8]]), + }, + READ_FORMAT_CAPACITIES => ScsiCommand::ReadFormatCapacities { + alloc_len: u16::from_be_bytes([cb[7], cb[8]]), + }, + PREVENT_ALLOW_MEDIUM_REMOVAL => ScsiCommand::PreventAllowMediumRemoval { + prevent: (cb[1] & 0b00000001) != 0, + }, + START_STOP_UNIT => ScsiCommand::StartStopUnit { + start: (cb[4] & 0b00000001) != 0, + load_eject: (cb[4] & 0b00000010) != 0, + }, + _ => ScsiCommand::Unknown, + } +} diff --git a/kernel/src/storage.rs b/kernel/src/storage.rs new file mode 100644 index 0000000..ee1fa1e --- /dev/null +++ b/kernel/src/storage.rs @@ -0,0 +1,162 @@ +use alloc::{string::String, vec::Vec}; +use core::str::FromStr; +use embassy_rp::gpio::{Input, Output}; +use embassy_rp::peripherals::SPI0; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::lazy_lock::LazyLock; +use embassy_sync::mutex::Mutex; +use embassy_time::Delay; +use embedded_hal_bus::spi::ExclusiveDevice; +use embedded_sdmmc::{ + Block, BlockCount, BlockDevice, BlockIdx, Directory, SdCard as SdmmcSdCard, TimeSource, + Timestamp, Volume, VolumeIdx, VolumeManager, sdcard::Error, +}; +use embedded_sdmmc::{File as SdFile, LfnBuffer, Mode, ShortFileName}; + +pub const MAX_DIRS: usize = 4; +pub const MAX_FILES: usize = 5; +pub const MAX_VOLUMES: usize = 1; + +type Device = ExclusiveDevice, Output<'static>, embassy_time::Delay>; +type SD = SdmmcSdCard; +type VolMgr = VolumeManager; +type Vol<'a> = Volume<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; +type Dir<'a> = Directory<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; +pub type File<'a> = SdFile<'a, SD, DummyTimeSource, MAX_DIRS, MAX_FILES, MAX_VOLUMES>; + +pub static SDCARD: LazyLock>> = + LazyLock::new(|| Mutex::new(None)); + +pub struct DummyTimeSource {} +impl TimeSource for DummyTimeSource { + fn get_timestamp(&self) -> Timestamp { + Timestamp::from_calendar(2022, 1, 1, 0, 0, 0).unwrap() + } +} + +#[derive(Clone, PartialEq)] +pub struct FileName { + pub long_name: String, + pub short_name: ShortFileName, +} + +pub struct SdCard { + det: Input<'static>, + volume_mgr: VolMgr, +} + +impl SdCard { + pub const BLOCK_SIZE: u16 = 512; + + pub fn new(sdcard: SD, det: Input<'static>) -> Self { + let volume_mgr = VolumeManager::<_, _, MAX_DIRS, MAX_FILES, MAX_VOLUMES>::new_with_limits( + sdcard, + DummyTimeSource {}, + 5000, + ); + Self { + det: det, + volume_mgr, + } + } + + /// Returns true if an SD card is inserted. + /// The DET pin is active-low via mechanical switch in the socket. + pub fn is_attached(&self) -> bool { + self.det.is_low() + } + + pub fn size(&self) -> u64 { + let mut result = 0; + + self.volume_mgr.device(|sd| { + result = sd.num_bytes().unwrap_or(0); + DummyTimeSource {} + }); + + result + } + + pub fn num_blocks(&self) -> u32 { + let mut result = 0; + + self.volume_mgr.device(|sd| { + result = sd.num_blocks().unwrap_or(BlockCount(0)).0; + DummyTimeSource {} + }); + + result + } + + pub fn read_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { + let mut res: Result<(), Error> = Ok(()); + self.volume_mgr.device(|sd| { + res = sd.read(blocks, start_block_idx); + DummyTimeSource {} + }); + res.map_err(|_| ()) + } + + pub fn write_blocks(&self, blocks: &mut [Block], start_block_idx: BlockIdx) -> Result<(), ()> { + let mut res: Result<(), Error> = Ok(()); + self.volume_mgr.device(|sd| { + let res = sd.write(blocks, start_block_idx); + DummyTimeSource {} + }); + res.map_err(|_| ()) + } + + pub fn access_root_dir(&mut self, mut access: impl FnMut(Dir)) { + let volume0 = self.volume_mgr.open_volume(VolumeIdx(0)).unwrap(); + let root_dir = volume0.open_root_dir().unwrap(); + + access(root_dir); + } + + pub async fn read_file( + &mut self, + name: &ShortFileName, + mut access: impl FnMut(File), + ) -> Result<(), ()> { + let mut res = Err(()); + self.access_root_dir(|root_dir| { + if let Ok(file) = root_dir.open_file_in_dir(name, Mode::ReadOnly) { + res = Ok(()); + access(file); + } + }); + + res + } + + /// Returns a Vec of file names (long format) that match the given extension (e.g., "BIN") + pub fn list_files_by_extension(&mut self, ext: &str) -> Result, ()> { + let mut result = Vec::new(); + + // Only proceed if card is inserted + if !self.is_attached() { + return Ok(result); + } + + let mut lfn_storage = [0; 50]; + let mut lfn_buffer = LfnBuffer::new(&mut lfn_storage); + + self.access_root_dir(|dir| { + dir.iterate_dir_lfn(&mut lfn_buffer, |entry, name| { + if let Some(name) = name { + let name = String::from_str(name).unwrap(); + if name.contains(ext) { + result.push(FileName { + long_name: name, + short_name: entry.name.clone(), + }); + } + } + }) + .unwrap() + }); + + Ok(result) + } +} diff --git a/kernel/src/ui.rs b/kernel/src/ui.rs new file mode 100644 index 0000000..03122c1 --- /dev/null +++ b/kernel/src/ui.rs @@ -0,0 +1,152 @@ +use crate::{ + BINARY_CH, TASK_STATE, TaskState, + display::{FRAMEBUFFER, SCREEN_HEIGHT, SCREEN_WIDTH}, + elf::load_binary, + format, + peripherals::keyboard, + storage::FileName, +}; +use alloc::{string::String, vec::Vec}; +use core::{fmt::Debug, str::FromStr, sync::atomic::Ordering}; +use embassy_sync::{ + blocking_mutex::raw::{CriticalSectionRawMutex, ThreadModeRawMutex}, + mutex::Mutex, +}; +use embedded_graphics::{ + Drawable, + mono_font::{MonoTextStyle, ascii::FONT_9X15}, + pixelcolor::Rgb565, + prelude::{Dimensions, Point, RgbColor, Size}, + primitives::Rectangle, + text::Text, +}; +use embedded_layout::{ + align::{horizontal, vertical}, + layout::linear::LinearLayout, + object_chain::Chain, + prelude::*, +}; +use embedded_text::TextBox; +use shared::keyboard::{KeyCode, KeyState}; + +pub static SELECTIONS: Mutex = + Mutex::new(SelectionList::new()); + +pub async fn ui_handler() { + loop { + if let TaskState::Ui = *TASK_STATE.lock().await { + if let Some(event) = keyboard::read_keyboard_fifo().await { + if let KeyState::Pressed = event.state { + match event.key { + KeyCode::JoyUp => { + let mut selections = SELECTIONS.lock().await; + selections.up(); + } + KeyCode::JoyDown => { + let mut selections = SELECTIONS.lock().await; + selections.down(); + } + KeyCode::Enter | KeyCode::JoyRight => { + let selections = SELECTIONS.lock().await; + let selection = selections.selections + [selections.current_selection as usize - 1] + .clone(); + + let entry = + unsafe { load_binary(&selection.short_name).await.unwrap() }; + BINARY_CH.send(entry).await; + } + _ => (), + } + } + } + + draw_selection().await; + } else { + embassy_time::Timer::after_millis(50).await; + } + } +} + +async fn draw_selection() { + let file_names: Vec = { + let guard = SELECTIONS.lock().await; + guard.selections.clone() + }; + + let text_style = MonoTextStyle::new(&FONT_9X15, Rgb565::WHITE); + let display_area = unsafe { FRAMEBUFFER.bounding_box() }; + + const NO_BINS: &str = "No Programs found on SD Card. Ensure programs end with '.bin', and are located in the root directory"; + let no_bins = String::from_str(NO_BINS).unwrap(); + + if file_names.is_empty() { + TextBox::new( + &no_bins, + Rectangle::new( + Point::new(25, 25), + Size::new(display_area.size.width - 50, display_area.size.width - 50), + ), + text_style, + ) + .draw(unsafe { &mut FRAMEBUFFER }) + .unwrap(); + } else { + let mut file_names = file_names.iter(); + let Some(first) = file_names.next() else { + Text::new(NO_BINS, Point::zero(), text_style) + .draw(unsafe { &mut FRAMEBUFFER }) + .unwrap(); + + return; + }; + + let chain = Chain::new(Text::new(&first.long_name, Point::zero(), text_style)); + + // for _ in 0..file_names.len() { + // let chain = chain.append(Text::new( + // file_names.next().unwrap(), + // Point::zero(), + // text_style, + // )); + // } + + LinearLayout::vertical(chain) + .with_alignment(horizontal::Center) + .arrange() + .align_to(&display_area, horizontal::Center, vertical::Center) + .draw(unsafe { &mut FRAMEBUFFER }) + .unwrap(); + } +} + +#[derive(Clone)] +pub struct SelectionList { + current_selection: u16, + pub selections: Vec, +} + +impl SelectionList { + pub const fn new() -> Self { + Self { + selections: Vec::new(), + current_selection: 0, + } + } + + pub fn reset(&mut self) { + self.current_selection = 1 + } + + fn down(&mut self) { + if self.current_selection + 1 < self.selections.len() as u16 { + self.current_selection += 1 + } + } + + fn up(&mut self) { + if self.current_selection > self.selections.len() as u16 { + self.current_selection -= 1 + } + } +} diff --git a/kernel/src/usb.rs b/kernel/src/usb.rs new file mode 100644 index 0000000..49c0665 --- /dev/null +++ b/kernel/src/usb.rs @@ -0,0 +1,43 @@ +use crate::{TASK_STATE, TASK_STATE_CHANGED, TaskState, scsi::MassStorageClass}; +use embassy_futures::{ + join::join, + select::{select, select3}, +}; +use embassy_rp::{peripherals::USB, usb::Driver}; +use embassy_usb::{Builder, Config}; + +pub async fn usb_handler(driver: Driver<'static, USB>) { + let mut config = Config::new(0xc0de, 0xbabe); + config.manufacturer = Some("LegitCamper"); + config.product = Some("PicoCalc"); + config.serial_number = Some("01001100"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 64]; + let mut control_buf = [0; 64]; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + let mut scsi = MassStorageClass::new(&mut builder); + let mut usb = builder.build(); + + loop { + defmt::info!("in: {}", *TASK_STATE.lock().await as u32); + if *TASK_STATE.lock().await == TaskState::Ui { + defmt::info!("running scsi and usb"); + select(join(usb.run(), scsi.poll()), TASK_STATE_CHANGED.wait()).await; + } else { + defmt::info!("not in ui state"); + TASK_STATE_CHANGED.wait().await; + } + } +} diff --git a/kernel/src/utils.rs b/kernel/src/utils.rs new file mode 100644 index 0000000..62683c6 --- /dev/null +++ b/kernel/src/utils.rs @@ -0,0 +1,11 @@ +#[macro_export] +macro_rules! format { + ($len:literal, $($arg:tt)*) => {{ + use heapless::String; + use core::fmt::Write; + + let mut s: String<$len> = String::new(); + let _ = write!(&mut s, $($arg)*); + s + }} +} diff --git a/shared/Cargo.toml b/shared/Cargo.toml new file mode 100644 index 0000000..4ceba1e --- /dev/null +++ b/shared/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "shared" +version = "0.1.0" +edition = "2024" + +[features] +defmt = ["dep:defmt"] + +[dependencies] +bitflags = "2.9.1" +defmt = { version = "1.0.1", optional = true } diff --git a/shared/src/lib.rs b/shared/src/lib.rs new file mode 100644 index 0000000..3823869 --- /dev/null +++ b/shared/src/lib.rs @@ -0,0 +1,142 @@ +#![no_std] + +pub mod keyboard { + bitflags::bitflags! { + #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] + pub struct Modifiers: u8 { + const NONE = 0; + const CTRL = 1; + const ALT = 2; + const LSHIFT = 4; + const RSHIFT = 8; + const SYM = 16; + } + } + + #[derive(Debug)] + pub struct KeyEvent { + pub key: KeyCode, + pub state: KeyState, + pub mods: Modifiers, + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum KeyState { + Idle = 0, + Pressed = 1, + Hold = 2, + Released = 3, + } + + impl From for KeyState { + fn from(value: u8) -> Self { + match value { + 1 => KeyState::Pressed, + 2 => KeyState::Hold, + 3 => KeyState::Released, + 0 | _ => KeyState::Idle, + } + } + } + + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[repr(C)] + #[repr(u8)] + pub enum KeyCode { + JoyUp = 0x01, + JoyDown = 0x02, + JoyLeft = 0x03, + JoyRight = 0x04, + JoyCenter = 0x05, + BtnLeft1 = 0x06, + BtnRight1 = 0x07, + BtnLeft2 = 0x11, + BtnRight2 = 0x12, + Backspace = 0x08, + Tab = 0x09, + Enter = 0x0A, + ModAlt = 0xA1, + ModShiftLeft = 0xA2, + ModShiftRight = 0xA3, + ModSym = 0xA4, + ModCtrl = 0xA5, + Esc = 0xB1, + Left = 0xB4, + Up = 0xB5, + Down = 0xB6, + Right = 0xB7, + Break = 0xD0, + Insert = 0xD1, + Home = 0xD2, + Del = 0xD4, + End = 0xD5, + PageUp = 0xD6, + PageDown = 0xD7, + CapsLock = 0xC1, + F1 = 0x81, + F2 = 0x82, + F3 = 0x83, + F4 = 0x84, + F5 = 0x85, + F6 = 0x86, + F7 = 0x87, + F8 = 0x88, + F9 = 0x89, + F10 = 0x90, + Char(char), + Unknown(u8), + } + + impl From for KeyCode { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::JoyUp, + 0x02 => Self::JoyDown, + 0x03 => Self::JoyLeft, + 0x04 => Self::JoyRight, + 0x05 => Self::JoyCenter, + 0x06 => Self::BtnLeft1, + 0x07 => Self::BtnRight1, + 0x08 => Self::Backspace, + 0x09 => Self::Tab, + 0x0A => Self::Enter, + 0x11 => Self::BtnLeft2, + 0x12 => Self::BtnRight2, + 0xA1 => Self::ModAlt, + 0xA2 => Self::ModShiftLeft, + 0xA3 => Self::ModShiftRight, + 0xA4 => Self::ModSym, + 0xA5 => Self::ModCtrl, + 0xB1 => Self::Esc, + 0xB4 => Self::Left, + 0xB5 => Self::Up, + 0xB6 => Self::Down, + 0xB7 => Self::Right, + 0xC1 => Self::CapsLock, + 0xD0 => Self::Break, + 0xD1 => Self::Insert, + 0xD2 => Self::Home, + 0xD4 => Self::Del, + 0xD5 => Self::End, + 0xD6 => Self::PageUp, + 0xD7 => Self::PageDown, + 0x81 => Self::F1, + 0x82 => Self::F2, + 0x83 => Self::F3, + 0x84 => Self::F4, + 0x85 => Self::F5, + 0x86 => Self::F6, + 0x87 => Self::F7, + 0x88 => Self::F8, + 0x89 => Self::F9, + 0x90 => Self::F10, + _ => match char::from_u32(value as u32) { + Some(c) => Self::Char(c), + None => Self::Unknown(value), + }, + } + } + } +} diff --git a/src/display.rs b/src/display.rs deleted file mode 100644 index 7cf9ab4..0000000 --- a/src/display.rs +++ /dev/null @@ -1,87 +0,0 @@ -use core::sync::atomic::Ordering; - -use defmt::info; -use embassy_rp::{ - gpio::{Level, Output}, - peripherals::{PIN_13, PIN_14, PIN_15, SPI1}, - spi::{Async, Spi}, -}; -use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, signal::Signal}; -use embassy_time::{Delay, Instant, Timer}; -use embedded_graphics::{ - Drawable, - draw_target::DrawTarget, - mono_font::{MonoTextStyle, ascii::FONT_10X20}, - pixelcolor::Rgb565, - prelude::{Dimensions, Point, RgbColor, Size}, - primitives::Rectangle, - text::{Alignment, Text}, -}; -use embedded_hal_bus::spi::ExclusiveDevice; -use portable_atomic::AtomicBool; -use st7365p_lcd::{FrameBuffer, ST7365P}; - -use crate::LAST_TEXT_RECT; - -const SCREEN_WIDTH: usize = 320; -const SCREEN_HEIGHT: usize = 320; - -pub static DISPLAY_SIGNAL: Signal = Signal::new(); - -pub async fn display_handler( - spi: Spi<'static, SPI1, Async>, - cs: PIN_13, - data: PIN_14, - reset: PIN_15, -) { - let spi_device = ExclusiveDevice::new(spi, Output::new(cs, Level::Low), Delay).unwrap(); - let mut display = ST7365P::new( - spi_device, - Output::new(data, Level::Low), - Some(Output::new(reset, Level::High)), - false, - true, - Delay, - ); - let mut framebuffer: FrameBuffer< - SCREEN_WIDTH, - SCREEN_HEIGHT, - { SCREEN_WIDTH * SCREEN_HEIGHT }, - > = FrameBuffer::new(); - display.init().await.unwrap(); - display.set_custom_orientation(0x40).await.unwrap(); - framebuffer.draw(&mut display).await.unwrap(); - display.set_on().await.unwrap(); - - DISPLAY_SIGNAL.signal(()); - - loop { - DISPLAY_SIGNAL.wait().await; - - let text_string = crate::STRING.lock().await.clone(); - - let text = Text::with_alignment( - &text_string, - Point::new(160, 160), - MonoTextStyle::new(&FONT_10X20, Rgb565::RED), - Alignment::Center, - ); - - { - let rect = LAST_TEXT_RECT.lock().await; - if let Some(rect) = *rect.borrow() { - framebuffer.fill_solid(&rect, Rgb565::BLACK).unwrap(); - } - *rect.borrow_mut() = Some(text.bounding_box()); - } - - text.draw(&mut framebuffer).unwrap(); - - let start = Instant::now(); - framebuffer - .partial_draw_batched(&mut display) - .await - .unwrap(); - info!("Elapsed {}ms", start.elapsed().as_millis()); - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e754b93..0000000 --- a/src/main.rs +++ /dev/null @@ -1,82 +0,0 @@ -#![feature(impl_trait_in_assoc_type)] -#![feature(ascii_char)] -#![no_std] -#![no_main] - -use crate::{ - display::DISPLAY_SIGNAL, - peripherals::keyboard::{KeyCode, KeyState, read_keyboard_fifo}, -}; - -use {defmt_rtt as _, panic_probe as _}; - -use core::cell::RefCell; -use embassy_executor::Spawner; -use embassy_futures::join::join; -use embassy_rp::peripherals::I2C1; -use embassy_rp::{i2c, i2c::I2c, spi}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::mutex::Mutex; -use embassy_time::Timer; -use embedded_graphics::primitives::Rectangle; -use heapless::String; - -mod peripherals; -use peripherals::conf_peripherals; -mod display; -use display::display_handler; - -embassy_rp::bind_interrupts!(struct Irqs { - I2C1_IRQ => i2c::InterruptHandler; -}); - -static STRING: Mutex> = Mutex::new(String::new()); -static LAST_TEXT_RECT: Mutex>> = - Mutex::new(RefCell::new(None)); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_rp::init(Default::default()); - - STRING.lock().await.push_str("Press Del").unwrap(); - - // configure keyboard event handler - let mut config = i2c::Config::default(); - config.frequency = 400_000; - let i2c1 = I2c::new_async(p.I2C1, p.PIN_7, p.PIN_6, Irqs, config); - conf_peripherals(i2c1).await; - - let mut config = spi::Config::default(); - config.frequency = 16_000_000; - let spi1 = spi::Spi::new( - p.SPI1, p.PIN_10, p.PIN_11, p.PIN_12, p.DMA_CH0, p.DMA_CH1, config, - ); - - join( - async { - loop { - Timer::after_millis(20).await; - if let Some(key) = read_keyboard_fifo().await - && key.state == KeyState::Pressed - { - let mut string = STRING.lock().await; - match key.key { - KeyCode::Backspace => { - string.pop().unwrap(); - } - KeyCode::Del => { - string.clear(); - } - KeyCode::Char(c) => { - string.push(c).unwrap(); - } - _ => (), - } - DISPLAY_SIGNAL.signal(()); - } - } - }, - display_handler(spi1, p.PIN_13, p.PIN_14, p.PIN_15), - ) - .await; -} diff --git a/src/peripherals/keyboard.rs b/src/peripherals/keyboard.rs deleted file mode 100644 index 208a335..0000000 --- a/src/peripherals/keyboard.rs +++ /dev/null @@ -1,197 +0,0 @@ - -use crate::peripherals::PERIPHERAL_BUS; - -const REG_ID_KEY: u8 = 0x04; -const REG_ID_FIF: u8 = 0x09; - -const KEY_CAPSLOCK: u8 = 1 << 5; -const KEY_NUMLOCK: u8 = 1 << 6; -const KEY_COUNT_MASK: u8 = 0x1F; // 0x1F == 31 - -pub async fn read_keyboard_fifo() -> Option { - let mut i2c = PERIPHERAL_BUS.get().lock().await; - let i2c = i2c.as_mut().unwrap(); - - let mut key_status = [0_u8; 1]; - - if i2c - .write_read_async(super::MCU_ADDR, [REG_ID_KEY], &mut key_status) - .await - .is_ok() - { - let _caps = key_status[0] & KEY_CAPSLOCK == KEY_CAPSLOCK; - let _num = key_status[0] & KEY_NUMLOCK == KEY_NUMLOCK; - let fifo_count = key_status[0] & KEY_COUNT_MASK; - - if fifo_count >= 1 { - let mut event = [0_u8; 2]; - - if i2c - .write_read_async(super::MCU_ADDR, [REG_ID_FIF], &mut event) - .await - .is_ok() - { - return Some(KeyEvent { - state: KeyState::from(event[0]), - key: KeyCode::from(event[1]), - mods: Modifiers::NONE, - }); - } - } - } - None -} - -const REG_ID_DEB: u8 = 0x06; -const REG_ID_FRQ: u8 = 0x07; - -pub async fn configure_keyboard(debounce: u8, poll_freq: u8) { - let mut i2c = PERIPHERAL_BUS.get().lock().await; - let i2c = i2c.as_mut().unwrap(); - - let _ = i2c - .write_read_async(super::MCU_ADDR, [REG_ID_DEB], &mut [debounce]) - .await; - - let _ = i2c - .write_read_async(super::MCU_ADDR, [REG_ID_FRQ], &mut [poll_freq]) - .await; -} - -bitflags::bitflags! { - #[derive(Default, Debug, PartialEq, Eq, Clone, Copy)] - pub struct Modifiers: u8 { - const NONE = 0; - const CTRL = 1; - const ALT = 2; - const LSHIFT = 4; - const RSHIFT = 8; - const SYM = 16; - } -} - -#[derive(Debug)] -pub struct KeyEvent { - pub key: KeyCode, - pub state: KeyState, - pub mods: Modifiers, -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum KeyState { - Idle = 0, - Pressed = 1, - Hold = 2, - Released = 3, -} - -impl From for KeyState { - fn from(value: u8) -> Self { - match value { - 1 => KeyState::Pressed, - 2 => KeyState::Hold, - 3 => KeyState::Released, - 0 | _ => KeyState::Idle, - } - } -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u8)] -pub enum KeyCode { - JoyUp = 0x01, - JoyDown = 0x02, - JoyLeft = 0x03, - JoyRight = 0x04, - JoyCenter = 0x05, - BtnLeft1 = 0x06, - BtnRight1 = 0x07, - BtnLeft2 = 0x11, - BtnRight2 = 0x12, - Backspace = 0x08, - Tab = 0x09, - Enter = 0x0A, - ModAlt = 0xA1, - ModShiftLeft = 0xA2, - ModShiftRight = 0xA3, - ModSym = 0xA4, - ModCtrl = 0xA5, - Esc = 0xB1, - Left = 0xB4, - Up = 0xB5, - Down = 0xB6, - Right = 0xB7, - Break = 0xD0, - Insert = 0xD1, - Home = 0xD2, - Del = 0xD4, - End = 0xD5, - PageUp = 0xD6, - PageDown = 0xD7, - CapsLock = 0xC1, - F1 = 0x81, - F2 = 0x82, - F3 = 0x83, - F4 = 0x84, - F5 = 0x85, - F6 = 0x86, - F7 = 0x87, - F8 = 0x88, - F9 = 0x89, - F10 = 0x90, - Char(char), - Unknown(u8), -} - -impl From for KeyCode { - fn from(value: u8) -> Self { - match value { - 0x01 => Self::JoyUp, - 0x02 => Self::JoyDown, - 0x03 => Self::JoyLeft, - 0x04 => Self::JoyRight, - 0x05 => Self::JoyCenter, - 0x06 => Self::BtnLeft1, - 0x07 => Self::BtnRight1, - 0x08 => Self::Backspace, - 0x09 => Self::Tab, - 0x0A => Self::Enter, - 0x11 => Self::BtnLeft2, - 0x12 => Self::BtnRight2, - 0xA1 => Self::ModAlt, - 0xA2 => Self::ModShiftLeft, - 0xA3 => Self::ModShiftRight, - 0xA4 => Self::ModSym, - 0xA5 => Self::ModCtrl, - 0xB1 => Self::Esc, - 0xB4 => Self::Left, - 0xB5 => Self::Up, - 0xB6 => Self::Down, - 0xB7 => Self::Right, - 0xC1 => Self::CapsLock, - 0xD0 => Self::Break, - 0xD1 => Self::Insert, - 0xD2 => Self::Home, - 0xD4 => Self::Del, - 0xD5 => Self::End, - 0xD6 => Self::PageUp, - 0xD7 => Self::PageDown, - 0x81 => Self::F1, - 0x82 => Self::F2, - 0x83 => Self::F3, - 0x84 => Self::F4, - 0x85 => Self::F5, - 0x86 => Self::F6, - 0x87 => Self::F7, - 0x88 => Self::F8, - 0x89 => Self::F9, - 0x90 => Self::F10, - _ => match char::from_u32(value as u32) { - Some(c) => Self::Char(c), - None => Self::Unknown(value), - }, - } - } -} diff --git a/user-apps/calculator/Cargo.toml b/user-apps/calculator/Cargo.toml new file mode 100644 index 0000000..bcb1288 --- /dev/null +++ b/user-apps/calculator/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "calculator" +version = "0.1.0" +edition = "2024" + +[dependencies] +abi = { path = "../../abi" } +embedded-graphics = "0.8.1" diff --git a/user-apps/calculator/build.rs b/user-apps/calculator/build.rs new file mode 100644 index 0000000..332a55b --- /dev/null +++ b/user-apps/calculator/build.rs @@ -0,0 +1,28 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("../memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=memory.x"); + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); +} diff --git a/user-apps/calculator/src/main.rs b/user-apps/calculator/src/main.rs new file mode 100644 index 0000000..6808c7e --- /dev/null +++ b/user-apps/calculator/src/main.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] + +extern crate alloc; +use abi::{KeyCode, display::Display, embassy_time, get_key, print, sleep}; +use alloc::{boxed::Box, string::String, vec}; +use core::{panic::PanicInfo, pin::Pin}; +use embedded_graphics::{ + Drawable, + geometry::{Dimensions, Point}, + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::Rgb565, + prelude::{Primitive, RgbColor, Size}, + primitives::{PrimitiveStyle, Rectangle}, + text::{Alignment, Text}, +}; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + loop {} +} + +pub async fn main() { + print("Starting Async Calculator app"); + let mut display = Display; + + let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::RED); + + let mut text = vec!['T', 'y', 'p', 'e']; + let mut dirty = true; + let mut last_bounds: Option = None; + + loop { + if dirty { + if let Some(bounds) = last_bounds { + Rectangle::new(bounds.top_left, bounds.size) + .into_styled(PrimitiveStyle::with_fill(Rgb565::BLACK)) + .draw(&mut display) + .unwrap(); + } + + let text = text.iter().cloned().collect::(); + let aligned_text = Text::with_alignment( + &text, + display.bounding_box().center(), + character_style, + Alignment::Center, + ); + last_bounds = Some(aligned_text.bounding_box()); + + aligned_text.draw(&mut display).unwrap(); + dirty = false; + } + + if let Some(event) = get_key() { + dirty = true; + match event.key { + KeyCode::Char(ch) => { + text.push(ch); + } + KeyCode::Del => { + text.clear(); + } + KeyCode::Backspace => { + text.pop(); + } + KeyCode::Esc => return, + _ => (), + } + } + } +} + +#[unsafe(no_mangle)] +pub extern "Rust" fn _start() -> Pin>> { + Box::pin(async { main().await }) +} diff --git a/user-apps/memory.x b/user-apps/memory.x new file mode 100644 index 0000000..19fa9a5 --- /dev/null +++ b/user-apps/memory.x @@ -0,0 +1,33 @@ +MEMORY +{ + /* Must match the USERAPP region in the kernel linker script */ + RAM : ORIGIN = 0x20010000, LENGTH = 192K +} + +SECTIONS +{ + /* Reserve first 1KB for patchable symbols */ + .user_reloc (NOLOAD) : ALIGN(4) + { + __user_reloc_start = .; + KEEP(*(.user_reloc*)); + __user_reloc_end = .; + } > RAM + + .text : ALIGN(4) + { + *(.text .text.*); + *(.rodata .rodata.*); + } > RAM + + .data : ALIGN(4) + { + *(.data .data.*); + } > RAM + + .bss : ALIGN(4) + { + *(.bss .bss.*); + *(COMMON); + } > RAM +}