diff options
| -rw-r--r-- | .cargo/imxrt1170evk.toml | 10 | ||||
| -rw-r--r-- | .cargo/qemu.toml | 10 | ||||
| -rw-r--r-- | .cargo/teensy4.toml | 10 | ||||
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 543 | ||||
| -rw-r--r-- | Cargo.toml | 36 | ||||
| -rw-r--r-- | LICENSE | 373 | ||||
| -rw-r--r-- | README.md | 64 | ||||
| -rw-r--r-- | crates/cortex-m/Cargo.toml | 7 | ||||
| -rw-r--r-- | crates/cortex-m/build.rs | 14 | ||||
| -rw-r--r-- | crates/cortex-m/src/lib.rs | 1 | ||||
| -rw-r--r-- | crates/imxrt1170evk/Cargo.toml | 17 | ||||
| -rw-r--r-- | crates/imxrt1170evk/build.rs | 17 | ||||
| -rw-r--r-- | crates/imxrt1170evk/src/lib.rs | 117 | ||||
| -rw-r--r-- | crates/imxrt1170evk/src/main.rs | 10 | ||||
| -rw-r--r-- | crates/qemu/Cargo.toml | 15 | ||||
| -rw-r--r-- | crates/qemu/src/lib.rs | 37 | ||||
| -rw-r--r-- | crates/qemu/src/main.rs | 72 | ||||
| -rw-r--r-- | crates/stdio-imxrt-usbd/Cargo.toml | 13 | ||||
| -rw-r--r-- | crates/stdio-imxrt-usbd/src/lib.rs | 297 | ||||
| -rw-r--r-- | crates/stdio-semihosting/.gitignore | 1 | ||||
| -rw-r--r-- | crates/stdio-semihosting/Cargo.toml | 8 | ||||
| -rw-r--r-- | crates/stdio-semihosting/src/lib.rs | 52 | ||||
| -rw-r--r-- | crates/teensy4/Cargo.toml | 12 | ||||
| -rw-r--r-- | crates/teensy4/src/lib.rs | 67 | ||||
| -rw-r--r-- | crates/teensy4/src/main.rs | 13 |
26 files changed, 1817 insertions, 0 deletions
diff --git a/.cargo/imxrt1170evk.toml b/.cargo/imxrt1170evk.toml new file mode 100644 index 0000000..a3f57df --- /dev/null +++ b/.cargo/imxrt1170evk.toml @@ -0,0 +1,10 @@ +[build] +target = "thumbv7em-threadx-eabihf" + +[target.thumbv7em-threadx-eabihf] +runner = "probe-rs run --chip=mimxrt1170" +rustflags = [ + "-Clink-arg=-Tweak-symbols.x", + "-Clink-arg=-Timxrt-link.x", + "-Ctarget-cpu=cortex-m7", +] diff --git a/.cargo/qemu.toml b/.cargo/qemu.toml new file mode 100644 index 0000000..5e76894 --- /dev/null +++ b/.cargo/qemu.toml @@ -0,0 +1,10 @@ +[build] +target = "thumbv7em-threadx-eabihf" + +[target.thumbv7em-threadx-eabihf] +runner = "qemu-system-arm -cpu cortex-m4 -machine lm3s6965evb -serial none -monitor none -display none -semihosting-config enable=on,target=native -s -kernel" +rustflags = [ + "-Clink-arg=-Tweak-symbols.x", + "-Clink-arg=-Tlink.x", + "-Ctarget-cpu=cortex-m4", +] diff --git a/.cargo/teensy4.toml b/.cargo/teensy4.toml new file mode 100644 index 0000000..dcdf45e --- /dev/null +++ b/.cargo/teensy4.toml @@ -0,0 +1,10 @@ +[build] +target = "thumbv7em-threadx-eabihf" + +[target.thumbv7em-threadx-eabihf] +runner = "teensy4-runner" +rustflags = [ + "-Clink-arg=-Tweak-symbols.x", + "-Clink-arg=-Tt4link.x", + "-Ctarget-cpu=cortex-m7", +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7868e5d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,543 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "git+https://github.com/rust-embedded/cortex-m?rev=8c62cc47314445346851b0d0664f9cc34c50b24d#8c62cc47314445346851b0d0664f9cc34c50b24d" +dependencies = [ + "bare-metal", + "bitfield", + "cortex-m-macros", + "critical-section", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "volatile-register", +] + +[[package]] +name = "cortex-m-macros" +version = "0.1.0" +source = "git+https://github.com/rust-embedded/cortex-m?rev=8c62cc47314445346851b0d0664f9cc34c50b24d#8c62cc47314445346851b0d0664f9cc34c50b24d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c439b23bf18154d8a634543caf1ee73cceb723f2902f7ea243634159a00fd47" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a75f3e7f3f01d649668d68e02d0ef92c7041a9c97b8fe6bc354ddbf40822d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cortex-m-semihosting" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c23234600452033cc77e4b761e740e02d2c4168e11dbf36ab14a0f58973592b0" +dependencies = [ + "cortex-m", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "futures" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + +[[package]] +name = "futures-task" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" + +[[package]] +name = "futures-util" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "imxrt-boot-gen" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac882631c124a0fcb2f9f5969e5e81ff8fc6c68ffb82d2728a6d1264d1647ac1" + +[[package]] +name = "imxrt-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fd44f861bfa8cb39efcf6461461ed3c91c63f629c3811abc00c4310f7629ef" +dependencies = [ + "critical-section", + "ral-registers", +] + +[[package]] +name = "imxrt-hal" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfcef664c97cc60f6fc3b74ff858ff84b95de89bcfad9884dd0d97823c17d71" +dependencies = [ + "bitflags", + "cfg-if", + "cortex-m", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-io 0.7.1", + "futures", + "imxrt-dma", + "imxrt-iomuxc", + "imxrt-ral", + "nb 1.1.0", + "ral-registers", +] + +[[package]] +name = "imxrt-iomuxc" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e49e4e4a4f2c73284ec46cd459a9c4eb7d93b8876a8a6cf5cd379e36e677bc83" + +[[package]] +name = "imxrt-ral" +version = "0.6.2" +source = "git+https://github.com/mciantyre/imxrt-ral?branch=rust-threadx#83e17a2759d2ee82625d783b79ab0d2af8ee1460" +dependencies = [ + "cortex-m", + "ral-registers", +] + +[[package]] +name = "imxrt-rt" +version = "0.1.8" +source = "git+https://github.com/mciantyre/imxrt-rt?branch=rust-threadx#8b56a9a4d9f551384efb961a4e851e9f2877dcfa" +dependencies = [ + "cfg-if", + "cortex-m-rt", +] + +[[package]] +name = "imxrt-usbd" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acac3ea0fc5a0f2cdfe5cf9da318a24b9ee5eb4a51bed893d0637e0d2b78e01f" +dependencies = [ + "bitflags", + "cortex-m", + "imxrt-ral", + "usb-device", +] + +[[package]] +name = "imxrt1170evk-fcb" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a8b1cb9130fe5713ab9be9a051705ded608a4a8d3dc32104abcdaa716583d1" +dependencies = [ + "imxrt-boot-gen", +] + +[[package]] +name = "lm3s6965" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d7ed5360fee8fd434cf7995ef1d7ad42697abb538e34383a39da8df5495446" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "panic-semihosting" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee8a3e1233d9073d76a870223512ce4eeea43c067a94a445c13bd6d792d7b1ab" +dependencies = [ + "cortex-m", + "cortex-m-semihosting", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ral-registers" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46b71a9d9206e8b46714c74255adcaea8b11e0350c1d8456165073c3f75fc81a" + +[[package]] +name = "rust-threadx-cortex-m" +version = "0.1.0" + +[[package]] +name = "rust-threadx-imxrt1170evk" +version = "0.1.0" +dependencies = [ + "cortex-m", + "imxrt-ral", + "imxrt-rt", + "imxrt1170evk-fcb", + "rust-threadx-cortex-m", + "rust-threadx-stdio-semihosting", +] + +[[package]] +name = "rust-threadx-qemu" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "cortex-m-semihosting", + "lm3s6965", + "panic-semihosting", + "rust-threadx-cortex-m", + "rust-threadx-stdio-semihosting", +] + +[[package]] +name = "rust-threadx-stdio-imxrt-usbd" +version = "0.1.0" +dependencies = [ + "cortex-m", + "imxrt-ral", + "imxrt-usbd", + "usb-device", + "usbd-serial", +] + +[[package]] +name = "rust-threadx-stdio-semihosting" +version = "0.1.0" +dependencies = [ + "cortex-m-semihosting", +] + +[[package]] +name = "rust-threadx-teensy4" +version = "0.1.0" +dependencies = [ + "cortex-m", + "rust-threadx-cortex-m", + "rust-threadx-stdio-imxrt-usbd", + "teensy4-bsp", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "teensy4-bsp" +version = "0.6.0" +source = "git+https://github.com/mciantyre/teensy4-rs?branch=rust-threadx#c2dc3d0beab8d3a9d6f5eda39311eb68fda82053" +dependencies = [ + "cortex-m", + "imxrt-hal", + "imxrt-ral", + "imxrt-rt", + "imxrt-usbd", + "teensy4-fcb", + "teensy4-pins", +] + +[[package]] +name = "teensy4-fcb" +version = "0.5.0" +source = "git+https://github.com/mciantyre/teensy4-rs?branch=rust-threadx#c2dc3d0beab8d3a9d6f5eda39311eb68fda82053" +dependencies = [ + "imxrt-boot-gen", +] + +[[package]] +name = "teensy4-pins" +version = "0.4.0" +source = "git+https://github.com/mciantyre/teensy4-rs?branch=rust-threadx#c2dc3d0beab8d3a9d6f5eda39311eb68fda82053" +dependencies = [ + "imxrt-iomuxc", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[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-serial" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065e4eaf93db81d5adac82d9cef8f8da314cb640fa7f89534b972383f1cf80fc" +dependencies = [ + "embedded-hal 0.2.7", + "embedded-io 0.6.1", + "nb 1.1.0", + "usb-device", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ab25a71 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,36 @@ +[workspace] +resolver = "3" +members = ["crates/*"] + +[workspace.dependencies] +rust-threadx-cortex-m = { path = "crates/cortex-m" } +rust-threadx-qemu = { path = "crates/qemu" } +rust-threadx-stdio-imxrt-usbd = { path = "crates/stdio-imxrt-usbd" } +rust-threadx-stdio-semihosting = { path = "crates/stdio-semihosting" } + +cortex-m = { version = "0.7" } +cortex-m-rt = { version = "0.7" } +cortex-m-semihosting = { version = "0.5" } +lm3s6965 = "0.2" + +imxrt-ral = { version = "0.6.2", features = ["rt"] } +imxrt-rt = { version = "0.1.8", features = ["device"] } + +[patch.crates-io] +cortex-m = { git = "https://github.com/rust-embedded/cortex-m", rev = "8c62cc47314445346851b0d0664f9cc34c50b24d" } +imxrt-ral = { + git = "https://github.com/mciantyre/imxrt-ral", + branch = "rust-threadx", +} +imxrt-rt = { + git = "https://github.com/mciantyre/imxrt-rt", + branch = "rust-threadx", +} +teensy4-fcb = { + git = "https://github.com/mciantyre/teensy4-rs", + branch = "rust-threadx", +} +teensy4-bsp = { + git = "https://github.com/mciantyre/teensy4-rs", + branch = "rust-threadx", +} @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d037d41 --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# rust-threadx-demos + +These demos use ThreadX as a Rust standard library implementation. +They work in / on + +- QEMU +- a Teensy 4.0 +- an iMXRT1170EVK + +Right now, they blink LEDs and / or print `"hello world!"`. + +These demos are for [my RustConf 2026 talk][project-landing]! + +[project-landing]: https://www.mciantyre.dev/projects/rust-std-on-threadx/ + +## Getting started + +You'll need a custom toolchain to build these examples. +[Here][project-landing] is how to build that toolchain. +All commands below assume your toolchain keeps the `stage1` name. + +### QEMU + +Install `qemu-system-arm` for your host. +Then, run the example: + +``` +cargo +stage1 --config .cargo/qemu.toml run --release --package=rust-threadx-qemu +``` + +QEMU's stdio uses semihosting for input and output. + +### Teensy 4.0 + +You'll need a Teensy 4.0 board. +[Install the dependencies][teensy4-rs] discussed in the teensy4-rs project, including the command-line `teensy_loader_cli`. +Also, [install the teensy4-runner][teensy4-runner]. + +[teensy4-rs]: https://github.com/mciantyre/teensy4-rs +[teensy4-runner]: https://github.com/mciantyre/teensy4-rs/tree/master/tools + +Run the following to compile the example, convert it to Intel HEX, and attempt board programming: + +``` +cargo +stage1 --config .cargo/teensy4.toml run --release --package=rust-threadx-teensy4 +``` + +Once running on your board, you should see the LED blinking. +Connect to the USB serial interface to see the welcome message. +The board's stdio uses USB serial for stdout; stdin is not (yet) implemented. + +### iMXRT1170EVK + +You'll need the board and a recent build of [probe-rs]. +Run the following to compile and flash the example: + +[probe-rs]: https://probe.rs + +``` +cargo +stage1 --config .cargo/imxrt1170evk.toml run --release --package=rust-threadx-imxrt1170evk +``` + +You should see the LED blinking. +The board's stdio uses semihosting for input and output; probe-rs should automatically detect this. diff --git a/crates/cortex-m/Cargo.toml b/crates/cortex-m/Cargo.toml new file mode 100644 index 0000000..9f18dd2 --- /dev/null +++ b/crates/cortex-m/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rust-threadx-cortex-m" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] diff --git a/crates/cortex-m/build.rs b/crates/cortex-m/build.rs new file mode 100644 index 0000000..286e738 --- /dev/null +++ b/crates/cortex-m/build.rs @@ -0,0 +1,14 @@ +use std::{env, path}; + +fn main() { + let out_dir = path::PathBuf::from(env::var("OUT_DIR").unwrap()); + std::fs::write( + out_dir.join("weak-symbols.x"), + r#" +PROVIDE(PendSV = PendSV_Handler); +PROVIDE(SysTick = _tx_timer_interrupt); +"#, + ) + .unwrap(); + println!("cargo::rustc-link-search={}", out_dir.display()); +} diff --git a/crates/cortex-m/src/lib.rs b/crates/cortex-m/src/lib.rs new file mode 100644 index 0000000..0c9ac1a --- /dev/null +++ b/crates/cortex-m/src/lib.rs @@ -0,0 +1 @@ +#![no_std] diff --git a/crates/imxrt1170evk/Cargo.toml b/crates/imxrt1170evk/Cargo.toml new file mode 100644 index 0000000..995eff0 --- /dev/null +++ b/crates/imxrt1170evk/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rust-threadx-imxrt1170evk" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +imxrt-ral = { workspace = true, features = ["imxrt1176_cm7"] } +imxrt-rt = { workspace = true } +imxrt1170evk-fcb = { version = "0.2" } + +cortex-m = { workspace = true } +rust-threadx-cortex-m = { workspace = true } +rust-threadx-stdio-semihosting = { workspace = true } + +[build-dependencies] +imxrt-rt = { workspace = true } diff --git a/crates/imxrt1170evk/build.rs b/crates/imxrt1170evk/build.rs new file mode 100644 index 0000000..6d4b58e --- /dev/null +++ b/crates/imxrt1170evk/build.rs @@ -0,0 +1,17 @@ +use imxrt_rt::{Family, Memory, RuntimeBuilder}; + +fn main() { + RuntimeBuilder::from_flexspi(Family::Imxrt1170, 16 * 1024 * 1024) + .text(Memory::Itcm) + .vectors(Memory::Itcm) + .data(Memory::Dtcm) + .bss(Memory::Dtcm) + .uninit(Memory::Ocram) + .rodata(Memory::Ocram) + .stack(Memory::Dtcm) + .heap(Memory::Ocram) + .heap_size(8 * 1024) + .stack_size(4 * 1024) + .build() + .unwrap(); +} diff --git a/crates/imxrt1170evk/src/lib.rs b/crates/imxrt1170evk/src/lib.rs new file mode 100644 index 0000000..b626843 --- /dev/null +++ b/crates/imxrt1170evk/src/lib.rs @@ -0,0 +1,117 @@ +#![no_std] +#![feature(rustc_private)] +extern crate fusible; + +use core::pin::Pin; + +use imxrt_ral as ral; +use imxrt_rt as _; +use imxrt1170evk_fcb as _; +use rust_threadx_cortex_m as _; +use rust_threadx_stdio_semihosting as _; + +#[doc(hidden)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _tx_initialize_low_level() { + use cortex_m::{ + Peripherals, + peripheral::{scb::SystemHandler, syst::SystClkSource}, + }; + + cortex_m::interrupt::disable(); + + unsafe { + let Peripherals { + mut SYST, mut SCB, .. + } = Peripherals::steal(); + + SCB.set_priority(SystemHandler::SysTick, 0x40); + SCB.set_priority(SystemHandler::PendSV, 0xff); + SCB.set_priority(SystemHandler::SVCall, 0xff); + + SYST.set_clock_source(SystClkSource::External); + SYST.set_reload(100_000 / 100 - 1); + SYST.clear_current(); + SYST.enable_interrupt(); + SYST.enable_counter(); + } + + unsafe { + use ral::ccm; + + let ccm = ccm::CCM::instance(); + let clock_root = &ccm.CLOCK_ROOT[8]; + ral::modify_reg!(ccm::clockroot, clock_root, CLOCK_ROOT_CONTROL, MUX: 1, DIV: 240 - 1); + } + + unsafe { + use ral::iomuxc; + let iomuxc = iomuxc::IOMUXC::instance(); + + ral::write_reg!(iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_AD_04, MUX_MODE: 5, SION: 0); + + ral::write_reg!(iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_04, PUE: 0); + } + + unsafe { + use ral::gpio; + let gpio3 = gpio::GPIO3::instance(); + + const GPIO3_OUTPUTS: u32 = led::MASK; + + ral::write_reg!(gpio, gpio3, GDIR, GPIO3_OUTPUTS); + } +} + +/// Controls the LED. +pub mod led { + use crate::ral::gpio; + + pub(crate) const MASK: u32 = 1 << 3; + + /// Turn on the LED. + #[inline] + pub fn set() { + unsafe { crate::ral::write_reg!(gpio, gpio::GPIO3, DR_SET, MASK) }; + } + + /// Turn off the LED. + #[inline] + pub fn clear() { + unsafe { crate::ral::write_reg!(gpio, gpio::GPIO3, DR_CLEAR, MASK) }; + } + + /// Toggle the LED. + /// + /// This is achieved in hardware, without reading the current state + /// of the LED. It's usually more efficient. + #[inline] + pub fn toggle() { + unsafe { crate::ral::write_reg!(gpio, gpio::GPIO3, DR_TOGGLE, MASK) }; + } + + /// Drive the LED on or off. + #[inline] + pub fn drive(value: bool) { + if value { set() } else { clear() } + } +} + +use fusible::thread::{StaticStack, Thread, ThreadContext}; +static STACK: StaticStack<512> = StaticStack::new(); +static THREAD: ThreadContext = Thread::context(); + +#[unsafe(no_mangle)] +#[doc(hidden)] +pub extern "C" fn __rust_threadx_app_define() { + Thread::create( + Pin::static_ref(&THREAD), + STACK.take().unwrap(), + &Default::default(), + || loop { + fusible::thread::sleep(25); + led::toggle(); + }, + ) + .unwrap(); +} diff --git a/crates/imxrt1170evk/src/main.rs b/crates/imxrt1170evk/src/main.rs new file mode 100644 index 0000000..737a6cc --- /dev/null +++ b/crates/imxrt1170evk/src/main.rs @@ -0,0 +1,10 @@ +use rust_threadx_imxrt1170evk as _; + +fn main() { + let mut count = 0_usize; + loop { + println!("Hello world! The count is {count}"); + count = count.wrapping_add(1); + std::thread::sleep(std::time::Duration::from_millis(500)) + } +} diff --git a/crates/qemu/Cargo.toml b/crates/qemu/Cargo.toml new file mode 100644 index 0000000..904f696 --- /dev/null +++ b/crates/qemu/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rust-threadx-qemu" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cortex-m = { workspace = true } +cortex-m-rt = { workspace = true } +cortex-m-semihosting = { workspace = true } +lm3s6965 = { workspace = true } +panic-semihosting = { version = "0.6", features = ["exit"] } + +rust-threadx-cortex-m = { workspace = true } +rust-threadx-stdio-semihosting = { workspace = true } diff --git a/crates/qemu/src/lib.rs b/crates/qemu/src/lib.rs new file mode 100644 index 0000000..dc91465 --- /dev/null +++ b/crates/qemu/src/lib.rs @@ -0,0 +1,37 @@ +#![no_std] + +use cortex_m_rt as _; +use lm3s6965 as _; + +use rust_threadx_cortex_m as _; +use rust_threadx_stdio_semihosting as _; + +pub fn exit_success() -> ! { + use cortex_m_semihosting::debug::*; + exit(EXIT_SUCCESS); + unreachable!() +} + +// Designed for ARMv6-M. Just enough to get the example +// running in QEMU with a meaningless system tick. +#[unsafe(no_mangle)] +pub extern "C" fn _tx_initialize_low_level() { + use cortex_m::{Peripherals, peripheral::scb::SystemHandler}; + + cortex_m::interrupt::disable(); + + let Peripherals { + mut SYST, mut SCB, .. + } = Peripherals::take().unwrap(); + + SYST.set_reload(80000 - 1); + SYST.clear_current(); + SYST.enable_interrupt(); + SYST.enable_counter(); + + unsafe { + SCB.set_priority(SystemHandler::SysTick, 0x40); + SCB.set_priority(SystemHandler::PendSV, 0xff); + SCB.set_priority(SystemHandler::SVCall, 0xff); + } +} diff --git a/crates/qemu/src/main.rs b/crates/qemu/src/main.rs new file mode 100644 index 0000000..3a3df38 --- /dev/null +++ b/crates/qemu/src/main.rs @@ -0,0 +1,72 @@ +use rust_threadx_qemu::exit_success; +use std::{ + sync::{Arc, Condvar, Mutex}, + time::Duration, +}; + +fn main() { + println!("Hello, world!"); + println!("Testing allocations in 0.25 seconds..."); + std::thread::sleep(Duration::from_millis(250)); + test_heap_allocation(); + + let mtx = Arc::new(Mutex::new(0_u32)); + let cv = Arc::new(Condvar::new()); + + std::thread::scope(|s| { + s.spawn(|| { + println!("Hello from one thread!"); + let mut guard = mtx.lock().unwrap(); + *guard += 1; + cv.notify_all(); + }); + }); + + let (m, c) = (mtx.clone(), cv.clone()); + std::thread::spawn(move || { + println!("Hello from another thread!"); + + let guard = m.lock().unwrap(); + let mut guard = c.wait_while(guard, |c| *c != 1).unwrap(); + *guard += 1; + c.notify_all(); + }) + .join() + .unwrap(); + + let guard = mtx.lock().unwrap(); + let guard = cv.wait_while(guard, |c| *c != 2).unwrap(); + assert_eq!(*guard, 2); + + echo_stdin(); + exit_success(); +} + +fn echo_stdin() { + use std::io::{self, Write}; + + print!("Enter a message: "); + // `print!` does not flush, so make sure the prompt reaches the host before + // we block waiting on input. + io::stdout().flush().ok(); + + let mut message = String::new(); + match io::stdin().read_line(&mut message) { + Ok(0) => println!("No message received (EOF)"), + Ok(_) => println!("You said: {}", message.trim_end()), + Err(e) => println!("Failed to read message: {e}"), + } +} + +fn test_heap_allocation() { + use core::hint::black_box; + + for len in [32usize, 64, 128] { + let mut v: Vec<usize> = Vec::with_capacity(len); + for i in 0..len { + v.push(black_box(i)); + } + let v = black_box(v); + println!("Allocated vector with {} elements", v.len()); + } +} diff --git a/crates/stdio-imxrt-usbd/Cargo.toml b/crates/stdio-imxrt-usbd/Cargo.toml new file mode 100644 index 0000000..ce3012e --- /dev/null +++ b/crates/stdio-imxrt-usbd/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rust-threadx-stdio-imxrt-usbd" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +imxrt-usbd = "0.4" +usb-device = "0.3" +usbd-serial = "0.2" + +cortex-m = { workspace = true } +imxrt-ral = { workspace = true } diff --git a/crates/stdio-imxrt-usbd/src/lib.rs b/crates/stdio-imxrt-usbd/src/lib.rs new file mode 100644 index 0000000..dbd4f66 --- /dev/null +++ b/crates/stdio-imxrt-usbd/src/lib.rs @@ -0,0 +1,297 @@ +//! Route stdout through a USB serial interface +//! +//! You'll notice this looks pretty similar to imxrt-log's usbd backend. +//! I replace the bbqueue with a fusible Queue and BlockPool, and I run +//! the "backend" on a fusible Thread. +//! +//! You're responsible for registering the USB interrupt and calling +//! `on_interrupt` in that interrupt. You'll also need to call `start` +//! to set up the driver. +//! +//! This only supports stdout; I haven't gotten stdin working yet. + +#![no_std] +#![feature(rustc_private)] +extern crate fusible; + +use usb_device::device::UsbDeviceState; + +const VID_PID: usb_device::device::UsbVidPid = usb_device::device::UsbVidPid(0x5824, 0x27dd); +const PRODUCT: &str = "rust-threadx-usbd"; + +/// Provide some extra overhead for the interrupt endpoint. +/// +/// If you start noticing panics, check to make sure that this buffer +/// is large enough for all the max packet sizes for all the endpoints. +const ENDPOINT_BYTES: usize = MAX_PACKET_SIZE * 2 + EP0_CONTROL_PACKET_SIZE * 2 + 128; +static ENDPOINT_MEMORY: imxrt_usbd::EndpointMemory<ENDPOINT_BYTES> = + imxrt_usbd::EndpointMemory::new(); +static ENDPOINT_STATE: imxrt_usbd::EndpointState<6> = imxrt_usbd::EndpointState::new(); + +type Bus = imxrt_usbd::BusAdapter; +type Class<'a> = usbd_serial::CdcAcmClass<'a, Bus>; +type Device<'a> = usb_device::device::UsbDevice<'a, Bus>; + +/// High-speed bulk endpoint limit. +const MAX_PACKET_SIZE: usize = 512; +/// Size for control transfers on endpoint 0. +const EP0_CONTROL_PACKET_SIZE: usize = 64; +/// The USB GPT timer we use to (infrequently) check for data. +const GPT_INSTANCE: imxrt_usbd::gpt::Instance = imxrt_usbd::gpt::Instance::Gpt0; + +struct Backend<'a> { + class: Class<'a>, + device: Device<'a>, + configured: bool, + stdout_queue: &'static BlockQueue, +} + +impl Backend<'_> { + fn poll(&mut self) { + // Is there a CDC class event, like a completed transfer? If so, check + // the consumer immediately, even if a timer hasn't expired. + // + // Checking the consumer on class traffic lets the driver burst out data. + // Suppose the user wants to use the USB GPT timer, and they configure a very + // long interval. That interval expires, and we see tons of data in the consumer. + // We should write that out as fast as possible, even if the timer hasn't elapsed. + // That's the behavior provided by the class_event flag. + let class_event = self.device.poll(&mut [&mut self.class]); + let timer_event = self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| { + let mut elapsed = false; + while gpt.is_elapsed() { + gpt.clear_elapsed(); + elapsed = true; + } + // Simulate a timer event if the timer is not running. + // + // If the timer is not running, its because the user disabled interrupts, + // and they're using their own timer / polling loop. There might not always + // be a class traffic (transfer complete) event when the user polls, so + // signaling true allows the poll to check the consumer for new data and + // send it. + // + // If the timer is running, checking the consumer depends on the elapsed + // timer. + elapsed || !gpt.is_running() + }); + let check_consumer = class_event || timer_event; + + if self.device.state() != UsbDeviceState::Configured { + if self.configured { + // Turn off the timer, but only if we were previously configured. + self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| gpt.stop()); + } + self.configured = false; + // We can't use the class if we're not configured, + // so bail out here. + return; + } + + // We're now configured. Are we newly configured? + if !self.configured { + // Must call this when we transition into configured. + self.device.bus().configure(); + self.device.bus().gpt_mut(GPT_INSTANCE, |gpt| { + // There's no need for a timer if interrupts are disabled. + // If the user disabled USB interrupts and decided to poll this + // from another timer, this USB timer could unnecessarily block + // that timer from checking the consumer queue. + if gpt.is_interrupt_enabled() { + gpt.run() + } + }); + self.configured = true; + } + + self.class.read_packet(&mut []).ok(); + + if check_consumer && let Some(block) = self.stdout_queue.try_receive() { + // Ignoring errors here... and we're not really utilizing the full + // width of the endpoint buffer. Let's see how long we can get + // away with this... + let _ = self.class.write_packet(&block.byte_buffer[..block.len]); + } + } +} + +fn run<const N: u8>( + peripherals: imxrt_usbd::Instances<N>, + interrupt: imxrt_ral::Interrupt, + stdout_queue: &'static BlockQueue, +) -> ! { + let events = Semaphore::create(Pin::static_ref(&EVENTS), &Default::default()).unwrap(); + + let bus = { + // Safety: we ensure that the bus, class, and all other related USB objects + // are accessed in poll(). poll() is not reentrant, so there's no racing + // occuring across executing contexts. + let bus = unsafe { + imxrt_usbd::BusAdapter::without_critical_sections( + peripherals, + &ENDPOINT_MEMORY, + &ENDPOINT_STATE, + imxrt_usbd::Speed::High, + ) + }; + bus.set_interrupts(true); + bus.gpt_mut(GPT_INSTANCE, |gpt| { + gpt.stop(); + gpt.clear_elapsed(); + gpt.set_interrupt_enabled(true); + gpt.set_mode(imxrt_usbd::gpt::Mode::Repeat); + gpt.set_load(4_000); + gpt.reset(); + }); + usb_device::bus::UsbBusAllocator::new(bus) + }; + let class = usbd_serial::CdcAcmClass::new(&bus, MAX_PACKET_SIZE as u16); + + let device = usb_device::device::UsbDeviceBuilder::new(&bus, VID_PID) + .strings(&[usb_device::device::StringDescriptors::default().product(PRODUCT)]) + .unwrap() + .device_class(usbd_serial::USB_CLASS_CDC) + .max_packet_size_0(EP0_CONTROL_PACKET_SIZE as u8) + .unwrap() + .build(); + + // Not sure which endpoints the CDC ACM class will pick, + // so enable the setting for all non-zero endpoints. + for idx in 1..8 { + for dir in &[usb_device::UsbDirection::In, usb_device::UsbDirection::Out] { + let ep_addr = usb_device::endpoint::EndpointAddress::from_parts(idx, *dir); + // CDC class requires that we send the ZLP. + // Let the hardware do that for us. + device.bus().enable_zlt(ep_addr); + } + } + + let mut backend = Backend { + class, + device, + configured: false, + stdout_queue, + }; + + loop { + backend.poll(); + unsafe { cortex_m::peripheral::NVIC::unmask(interrupt) }; + events.get().unwrap(); + } +} + +use core::pin::Pin; +use fusible::semaphore::{Semaphore, SemaphoreContext}; + +static EVENTS: SemaphoreContext = Semaphore::context(); + +#[inline] +pub fn on_interrupt(intr: imxrt_ral::Interrupt) { + cortex_m::peripheral::NVIC::mask(intr); + if let Some(events) = Pin::static_ref(&EVENTS).try_created() { + events.put(); + } +} + +use fusible::thread::{StaticStack, Thread, ThreadContext}; + +static STACK: StaticStack<2048> = StaticStack::new(); +static THREAD: ThreadContext = Thread::context(); + +pub fn start<const N: u8>(peripherals: imxrt_usbd::Instances<N>, interrupt: imxrt_ral::Interrupt) { + BlockPool::create( + Pin::static_ref(&BLOCK_POOL), + BLOCK_STORAGE.take().unwrap(), + &Default::default(), + ) + .unwrap(); + + let stdout_queue = Queue::create( + Pin::static_ref(&STDOUT_QUEUE), + STDOUT_STORAGE.take().unwrap(), + &Default::default(), + ) + .unwrap(); + + Thread::create( + Pin::static_ref(&THREAD), + STACK.take().unwrap(), + &Default::default(), + move || run(peripherals, interrupt, stdout_queue), + ) + .unwrap(); +} + +use fusible::block_pool::{Block, BlockPool, BlockPoolContext, StaticBlocks}; + +type ByteBuffer = [u8; MAX_PACKET_SIZE]; + +struct BlockWithLen { + len: usize, + byte_buffer: ByteBuffer, +} + +impl BlockWithLen { + fn copy_of(buf: &[u8]) -> Self { + let mut byte_buffer: ByteBuffer = [0_u8; _]; + let len = byte_buffer.len().min(buf.len()); + byte_buffer[..len].copy_from_slice(&buf[..len]); + Self { len, byte_buffer } + } +} + +const BLOCK_COUNT: usize = 4; +static BLOCK_STORAGE: StaticBlocks<BlockWithLen, BLOCK_COUNT> = StaticBlocks::new(); +static BLOCK_POOL: BlockPoolContext<BlockWithLen> = BlockPool::context(); + +type ByteBufferBlock = Block<'static, BlockWithLen>; + +use fusible::queue::{Queue, QueueContext, StaticQueueSlots}; + +static STDOUT_STORAGE: StaticQueueSlots<ByteBufferBlock, BLOCK_COUNT> = StaticQueueSlots::new(); +static STDOUT_QUEUE: QueueContext<ByteBufferBlock> = Queue::context(); + +type BlockQueue = Queue<ByteBufferBlock>; + +fn stdout_write(buf: &[u8]) -> usize { + let mut written = 0; + + let Some(block_pool) = Pin::static_ref(&BLOCK_POOL).try_created() else { + return written; + }; + + let Some(stdout_queue) = Pin::static_ref(&STDOUT_QUEUE).try_created() else { + return written; + }; + + for chunk in buf.chunks(MAX_PACKET_SIZE) { + let Some(block) = block_pool.try_allocate(|| { + let block = BlockWithLen::copy_of(&chunk); + written += block.len; + block + }) else { + break; + }; + + if stdout_queue.try_send(block).is_some() { + break; + } + } + + written +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stdout_write(buf: &[u8]) -> usize { + stdout_write(buf) +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stderr_write(buf: &[u8]) -> usize { + stdout_write(buf) +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stdin_read(_: &mut [u8]) -> usize { + 0 // Haven't gotten this working yet. +} diff --git a/crates/stdio-semihosting/.gitignore b/crates/stdio-semihosting/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/crates/stdio-semihosting/.gitignore @@ -0,0 +1 @@ +/target diff --git a/crates/stdio-semihosting/Cargo.toml b/crates/stdio-semihosting/Cargo.toml new file mode 100644 index 0000000..3178f61 --- /dev/null +++ b/crates/stdio-semihosting/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "rust-threadx-stdio-semihosting" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cortex-m-semihosting = { workspace = true } diff --git a/crates/stdio-semihosting/src/lib.rs b/crates/stdio-semihosting/src/lib.rs new file mode 100644 index 0000000..1a18b84 --- /dev/null +++ b/crates/stdio-semihosting/src/lib.rs @@ -0,0 +1,52 @@ +//! ThreadX stdio implemented over semihosting + +#![no_std] + +#[unsafe(no_mangle)] +fn __rust_threadx_stdout_write(buf: &[u8]) -> usize { + use cortex_m_semihosting::hio; + // `write_all` loops until the whole buffer is sent or the host reports an + // error, so a success means every byte was written. + let Ok(mut out) = hio::hstdout() else { + return 0; + }; + match out.write_all(buf) { + Ok(()) => buf.len(), + Err(()) => 0, + } +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stderr_write(buf: &[u8]) -> usize { + use cortex_m_semihosting::hio; + let Ok(mut out) = hio::hstderr() else { + return 0; + }; + match out.write_all(buf) { + Ok(()) => buf.len(), + Err(()) => 0, + } +} + +#[unsafe(no_mangle)] +fn __rust_threadx_stdin_read(buf: &mut [u8]) -> usize { + use cortex_m_semihosting::{nr, syscall}; + + if buf.is_empty() { + return 0; + } + + // `hio` only opens the host console for writing, so open it for reading + // directly. `:tt` is the special semihosting path for the console. + let name = b":tt\0"; + let fd = match unsafe { syscall!(OPEN, name.as_ptr(), nr::open::R, name.len() - 1) } as isize { + -1 => return 0, + fd => fd as usize, + }; + + // SYS_READ returns the number of bytes that were *not* read; on error it + // returns a value larger than the request, which `saturating_sub` maps to 0. + let not_read = unsafe { syscall!(READ, fd, buf.as_mut_ptr(), buf.len()) }; + let _ = unsafe { syscall!(CLOSE, fd) }; + buf.len().saturating_sub(not_read) +} diff --git a/crates/teensy4/Cargo.toml b/crates/teensy4/Cargo.toml new file mode 100644 index 0000000..4b196d5 --- /dev/null +++ b/crates/teensy4/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rust-threadx-teensy4" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +cortex-m = { workspace = true } +teensy4-bsp = { version = "0.6", features = ["rt"] } + +rust-threadx-cortex-m = { workspace = true } +rust-threadx-stdio-imxrt-usbd = { workspace = true } diff --git a/crates/teensy4/src/lib.rs b/crates/teensy4/src/lib.rs new file mode 100644 index 0000000..35afb31 --- /dev/null +++ b/crates/teensy4/src/lib.rs @@ -0,0 +1,67 @@ +#![no_std] +#![feature(rustc_private)] +extern crate fusible; + +use core::pin::Pin; + +use rust_threadx_cortex_m as _; +use rust_threadx_stdio_imxrt_usbd as stdio; + +#[doc(hidden)] +#[unsafe(no_mangle)] +pub unsafe extern "C" fn _tx_initialize_low_level() { + use cortex_m::{ + Peripherals, + peripheral::{scb::SystemHandler, syst::SystClkSource}, + }; + + cortex_m::interrupt::disable(); + + unsafe { + let Peripherals { + mut SYST, mut SCB, .. + } = Peripherals::steal(); + + SCB.set_priority(SystemHandler::SysTick, 0x40); + SCB.set_priority(SystemHandler::PendSV, 0xff); + SCB.set_priority(SystemHandler::SVCall, 0xff); + + SYST.set_clock_source(SystClkSource::External); + SYST.set_reload(100_000 / 100 - 1); + SYST.clear_current(); + SYST.enable_interrupt(); + SYST.enable_counter(); + } +} + +use fusible::thread::{StaticStack, Thread, ThreadContext}; +static STACK: StaticStack<512> = StaticStack::new(); +static THREAD: ThreadContext = Thread::context(); + +#[unsafe(no_mangle)] +#[doc(hidden)] +pub extern "C" fn __rust_threadx_app_define() { + let instances = teensy4_bsp::board::instances(); + let mut resources = teensy4_bsp::board::t40(instances); + + let led = teensy4_bsp::board::led(&mut resources.gpio2, resources.pins.p13); + Thread::create( + Pin::static_ref(&THREAD), + STACK.take().unwrap(), + &Default::default(), + move || loop { + fusible::thread::sleep(50); + led.toggle(); + }, + ) + .unwrap(); + + stdio::start(resources.usb, interrupt::USB_OTG1); +} + +use teensy4_bsp::{ral::interrupt, rt::interrupt}; + +#[interrupt] +fn USB_OTG1() { + stdio::on_interrupt(interrupt::USB_OTG1); +} diff --git a/crates/teensy4/src/main.rs b/crates/teensy4/src/main.rs new file mode 100644 index 0000000..16a1a1b --- /dev/null +++ b/crates/teensy4/src/main.rs @@ -0,0 +1,13 @@ +use std::thread; +use std::time::Duration; + +use rust_threadx_teensy4 as _; + +fn main() { + let mut count = 0_usize; + loop { + thread::sleep(Duration::from_millis(500)); + println!("Hello world! The count is {count}\r"); + count = count.wrapping_add(1); + } +} |
