From dbe610d1da96b36589d6f5f958aa319a28e1b650 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 1 Jun 2026 10:48:42 -0700 Subject: [PATCH 01/27] i2c::slave: implement embedded-mcu-hal i2c::target trait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds embedded_mcu_hal::i2c::target::blocking::I2c and ::asynch::I2c trait impls to the existing I2cSlave driver. The inherent listen / respond_to_write / respond_to_read API and the Command / Response enums are preserved verbatim — the trait impls are layered on top via private helpers that return richer InternalEvent / InternalTermination enums. Both flavours are implemented twice, once for SevenBitAddress and once for TenBitAddress; a runtime address-mode check returns ErrorKind::Other when the wrong impl is driven against the configured address. Adds a public recover() method on both I2cSlave<'_, Blocking> and I2cSlave<'_, Async> that aborts any in-flight DMA, NAKs a pending byte, disables DMA arming, clears the deselect latch, and drops any pending remediation flags. The configured address(es) and slven bit are preserved across recover, matching the trait contract. Surfaces a RepeatedStart edge from a previous respond_to_* as a separate listen event for the trait impls (via the new PendingEdge bookkeeping field). The inherent API ignores this field, so existing callers are unaffected. A probe (0-byte write) surfaces as Request::Stop(addr) with no preceding Write — documented in the module rustdoc. Adds two rt685s-evk examples that drive the new trait API end-to-end: i2c-slave-async-target-trait.rs and i2c-slave-target-trait.rs. Pin-out matches the existing i2c-slave-async.rs example so the Pi5 master test rig referenced in that example works unchanged. Includes a host-side unit test module covering the Error::kind() mapping for every TransferError variant + UnsupportedConfiguration, and the AddressKind helper round-trips. Tests compile via cargo check --tests; running them requires the same out-of-tree test harness setup the rest of the crate already needs (cortex-m-rt is pulled in unconditionally). Bumps the embedded-mcu-hal dependency from 0.2.0 to 0.3.0, which is the first release containing the i2c::target trait module. --- Cargo.lock | 210 ++-- Cargo.toml | 2 +- examples/rt685s-evk/Cargo.lock | 129 ++- examples/rt685s-evk/Cargo.toml | 2 +- .../src/bin/i2c-slave-async-target-trait.rs | 169 ++++ .../src/bin/i2c-slave-target-trait.rs | 131 +++ src/i2c/slave.rs | 921 +++++++++++++++++- 7 files changed, 1432 insertions(+), 132 deletions(-) create mode 100644 examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs create mode 100644 examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs diff --git a/Cargo.lock b/Cargo.lock index 2ae1b6e2..e3289f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,15 +13,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "az" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" [[package]] name = "bare-metal" @@ -52,9 +52,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytemuck" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -64,9 +64,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -74,15 +74,15 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "num-traits", ] @@ -137,9 +137,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "darling" @@ -178,9 +178,18 @@ dependencies = [ [[package]] name = "defmt" -version = "1.0.1" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.0", +] + +[[package]] +name = "defmt" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ "bitflags", "defmt-macros", @@ -188,9 +197,9 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" dependencies = [ "defmt-parser", "proc-macro-error2", @@ -210,28 +219,28 @@ dependencies = [ [[package]] name = "defmt-rtt" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2cac3b8a5644a9e02b75085ebad3b6deafdbdbdec04bb25086523828aa4dfd1" +checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" dependencies = [ "critical-section", - "defmt", + "defmt 1.1.0", ] [[package]] name = "document-features" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embassy-embedded-hal" @@ -296,7 +305,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt", + "defmt 1.1.0", "num-traits", ] @@ -318,7 +327,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.0", "document-features", "embassy-embedded-hal", "embassy-executor", @@ -360,7 +369,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.1.0", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -402,6 +411,12 @@ dependencies = [ "heapless", ] +[[package]] +name = "embedded-crc-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" + [[package]] name = "embedded-hal" version = "0.2.7" @@ -417,6 +432,9 @@ name = "embedded-hal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +dependencies = [ + "defmt 0.3.100", +] [[package]] name = "embedded-hal-async" @@ -424,6 +442,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" dependencies = [ + "defmt 0.3.100", "embedded-hal 1.0.0", ] @@ -469,12 +488,15 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" +checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ - "defmt", + "defmt 1.1.0", + "embedded-hal 1.0.0", + "embedded-hal-async", "num_enum", + "smbus-pec", ] [[package]] @@ -500,9 +522,9 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixed" -version = "1.29.0" +version = "1.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" +checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40" dependencies = [ "az", "bytemuck", @@ -518,21 +540,21 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "generator" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ "cc", "cfg-if", @@ -545,12 +567,13 @@ dependencies = [ [[package]] name = "half" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -564,9 +587,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" dependencies = [ "hash32", "stable_deref_trait", @@ -595,21 +618,21 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "litrs" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "log" -version = "0.4.27" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "loom" @@ -635,9 +658,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mimxrt600-fcb" @@ -657,7 +680,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.0", "vcell", ] @@ -670,7 +693,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.0", "vcell", ] @@ -709,9 +732,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -719,9 +742,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", @@ -748,9 +771,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "proc-macro-error-attr2" @@ -776,27 +799,27 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" [[package]] name = "regex-automata" @@ -862,9 +885,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "smallvec" @@ -872,11 +895,20 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smbus-pec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" +dependencies = [ + "embedded-crc-macros", +] + [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_cell" @@ -900,9 +932,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.100" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -919,18 +951,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -1009,15 +1041,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "valuable" @@ -1069,3 +1101,23 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 55a60ff6..e01b7189 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,7 +112,7 @@ embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-nb = { version = "1.0" } -embedded-mcu-hal = { version = "0.2.0", default-features = false } +embedded-mcu-hal = { version = "0.3.0", default-features = false } mimxrt600-fcb = "0.2.0" document-features = "0.2.7" paste = "1.0" diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index dc8011fb..05d9acbc 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -25,9 +25,9 @@ checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "az" @@ -99,9 +99,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -214,9 +214,18 @@ dependencies = [ [[package]] name = "defmt" -version = "1.0.1" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.0", +] + +[[package]] +name = "defmt" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ "bitflags", "defmt-macros", @@ -224,9 +233,9 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" dependencies = [ "defmt-parser", "proc-macro-error2", @@ -246,12 +255,12 @@ dependencies = [ [[package]] name = "defmt-rtt" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" dependencies = [ "critical-section", - "defmt", + "defmt 1.1.0", ] [[package]] @@ -265,9 +274,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embassy-embedded-hal" @@ -296,7 +305,7 @@ dependencies = [ "cordyceps", "cortex-m", "critical-section", - "defmt", + "defmt 1.1.0", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -334,7 +343,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt", + "defmt 1.1.0", "num-traits", ] @@ -355,7 +364,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.0", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -392,7 +401,7 @@ dependencies = [ "chrono", "cortex-m", "cortex-m-rt", - "defmt", + "defmt 1.1.0", "defmt-rtt", "embassy-executor", "embassy-futures", @@ -423,11 +432,11 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.1.0", "embedded-io-async 0.7.0", "futures-core", "futures-sink", - "heapless 0.9.2", + "heapless 0.9.3", ] [[package]] @@ -438,7 +447,7 @@ checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.1.0", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -463,9 +472,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693" dependencies = [ "embassy-executor-timer-queue", - "heapless 0.9.2", + "heapless 0.9.3", ] +[[package]] +name = "embedded-crc-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" + [[package]] name = "embedded-hal" version = "0.2.7" @@ -481,6 +496,9 @@ name = "embedded-hal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +dependencies = [ + "defmt 0.3.100", +] [[package]] name = "embedded-hal-async" @@ -488,6 +506,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" dependencies = [ + "defmt 0.3.100", "embedded-hal 1.0.0", ] @@ -543,13 +562,16 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" +checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ "chrono", - "defmt", + "defmt 1.1.0", + "embedded-hal 1.0.0", + "embedded-hal-async", "num_enum", + "smbus-pec", ] [[package]] @@ -665,9 +687,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ "cc", "cfg-if", @@ -710,9 +732,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" dependencies = [ "hash32", "stable_deref_trait", @@ -776,9 +798,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "litrs" @@ -788,9 +810,9 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "log" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" [[package]] name = "loom" @@ -816,9 +838,9 @@ dependencies = [ [[package]] name = "maybe-async" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" dependencies = [ "proc-macro2", "quote", @@ -827,9 +849,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mimxrt600-fcb" @@ -849,7 +871,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.0", "vcell", ] @@ -862,7 +884,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.1.0", "vcell", ] @@ -933,7 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt", + "defmt 1.1.0", ] [[package]] @@ -1020,9 +1042,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_core", ] @@ -1097,9 +1119,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "smallvec" @@ -1107,6 +1129,15 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smbus-pec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" +dependencies = [ + "embedded-crc-macros", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -1246,9 +1277,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1325,18 +1356,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/examples/rt685s-evk/Cargo.toml b/examples/rt685s-evk/Cargo.toml index 8f89ea49..f19062f3 100644 --- a/examples/rt685s-evk/Cargo.toml +++ b/examples/rt685s-evk/Cargo.toml @@ -51,7 +51,7 @@ embedded-storage-async = { version = "0.4.1" } is31fl3743b-driver = { version = "0.1.1", features = [ "is_blocking" ]} mimxrt600-fcb = "0.2.2" -embedded-mcu-hal = { version = "0.2.0", features = [ +embedded-mcu-hal = { version = "0.3.0", features = [ "chrono" ] } diff --git a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs new file mode 100644 index 00000000..74c79b62 --- /dev/null +++ b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs @@ -0,0 +1,169 @@ +//! I2C slave async example using the `embedded_mcu_hal::i2c::target` trait. +//! +//! This is the trait-based counterpart to `i2c-slave-async.rs`. It drives +//! the same FC2 hardware in the same listen → respond → re-listen loop, but +//! it goes through the `embedded_mcu_hal::i2c::target::asynch::I2c` trait +//! instead of the inherent `I2cSlave` methods. Differences from the +//! inherent example: +//! +//! * `listen()` returns a `Request` carrying the matched +//! address; the example logs it. +//! * `respond_to_write()` / `respond_to_read()` return `WriteStatus` / +//! `ReadStatus` enums that distinguish `Stopped` / `Restarted` / +//! `BufferFull` (for writes) and `Complete` / `NeedMore` / `EarlyStop` +//! (for reads). +//! * `RepeatedStart(prev_addr)` is surfaced as a separate `listen()` event +//! between a write and a read on the same controller transaction. +//! * `recover()` is called from the `Restarted` branch as a no-op +//! demonstration; in production it would only be needed after a +//! cancelled `respond_*` future. +//! +//! Tested against the same Raspberry Pi 5 master rig as the existing +//! `i2c-slave-async.rs` example +//! (https://github.com/jerrysxie/pi5-i2c-test). + +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_imxrt::i2c::slave::{Address, I2cSlave}; +use embassy_imxrt::i2c::{self, Async}; +use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_imxrt_examples as _; +// Bring the target trait methods into scope so we go through the trait +// instead of the inherent API. +use embedded_mcu_hal::i2c::SevenBitAddress; +use embedded_mcu_hal::i2c::target::Request; +use embedded_mcu_hal::i2c::target::asynch::I2c as TargetI2c; +use panic_probe as _; + +const SLAVE_ADDR: Option
= Address::new(0x20); +const BUFLEN: usize = 8; + +bind_interrupts!(struct Irqs { + FLEXCOMM2 => i2c::InterruptHandler; +}); + +#[embassy_executor::task] +async fn slave_service(mut i2c: I2cSlave<'static, Async>) { + loop { + let mut buf: [u8; BUFLEN] = [0xAA; BUFLEN]; + + for (i, e) in buf.iter_mut().enumerate() { + *e = i as u8; + } + + // Go through the target trait — note `<_ as TargetI2c>` + // disambiguates between the 7-bit and 10-bit trait impls. The + // address mode is checked at runtime against the address the slave + // was constructed with; a mismatch returns `ErrorKind::Other`. + let req: Request = match TargetI2c::::listen(&mut i2c).await { + Ok(r) => r, + Err(e) => { + info!("listen error: {:?}", defmt::Debug2Format(&e)); + continue; + } + }; + + match req { + Request::Stop(addr) => { + // A probe (address-only transaction terminated by STOP) + // surfaces here. The inherent API folds this into + // `Command::Probe`. + info!("Stop @ 0x{:02X} (probe)", addr); + } + Request::RepeatedStart(prev_addr) => { + // Surfaced when a previous respond_to_* observed a Sr. + info!("RepeatedStart from prev @ 0x{:02X}", prev_addr); + } + Request::Read(addr) => { + info!("Read @ 0x{:02X}", addr); + loop { + use embedded_mcu_hal::i2c::target::ReadStatus; + match TargetI2c::::respond_to_read(&mut i2c, &buf).await { + Ok(ReadStatus::Complete(n)) => { + info!("Read complete with {} bytes", n); + break; + } + Ok(ReadStatus::EarlyStop(n)) => { + info!("Read terminated by controller after {} bytes", n); + break; + } + Ok(ReadStatus::NeedMore(n)) => { + info!("Read NeedMore: sent {} bytes so far, more requested", n); + // Loop and supply more bytes. In a real + // application you would prepare the next chunk + // here; for the demo we just resend `buf`. + } + Ok(_) => { + // ReadStatus is `#[non_exhaustive]`; future + // variants are gracefully ignored. + info!("Read: unknown status variant"); + break; + } + Err(e) => { + info!("respond_to_read error: {:?}", defmt::Debug2Format(&e)); + break; + } + } + } + } + Request::Write(addr) => { + info!("Write @ 0x{:02X}", addr); + loop { + use embedded_mcu_hal::i2c::target::WriteStatus; + match TargetI2c::::respond_to_write(&mut i2c, &mut buf).await { + Ok(WriteStatus::Stopped(n)) => { + info!("Write stopped after {} bytes", n); + break; + } + Ok(WriteStatus::Restarted(n)) => { + info!("Write restarted after {} bytes — next listen will surface RepeatedStart", n); + // Do NOT call recover() here: a Restarted is a + // healthy continuation of an in-flight master + // transaction (Sr + ADDR+R/W is queued on the + // wire). recover() would NAK the new address + // byte and drop the queued RepeatedStart edge, + // causing the master to see a spurious NACK. + // Reserve recover() for wedged/cancelled + // transfers — e.g. after dropping a + // respond_to_* future mid-transaction. + break; + } + Ok(WriteStatus::BufferFull(n)) => { + info!("Write BufferFull after {} bytes — supplying more buffer space", n); + // Loop and continue draining. + } + Ok(_) => { + // WriteStatus is `#[non_exhaustive]`; future + // variants are gracefully ignored. + info!("Write: unknown status variant"); + break; + } + Err(e) => { + info!("respond_to_write error: {:?}", defmt::Debug2Format(&e)); + break; + } + } + } + } + // GeneralCall / SmbusAlert are not produced by this peripheral + // in v1; the catch-all covers any future variants. + _ => { + info!("unhandled request variant"); + } + } + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("i2cs target-trait example - I2c::new"); + let i2c = I2cSlave::new_async(p.FLEXCOMM2, p.PIO0_18, p.PIO0_17, Irqs, SLAVE_ADDR.unwrap(), p.DMA0_CH4).unwrap(); + + spawner.spawn(slave_service(i2c).unwrap()); +} diff --git a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs new file mode 100644 index 00000000..5bdb6e53 --- /dev/null +++ b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs @@ -0,0 +1,131 @@ +//! Blocking I2C slave example using the `embedded_mcu_hal::i2c::target` trait. +//! +//! This is the trait-based counterpart to `i2c-slave.rs`. It drives the +//! same FC2 hardware in the same listen → respond → re-listen loop, but it +//! goes through the `embedded_mcu_hal::i2c::target::blocking::I2c` trait +//! instead of the inherent `I2cSlave` methods. +//! +//! Tested against the same Raspberry Pi 5 master rig as the existing +//! `i2c-slave.rs` example +//! (https://github.com/jerrysxie/pi5-i2c-test). + +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_imxrt::i2c::Blocking; +use embassy_imxrt::i2c::slave::{Address, I2cSlave}; +use embassy_imxrt_examples as _; +use embedded_mcu_hal::i2c::SevenBitAddress; +use embedded_mcu_hal::i2c::target::Request; +use embedded_mcu_hal::i2c::target::blocking::I2c as TargetI2c; +use panic_probe as _; + +const SLAVE_ADDR: Option
= Address::new(0x20); +const BUFLEN: usize = 8; + +#[embassy_executor::task] +async fn slave_service(mut i2c: I2cSlave<'static, Blocking>) { + loop { + let mut buf: [u8; BUFLEN] = [0xAA; BUFLEN]; + + for (i, e) in buf.iter_mut().enumerate() { + *e = i as u8; + } + + let req: Request = match TargetI2c::::listen(&mut i2c) { + Ok(r) => r, + Err(e) => { + info!("listen error: {:?}", defmt::Debug2Format(&e)); + continue; + } + }; + + match req { + Request::Stop(addr) => { + info!("Stop @ 0x{:02X} (probe)", addr); + } + Request::RepeatedStart(prev_addr) => { + info!("RepeatedStart from prev @ 0x{:02X}", prev_addr); + } + Request::Read(addr) => { + info!("Read @ 0x{:02X}", addr); + loop { + use embedded_mcu_hal::i2c::target::ReadStatus; + match TargetI2c::::respond_to_read(&mut i2c, &buf) { + Ok(ReadStatus::Complete(n)) => { + info!("Read complete with {} bytes", n); + break; + } + Ok(ReadStatus::EarlyStop(n)) => { + info!("Read terminated by controller after {} bytes", n); + break; + } + Ok(ReadStatus::NeedMore(n)) => { + info!("Read NeedMore: sent {} bytes so far, more requested", n); + } + Ok(_) => { + info!("Read: unknown status variant"); + break; + } + Err(e) => { + info!("respond_to_read error: {:?}", defmt::Debug2Format(&e)); + break; + } + } + } + } + Request::Write(addr) => { + info!("Write @ 0x{:02X}", addr); + loop { + use embedded_mcu_hal::i2c::target::WriteStatus; + match TargetI2c::::respond_to_write(&mut i2c, &mut buf) { + Ok(WriteStatus::Stopped(n)) => { + info!("Write stopped after {} bytes", n); + break; + } + Ok(WriteStatus::Restarted(n)) => { + info!("Write restarted after {} bytes", n); + // Do NOT call recover() here: a Restarted is a + // healthy continuation of an in-flight master + // transaction (Sr + ADDR+R/W is queued on the + // wire). recover() would NAK the new address + // byte and drop the queued RepeatedStart edge, + // causing the master to see a spurious NACK. + // Reserve recover() for wedged/cancelled + // transfers — e.g. after dropping a + // respond_to_* future mid-transaction. + break; + } + Ok(WriteStatus::BufferFull(n)) => { + info!("Write BufferFull after {} bytes — supplying more buffer space", n); + } + Ok(_) => { + info!("Write: unknown status variant"); + break; + } + Err(e) => { + info!("respond_to_write error: {:?}", defmt::Debug2Format(&e)); + break; + } + } + } + } + _ => { + info!("unhandled request variant"); + } + } + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!("i2cs target-trait example (blocking) - I2c::new"); + let i2c = I2cSlave::new_blocking(p.FLEXCOMM2, p.PIO0_18, p.PIO0_17, SLAVE_ADDR.unwrap()).unwrap(); + + spawner.spawn(slave_service(i2c).unwrap()); +} diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index d0542223..6048ee22 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -1,4 +1,28 @@ //! Implements I2C function support over flexcomm + gpios +//! +//! # Target trait impls +//! +//! Besides the inherent [`I2cSlave::listen`] / [`I2cSlave::respond_to_write`] / +//! [`I2cSlave::respond_to_read`] API that returns [`Command`] / [`Response`], +//! this driver also implements the [`embedded_mcu_hal::i2c::target`] traits: +//! +//! - [`embedded_mcu_hal::i2c::target::blocking::I2c`] for `I2cSlave<'_, Blocking>` +//! - [`embedded_mcu_hal::i2c::target::asynch::I2c`] for `I2cSlave<'_, Async>` +//! +//! Both flavours are implemented twice — once for `SevenBitAddress` and once +//! for `TenBitAddress`. The implementation that matches the address mode +//! configured at construction time succeeds; the mismatched implementation +//! returns [`Error::UnsupportedConfiguration`], which maps to +//! [`ErrorKind::Other`](embedded_mcu_hal::i2c::target::ErrorKind::Other). +//! +//! The trait API surfaces extra information that the inherent API collapses: +//! the matched address payload on every event, restart and stop edges as +//! distinct [`Request`](embedded_mcu_hal::i2c::target::Request) variants, and +//! a `recover` method to re-baseline the peripheral after a cancelled +//! `respond_*` future. Probes (zero-byte writes) surface as +//! [`Request::Stop`](embedded_mcu_hal::i2c::target::Request::Stop) directly, +//! with no preceding `Write` event — the strict interpretation that a probe +//! is just an address-then-STOP on the wire. use core::future::{Future, poll_fn}; use core::marker::PhantomData; @@ -7,10 +31,12 @@ use core::task::Poll; use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; +use embedded_mcu_hal::i2c::target as mcu_target; +use embedded_mcu_hal::i2c::{SevenBitAddress, TenBitAddress}; use super::{ - Async, Blocking, Error, Info, Instance, InterruptHandler, Mode, REMEDIATON_SLAVE_NAK, Result, SclPin, SdaPin, - SlaveDma, TEN_BIT_PREFIX, TransferError, + Async, Blocking, Error, Info, Instance, InterruptHandler, Mode, REMEDIATON_NONE, REMEDIATON_SLAVE_NAK, Result, + SclPin, SdaPin, SlaveDma, TEN_BIT_PREFIX, TransferError, }; use crate::flexcomm::FlexcommRef; use crate::interrupt::typelevel::Interrupt; @@ -129,6 +155,78 @@ pub enum Response { Pending(usize), } +/// Bus event observed by the internal `listen` helpers. +/// +/// Richer than the public [`Command`] — it preserves the matched address +/// so the trait-impl wrapper can populate +/// [`Request`](embedded_mcu_hal::i2c::target::Request)'s `A` payload, and it +/// distinguishes the probe (address-only, terminated by STOP) from a +/// data-bearing write/read. +#[derive(Copy, Clone, Debug)] +enum InternalEvent { + /// 0-byte write transaction (address-only ACK then STOP). + Probe { addr: AddressKind }, + /// Controller is about to read from this target. + Read { addr: AddressKind }, + /// Controller is about to write to this target. + Write { addr: AddressKind }, +} + +/// Concrete address (7- or 10-bit) that just matched on the bus. +#[derive(Copy, Clone, Debug)] +enum AddressKind { + Seven(u8), + Ten(u16), +} + +/// Reason a `respond_to_*` inner helper returned. +/// +/// Richer than the public [`Response`] — distinguishes Stop vs Restart vs +/// BufferFull vs NeedMore vs EarlyStop, all observable from the existing +/// `slvdesel` / `slvstate` / buffer-exhaustion signals. +#[derive(Copy, Clone, Debug)] +enum InternalTermination { + /// Controller issued a `STOP`. `n` = bytes moved. + Stopped(usize), + /// Controller issued a repeated `START`. `n` = bytes moved. + Restarted(usize), + /// Supplied buffer was exhausted, controller still expects more + /// (writing more bytes / clocking more reads). `n` = buffer length. + Continued(usize), + /// Read-only: buffer fully consumed at exactly the moment the + /// controller NACK+STOPped. `n` = buffer length. + ReadComplete(usize), + /// Read-only: controller terminated with STOP (or Sr) before the + /// buffer ran out. `n` = bytes moved. + ReadEarlyStop(usize), +} + +/// Edge event queued by a previous `respond_to_*` for the next `listen` call. +/// +/// Only used by the trait impls — the inherent `listen` ignores this field. +/// Today only `RepeatedStart` is ever queued; a `STOP` terminator is folded +/// into the `WriteStatus::Stopped` / `ReadStatus::Complete` payload of the +/// preceding `respond_to_*` call rather than synthesised as a separate +/// `listen` event. +#[derive(Copy, Clone, Debug)] +enum EdgeKind { + /// Surface a [`Request::RepeatedStart`](mcu_target::Request::RepeatedStart) + /// at the next `listen`, then resume hardware polling for the next + /// addressed event. + RepeatedStart, +} + +/// Per-driver pending-edge bookkeeping for the target trait impls. +/// +/// Tracks the address that matched on the most recent addressed event and +/// any restart/stop edge that should be surfaced before the next hardware +/// poll. Populated only by the target trait helpers. +#[derive(Copy, Clone, Debug, Default)] +struct PendingEdge { + last_addr: Option, + pending: Option, +} + /// use `FCn` as I2C Slave controller pub struct I2cSlave<'a, M: Mode> { info: Info, @@ -136,6 +234,14 @@ pub struct I2cSlave<'a, M: Mode> { _phantom: PhantomData, dma_ch: Option>, ten_bit_info: Option, + /// Address configured at construction time. Used by the target trait + /// impls to populate the `A` payload on + /// [`Request`](mcu_target::Request) variants and to enforce the + /// runtime address-mode check. + configured_address: Address, + /// Pending edge bookkeeping for the target trait impls. The inherent + /// API does not touch this field. + pending: core::cell::Cell, } impl<'a, M: Mode> I2cSlave<'a, M> { @@ -207,6 +313,8 @@ impl<'a, M: Mode> I2cSlave<'a, M> { _phantom: PhantomData, dma_ch, ten_bit_info, + configured_address: address, + pending: core::cell::Cell::new(PendingEdge::default()), }) } } @@ -739,3 +847,812 @@ impl Drop for NakGuard { }) } } + +// --------------------------------------------------------------------------- +// embedded-mcu-hal::i2c::target trait impls +// --------------------------------------------------------------------------- +// +// The traits are layered on top of the existing inherent API. They preserve +// the inherent API verbatim and add: +// +// - A `recover` method to re-baseline the peripheral after a cancelled or +// wedged transfer. +// - A `Request` shape that carries the matched address and surfaces +// `RepeatedStart` / `Stop` as their own events (synthesised from the +// peripheral's `slvdesel` / `slvstate` bits). +// - `ReadStatus` / `WriteStatus` enums that distinguish the three possible +// terminators per direction. +// +// The address-mode generic is handled by writing two separate trait impl +// blocks per flavour (one for `SevenBitAddress`, one for `TenBitAddress`). +// At runtime the impl checks the address type configured at construction +// time and returns `ErrorKind::Other` from `listen` if the wrong impl was +// invoked. + +impl AddressKind { + fn as_u8(self) -> Option { + match self { + Self::Seven(a) => Some(a), + Self::Ten(_) => None, + } + } + + fn as_u16(self) -> u16 { + match self { + Self::Seven(a) => a as u16, + Self::Ten(a) => a, + } + } +} + +impl I2cSlave<'_, M> { + /// Snapshot of the configured address as an [`AddressKind`]. + fn matched_address(&self) -> AddressKind { + match self.configured_address { + Address::SevenBit(a) => AddressKind::Seven(a), + Address::TenBit(a) => AddressKind::Ten(a), + } + } + + /// Update the pending-edge bookkeeping with the address that just + /// matched on the bus. Called from every successful inner-listen. + fn note_match(&self, addr: AddressKind) { + let mut p = self.pending.get(); + p.last_addr = Some(addr); + self.pending.set(p); + } + + /// Queue an edge to be surfaced at the next trait-level `listen`. + fn queue_edge(&self, edge: EdgeKind) { + let mut p = self.pending.get(); + p.pending = Some(edge); + self.pending.set(p); + } + + /// Drain any queued edge, returning the matching + /// [`mcu_target::Request`]. Returns `None` if there is no queued edge. + fn drain_edge(&self, addr_for: fn(AddressKind) -> A) -> Option> + where + A: embedded_hal_1::i2c::AddressMode, + { + let mut p = self.pending.get(); + let edge = p.pending.take()?; + let addr = p.last_addr.unwrap_or_else(|| self.matched_address()); + self.pending.set(p); + Some(match edge { + EdgeKind::RepeatedStart => mcu_target::Request::RepeatedStart(addr_for(addr)), + }) + } + + /// Address-mode runtime guard. Returns `Err(UnsupportedConfiguration)` + /// when the trait impl being driven does not match the configured + /// address type — for example, calling the + /// `I2c` impl on a slave constructed with a 10-bit + /// address. + fn require_address_kind(&self, want_ten_bit: bool) -> Result<()> { + let is_ten_bit = self.ten_bit_info.is_some(); + if is_ten_bit == want_ten_bit { + Ok(()) + } else { + Err(Error::UnsupportedConfiguration) + } + } +} + +/// Convert a [`InternalTermination`] coming out of a write helper to the +/// corresponding [`mcu_target::WriteStatus`], queuing any edge that needs +/// to be surfaced at the next `listen`. +fn write_termination_to_status(slave: &I2cSlave<'_, M>, term: InternalTermination) -> mcu_target::WriteStatus { + match term { + InternalTermination::Stopped(n) => { + // Stopped is implicit in the WriteStatus itself; no edge to queue. + mcu_target::WriteStatus::Stopped(n) + } + InternalTermination::Restarted(n) => { + slave.queue_edge(EdgeKind::RepeatedStart); + mcu_target::WriteStatus::Restarted(n) + } + InternalTermination::Continued(n) => mcu_target::WriteStatus::BufferFull(n), + // Read-only terminators should never come out of a write helper. + InternalTermination::ReadComplete(n) | InternalTermination::ReadEarlyStop(n) => { + mcu_target::WriteStatus::Stopped(n) + } + } +} + +/// Convert a [`InternalTermination`] coming out of a read helper to the +/// corresponding [`mcu_target::ReadStatus`], queuing any edge that needs to +/// be surfaced at the next `listen`. +fn read_termination_to_status(slave: &I2cSlave<'_, M>, term: InternalTermination) -> mcu_target::ReadStatus { + match term { + InternalTermination::ReadComplete(n) => mcu_target::ReadStatus::Complete(n), + InternalTermination::ReadEarlyStop(n) => mcu_target::ReadStatus::EarlyStop(n), + InternalTermination::Continued(n) => mcu_target::ReadStatus::NeedMore(n), + InternalTermination::Restarted(n) => { + slave.queue_edge(EdgeKind::RepeatedStart); + mcu_target::ReadStatus::EarlyStop(n) + } + InternalTermination::Stopped(n) => mcu_target::ReadStatus::EarlyStop(n), + } +} + +// ----- Internal helpers: Blocking -------------------------------------------- + +impl I2cSlave<'_, Blocking> { + /// Trait-internal `listen` for the blocking flavour. Mirrors the + /// inherent `listen` but tracks the matched address. + fn listen_internal(&self) -> Result { + let i2c = self.info.regs; + + self.block_until_addressed()?; + + // Block until we know it is read or write + self.poll()?; + + if let Some(ten_bit_address) = self.ten_bit_info { + // For 10 bit address, the first byte received is the second byte of the address + if i2c.slvdat().read().data().bits() == ten_bit_address.second_byte { + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + self.poll()?; + } else { + // If the second byte of the 10 bit address is not received, then nack the address. + i2c.slvctl().write(|w| w.slvnack().nack()); + let addr = self.matched_address(); + self.note_match(addr); + return Ok(InternalEvent::Probe { addr }); + } + + // Check slave is still selected, master has not sent a stop + if i2c.stat().read().slvsel().is_selected() { + // Check for a restart + if i2c.stat().read().slvstate().is_slave_address() { + // Check if first byte of 10 bit address is received again with read bit set + if i2c.slvdat().read().data().bits() == ten_bit_address.first_byte | 1 { + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + self.poll()?; + } else { + // If the first byte of the 10 bit address is not received again, then nack the address. + i2c.slvctl().write(|w| w.slvnack().nack()); + let addr = self.matched_address(); + self.note_match(addr); + return Ok(InternalEvent::Probe { addr }); + } + // Check slave is ready for transmit + if !i2c.stat().read().slvstate().is_slave_transmit() { + return Err(TransferError::WriteFail.into()); + } + } else { + // Check slave is ready to receive + if !i2c.stat().read().slvstate().is_slave_receive() { + return Err(TransferError::ReadFail.into()); + } + } + } + } + + // We are already deselected, so it must be an 0 byte write transaction + if i2c.stat().read().slvdesel().is_deselected() { + // Clear the deselected bit + i2c.stat().write(|w| w.slvdesel().deselected()); + let addr = self.matched_address(); + self.note_match(addr); + return Ok(InternalEvent::Probe { addr }); + } + + let state = i2c.stat().read().slvstate().variant(); + let addr = self.matched_address(); + self.note_match(addr); + match state { + Some(Slvstate::SlaveReceive) => Ok(InternalEvent::Write { addr }), + Some(Slvstate::SlaveTransmit) => Ok(InternalEvent::Read { addr }), + _ => Err(TransferError::OtherBusError.into()), + } + } + + /// Trait-internal `respond_to_write` for the blocking flavour. + fn respond_to_write_internal(&self, buf: &mut [u8]) -> Result { + let i2c = self.info.regs; + let mut xfer_count: usize = 0; + let buf_len = buf.len(); + + for b in buf { + //poll until something happens + self.poll()?; + + let stat = i2c.stat().read(); + // if master send stop, we are done + if stat.slvdesel().is_deselected() { + break; + } + // if master send a restart, we are done + if stat.slvstate().is_slave_address() { + break; + } + + if !stat.slvstate().is_slave_receive() { + return Err(TransferError::ReadFail.into()); + } + + // Now we can safely read the next byte + *b = i2c.slvdat().read().data().bits(); + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + xfer_count += 1; + } + + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + // Clear the deselect bit + i2c.stat().write(|w| w.slvdesel().deselected()); + return Ok(InternalTermination::Stopped(xfer_count)); + } else if stat.slvstate().is_slave_address() { + // Handle restart + return Ok(InternalTermination::Restarted(xfer_count)); + } else if stat.slvstate().is_slave_receive() { + // Buffer exhausted; master is still sending — treat as + // "continue with more buffer" (BufferFull at trait level, + // Pending at the inherent-API level). + debug_assert_eq!(xfer_count, buf_len); + return Ok(InternalTermination::Continued(xfer_count)); + } + + // We should not get here + Err(TransferError::ReadFail.into()) + } + + /// Trait-internal `respond_to_read` for the blocking flavour. + fn respond_to_read_internal(&self, buf: &[u8]) -> Result { + let i2c = self.info.regs; + let mut xfer_count: usize = 0; + let buf_len = buf.len(); + + for b in buf { + // Block until something happens + self.poll()?; + + let stat = i2c.stat().read(); + // if master send nack or stop, we are done + if stat.slvdesel().is_deselected() { + break; + } + // if master send restart, we are done + if stat.slvstate().is_slave_address() { + break; + } + + // Verify that we are ready for write + if !stat.slvstate().is_slave_transmit() { + return Err(TransferError::WriteFail.into()); + } + + i2c.slvdat().write(|w| + // SAFETY: unsafe only here due to use of bits() + unsafe{w.data().bits(*b)}); + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + xfer_count += 1; + } + + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + // clear the deselect bit + i2c.stat().write(|w| w.slvdesel().deselected()); + // Distinguish Complete (buffer exactly exhausted) vs EarlyStop + // (buffer had bytes left when controller stopped). + if xfer_count == buf_len { + return Ok(InternalTermination::ReadComplete(xfer_count)); + } else { + return Ok(InternalTermination::ReadEarlyStop(xfer_count)); + } + } else if stat.slvstate().is_slave_address() { + // Handle restart after read + return Ok(InternalTermination::Restarted(xfer_count)); + } else if stat.slvstate().is_slave_transmit() { + // Buffer exhausted but master still expects bytes — NeedMore. + debug_assert_eq!(xfer_count, buf_len); + return Ok(InternalTermination::Continued(xfer_count)); + } + + // We should not get here + Err(TransferError::WriteFail.into()) + } + + /// Bring the slave back to a known-clean state after a wedged or + /// cancelled transfer. + /// + /// See [`mcu_target::blocking::I2c::recover`] for the contract. + /// + /// Performs: NAK any pending byte, disable DMA arming, clear the + /// deselect latch, drop any pending remediation, and drop any queued + /// edge bookkeeping. The configured address(es) and `slven` bit are + /// preserved. + pub fn recover(&self) -> Result<()> { + let i2c = self.info.regs; + critical_section::with(|_| { + // Drop any latent DMA arming. + i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); + // Disable interrupts we may have left enabled. + i2c.intenclr() + .write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit()); + + // NAK an in-flight pending byte if any; this is harmless when + // we are not in the pending state. + if i2c.stat().read().slvpending().is_pending() { + i2c.slvctl().write(|w| w.slvnack().set_bit()); + } + + // Clear the deselect latch if set. + if i2c.stat().read().slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + } + + // Drop any pending remediation. + self.info.remediation.store(REMEDIATON_NONE, Ordering::Release); + }); + + // Drop our software bookkeeping. + self.pending.set(PendingEdge::default()); + Ok(()) + } +} + +// ----- Internal helpers: Async ----------------------------------------------- + +impl I2cSlave<'_, Async> { + /// Trait-internal `listen` for the async flavour. Mirrors the inherent + /// async `listen` but tracks the matched address. + async fn listen_internal(&mut self) -> Result { + let i2c = self.info.regs; + + // Disable DMA + i2c.slvctl().write(|w| w.slvdma().disabled()); + + // Check whether we already have a matched address and just waiting + // for software ack/nack + if !i2c.stat().read().slvpending().is_pending() { + self.poll_sw_action().await; + } + + if i2c.stat().read().slvstate().is_slave_address() { + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + } + + // Poll for HW to transitioning from addressed to receive/transmit + self.poll_sw_action().await; + + if let Some(ten_bit_address) = self.ten_bit_info { + // For 10 bit address, the first byte received is the second byte of the address + if i2c.slvdat().read().data().bits() == ten_bit_address.second_byte { + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + self.poll_sw_action().await; + } else { + // If the second byte of the 10 bit address is not received, then nack the address. + i2c.slvctl().write(|w| w.slvnack().nack()); + let addr = self.matched_address(); + self.note_match(addr); + return Ok(InternalEvent::Probe { addr }); + } + + // Check slave is still selected, master has not sent a stop + if i2c.stat().read().slvsel().is_selected() { + // Check for a restart + if i2c.stat().read().slvstate().is_slave_address() { + // Check if first byte of 10 bit address is received again with read bit set + if i2c.slvdat().read().data().bits() == ten_bit_address.first_byte | 1 { + i2c.slvctl().write(|w| w.slvcontinue().continue_()); + self.poll_sw_action().await; + } else { + // If the first byte of the 10 bit address is not received again, then nack the address. + i2c.slvctl().write(|w| w.slvnack().nack()); + let addr = self.matched_address(); + self.note_match(addr); + return Ok(InternalEvent::Probe { addr }); + } + // Check slave is ready for transmit + if !i2c.stat().read().slvstate().is_slave_transmit() { + return Err(TransferError::WriteFail.into()); + } + } else { + // Check slave is ready to receive + if !i2c.stat().read().slvstate().is_slave_receive() { + return Err(TransferError::ReadFail.into()); + } + } + } + } + + // We are deselected, so it must be an 0 byte write transaction + if i2c.stat().read().slvdesel().is_deselected() { + // Clear the deselected bit + i2c.stat().write(|w| w.slvdesel().deselected()); + let addr = self.matched_address(); + self.note_match(addr); + return Ok(InternalEvent::Probe { addr }); + } + + let state = i2c.stat().read().slvstate().variant(); + let addr = self.matched_address(); + self.note_match(addr); + match state { + Some(Slvstate::SlaveReceive) => Ok(InternalEvent::Write { addr }), + Some(Slvstate::SlaveTransmit) => Ok(InternalEvent::Read { addr }), + _ => Err(TransferError::OtherBusError.into()), + } + } + + /// Trait-internal `respond_to_write` for the async flavour. + async fn respond_to_write_internal(&mut self, buf: &mut [u8]) -> Result { + let i2c = self.info.regs; + let buf_len = buf.len(); + + // Verify that we are ready for write + let stat = i2c.stat().read(); + if !stat.slvstate().is_slave_receive() { + // 0 byte write + if stat.slvdesel().is_deselected() { + return Ok(InternalTermination::Stopped(0)); + } + return Err(TransferError::ReadFail.into()); + } + + let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; + + // Enable DMA + i2c.slvctl().write(|w| w.slvdma().enabled()); + + // Enable interrupt + i2c.intenset() + .write(|w| w.slvpendingen().enabled().slvdeselen().enabled()); + + let options = dma::transfer::TransferOptions::default(); + let _transfer = dma_channel.read_from_peripheral(i2c.slvdat().as_ptr() as *mut u8, buf, options); + + let nak_guard = NakGuard { info: self.info }; + let _dma_guard = OnDrop::new(|| { + i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); + }); + + poll_fn(|cx| { + let i2c = self.info.regs; + + self.info.waker.register(cx.waker()); + dma_channel.get_waker().register(cx.waker()); + + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + return Poll::Ready(()); + } + if stat.slvpending().is_pending() { + return Poll::Ready(()); + } + if !dma_channel.is_active() && stat.slvstate().is_slave_receive() { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + + nak_guard.defuse(); + let xfer_count = self.abort_dma(buf_len)?; + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + return Ok(InternalTermination::Stopped(xfer_count)); + } else if stat.slvstate().is_slave_address() { + return Ok(InternalTermination::Restarted(xfer_count)); + } else if stat.slvstate().is_slave_receive() { + return Ok(InternalTermination::Continued(xfer_count)); + } + + Err(TransferError::ReadFail.into()) + } + + /// Trait-internal `respond_to_read` for the async flavour. + async fn respond_to_read_internal(&mut self, buf: &[u8]) -> Result { + let i2c = self.info.regs; + let buf_len = buf.len(); + + if !i2c.stat().read().slvstate().is_slave_transmit() { + return Err(TransferError::WriteFail.into()); + } + + i2c.slvctl().write(|w| w.slvdma().enabled()); + + i2c.intenset() + .write(|w| w.slvpendingen().enabled().slvdeselen().enabled()); + + let options = dma::transfer::TransferOptions::default(); + let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; + + let _transfer = dma_channel.write_to_peripheral(buf, i2c.slvdat().as_ptr() as *mut u8, options); + + let _dma_guard = OnDrop::new(|| { + i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); + }); + + poll_fn(|cx| { + let i2c = self.info.regs; + + self.info.waker.register(cx.waker()); + dma_channel.get_waker().register(cx.waker()); + + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + return Poll::Ready(()); + } + if stat.slvpending().is_pending() { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + + let xfer_count = self.abort_dma(buf_len)?; + let stat = i2c.stat().read(); + + if stat.slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + if xfer_count == buf_len { + return Ok(InternalTermination::ReadComplete(xfer_count)); + } else { + return Ok(InternalTermination::ReadEarlyStop(xfer_count)); + } + } else if stat.slvpending().is_pending() || stat.slvstate().is_slave_address() { + // Restart (or NACK-then-next-address) — surface as Restart so + // the next `listen` reports the RepeatedStart edge. + return Ok(InternalTermination::Restarted(xfer_count)); + } + + Err(TransferError::WriteFail.into()) + } + + /// Bring the slave back to a known-clean state after a wedged or + /// cancelled transfer. + /// + /// See [`mcu_target::asynch::I2c::recover`] for the contract. + /// + /// Aborts any in-flight DMA, NAKs any pending byte, disables DMA + /// arming, clears the deselect latch, drops any pending remediation, + /// and clears any queued edge bookkeeping. The configured address(es) + /// and `slven` bit are preserved. The async flavour additionally awaits + /// completion of the remediation pump so the returned future is the + /// canonical join point. + pub async fn recover(&mut self) -> Result<()> { + let i2c = self.info.regs; + if let Some(dma) = self.dma_ch.as_ref() + && dma.is_active() + { + dma.abort(); + } + + critical_section::with(|_| { + i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); + i2c.intenclr() + .write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit()); + + if i2c.stat().read().slvpending().is_pending() { + i2c.slvctl().write(|w| w.slvnack().set_bit()); + } + if i2c.stat().read().slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + } + self.info.remediation.store(REMEDIATON_NONE, Ordering::Release); + }); + + self.pending.set(PendingEdge::default()); + Ok(()) + } +} + +// ----- Error / ErrorType impls ----------------------------------------------- + +impl mcu_target::Error for Error { + fn kind(&self) -> mcu_target::ErrorKind { + match *self { + Self::UnsupportedConfiguration => mcu_target::ErrorKind::Other, + Self::Transfer(e) => match e { + TransferError::Timeout => mcu_target::ErrorKind::Other, + TransferError::ReadFail | TransferError::WriteFail => { + mcu_target::ErrorKind::NoAcknowledge(mcu_target::NoAcknowledgeSource::Data) + } + TransferError::AddressNack => { + mcu_target::ErrorKind::NoAcknowledge(mcu_target::NoAcknowledgeSource::Address) + } + TransferError::ArbitrationLoss => mcu_target::ErrorKind::ArbitrationLoss, + TransferError::StartStopError | TransferError::OtherBusError => mcu_target::ErrorKind::Bus, + }, + } + } +} + +impl mcu_target::ErrorType for I2cSlave<'_, M> { + type Error = Error; +} + +// ----- Blocking I2c impl ------------------------------------ + +impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { + fn recover(&mut self) -> Result<()> { + I2cSlave::::recover(self) + } + + fn listen(&mut self) -> Result> { + self.require_address_kind(false)?; + + // Drain any queued edge first. + if let Some(req) = self.drain_edge::(|a| a.as_u8().unwrap_or(0)) { + return Ok(req); + } + + let ev = self.listen_internal()?; + Ok(match ev { + InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u8().unwrap_or(0)), + InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u8().unwrap_or(0)), + InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u8().unwrap_or(0)), + }) + } + + fn respond_to_read(&mut self, buf: &[u8]) -> Result { + let term = self.respond_to_read_internal(buf)?; + Ok(read_termination_to_status(self, term)) + } + + fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + let term = self.respond_to_write_internal(buf)?; + Ok(write_termination_to_status(self, term)) + } +} + +// ----- Blocking I2c impl -------------------------------------- + +impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { + fn recover(&mut self) -> Result<()> { + I2cSlave::::recover(self) + } + + fn listen(&mut self) -> Result> { + self.require_address_kind(true)?; + + if let Some(req) = self.drain_edge::(|a| a.as_u16()) { + return Ok(req); + } + + let ev = self.listen_internal()?; + Ok(match ev { + InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u16()), + InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u16()), + InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u16()), + }) + } + + fn respond_to_read(&mut self, buf: &[u8]) -> Result { + let term = self.respond_to_read_internal(buf)?; + Ok(read_termination_to_status(self, term)) + } + + fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + let term = self.respond_to_write_internal(buf)?; + Ok(write_termination_to_status(self, term)) + } +} + +// ----- Async I2c impl --------------------------------------- + +impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { + async fn recover(&mut self) -> Result<()> { + I2cSlave::::recover(self).await + } + + async fn listen(&mut self) -> Result> { + self.require_address_kind(false)?; + + if let Some(req) = self.drain_edge::(|a| a.as_u8().unwrap_or(0)) { + return Ok(req); + } + + let ev = self.listen_internal().await?; + Ok(match ev { + InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u8().unwrap_or(0)), + InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u8().unwrap_or(0)), + InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u8().unwrap_or(0)), + }) + } + + async fn respond_to_read(&mut self, buf: &[u8]) -> Result { + let term = self.respond_to_read_internal(buf).await?; + Ok(read_termination_to_status(self, term)) + } + + async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + let term = self.respond_to_write_internal(buf).await?; + Ok(write_termination_to_status(self, term)) + } +} + +// ----- Async I2c impl ----------------------------------------- + +impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { + async fn recover(&mut self) -> Result<()> { + I2cSlave::::recover(self).await + } + + async fn listen(&mut self) -> Result> { + self.require_address_kind(true)?; + + if let Some(req) = self.drain_edge::(|a| a.as_u16()) { + return Ok(req); + } + + let ev = self.listen_internal().await?; + Ok(match ev { + InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u16()), + InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u16()), + InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u16()), + }) + } + + async fn respond_to_read(&mut self, buf: &[u8]) -> Result { + let term = self.respond_to_read_internal(buf).await?; + Ok(read_termination_to_status(self, term)) + } + + async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + let term = self.respond_to_write_internal(buf).await?; + Ok(write_termination_to_status(self, term)) + } +} + +#[cfg(test)] +mod tests { + use embedded_mcu_hal::i2c::target::{ErrorKind, NoAcknowledgeSource}; + + use super::*; + + #[test] + fn error_kind_mapping_covers_every_transfer_variant() { + // Every TransferError variant must map to a deterministic ErrorKind. + let cases = [ + (TransferError::Timeout, ErrorKind::Other), + ( + TransferError::ReadFail, + ErrorKind::NoAcknowledge(NoAcknowledgeSource::Data), + ), + ( + TransferError::WriteFail, + ErrorKind::NoAcknowledge(NoAcknowledgeSource::Data), + ), + ( + TransferError::AddressNack, + ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address), + ), + (TransferError::ArbitrationLoss, ErrorKind::ArbitrationLoss), + (TransferError::StartStopError, ErrorKind::Bus), + (TransferError::OtherBusError, ErrorKind::Bus), + ]; + + for (transfer, expected) in cases { + let err = Error::Transfer(transfer); + assert_eq!(::kind(&err), expected); + } + } + + #[test] + fn error_kind_mapping_unsupported_configuration_is_other() { + let err = Error::UnsupportedConfiguration; + assert_eq!( + ::kind(&err), + ErrorKind::Other, + ); + } + + #[test] + fn address_kind_round_trips() { + let seven = AddressKind::Seven(0x42); + assert_eq!(seven.as_u8(), Some(0x42)); + assert_eq!(seven.as_u16(), 0x42); + + let ten = AddressKind::Ten(0x1AB); + assert_eq!(ten.as_u8(), None); + assert_eq!(ten.as_u16(), 0x1AB); + } +} From c35cab92c010ca2d4cbca04dcc4d9914077f869e Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 1 Jun 2026 11:30:29 -0700 Subject: [PATCH 02/27] i2c::slave: surface NeedMore when async DMA read drains mid-transaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The async respond_to_read_internal poll_fn was missing the symmetric counterpart of the write path's '!dma_channel.is_active() && is_slave_receive()' wake condition. When DMA finished pushing the supplied buffer but the master kept clocking SCL for more bytes, the hardware did not raise slvpending (it just floated SDA so the master clocked 0xFF from the bus pull-ups), and the future slept until something external aborted the transaction. Symptom from a 16-byte controller read against an 8-byte slave buffer: the host received [0..7, 0xFF*8] after a ~9 s timeout instead of [0..7, 0..7], and the slave never logged NeedMore. Add the missing wake arm to poll_fn and the matching post-await branch that returns InternalTermination::Continued(buf_len) → ReadStatus::NeedMore so the caller can refill the buffer and resume the read. --- src/i2c/slave.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index 6048ee22..60294843 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -1382,6 +1382,14 @@ impl I2cSlave<'_, Async> { if stat.slvpending().is_pending() { return Poll::Ready(()); } + // DMA drained but the master still expects more bytes. + // Without this guard the future sleeps forever because the + // hardware does not raise slvpending once DMA is armed and + // running dry — SDA just floats and the master clocks 0xFF + // until something else aborts the transaction. + if !dma_channel.is_active() && stat.slvstate().is_slave_transmit() { + return Poll::Ready(()); + } Poll::Pending }) @@ -1401,6 +1409,13 @@ impl I2cSlave<'_, Async> { // Restart (or NACK-then-next-address) — surface as Restart so // the next `listen` reports the RepeatedStart edge. return Ok(InternalTermination::Restarted(xfer_count)); + } else if stat.slvstate().is_slave_transmit() { + // DMA drained while the master is still clocking — surface + // NeedMore so the caller can supply another buffer. The + // caller's next `respond_to_read` call re-arms DMA without + // dropping the transaction. + debug_assert_eq!(xfer_count, buf_len); + return Ok(InternalTermination::Continued(xfer_count)); } Err(TransferError::WriteFail.into()) From 78abc97ba6d259e40269ea7f296b2c707db9c9a9 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 1 Jun 2026 13:00:07 -0700 Subject: [PATCH 03/27] cargo +nightly fmt --- src/i2c/slave.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index 60294843..d388f18a 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -31,8 +31,7 @@ use core::task::Poll; use embassy_hal_internal::Peri; use embassy_hal_internal::drop::OnDrop; -use embedded_mcu_hal::i2c::target as mcu_target; -use embedded_mcu_hal::i2c::{SevenBitAddress, TenBitAddress}; +use embedded_mcu_hal::i2c::{SevenBitAddress, TenBitAddress, target as mcu_target}; use super::{ Async, Blocking, Error, Info, Instance, InterruptHandler, Mode, REMEDIATON_NONE, REMEDIATON_SLAVE_NAK, Result, From 4c91837208ed80146545f8629a85e6648a09ddb7 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 1 Jun 2026 13:01:59 -0700 Subject: [PATCH 04/27] bump MSRV to match what fixed requires --- .github/workflows/check.yml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 7a7e3118..25890879 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -177,21 +177,7 @@ jobs: strategy: fail-fast: false matrix: - msrv: ["1.90"] # We're relying on namespaced-features, which - # was released in 1.60 - # - # We also depend on `fixed' which requires rust - # 1.71 - # - # Additionally, we depend on embedded-hal-async - # which requires 1.75 - # - # embassy-time requires 1.79 due to - # collapse_debuginfo - # - # embassy upstream switched to rust 1.85 - # - # unsigned_is_multiple_of requires 1.90, else we get clippy warnings + msrv: ["1.93"] name: ubuntu / ${{ matrix.msrv }} steps: From fa8246b7d78914fc0226a0e6b1b1c6e4319100e3 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 1 Jun 2026 13:11:09 -0700 Subject: [PATCH 05/27] supply-chain: re-vet after embedded-mcu-hal 0.3.0 bump and dep updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs cargo vet to a clean 'Vetting Succeeded (111 fully audited, 1 partially audited, 9 exempted)' state on top of the embedded-mcu-hal 0.2.0 -> 0.3.0 bump and the dependency churn since the last vet pass. Audits added: - embedded-mcu-hal 0.2.0 -> 0.3.0 — first-party delta covering the new i2c::target trait module (which I authored) and the new smbus::bus module. Trait-only additions, no new unsafe, no new transitive deps. - defmt-rtt 1.0.0 -> 1.1.0 — single new public query function in_blocking_mode() (relaxed atomic read of the RTT up-channel flags). Pulled in transitively via the ariel-os import set. - typenum 1.20.0 -> 1.20.1 — macro hygiene fix using \\::tarr![...]\ inside the recursive expansion; doc_root URL bump. Trust entries added (publishers cargo-vet already covers via other audit sets): - dtolnay → syn, thiserror, thiserror-impl - joshlf → zerocopy, zerocopy-derive (already on the OpenDevicePartnership trust list) - cuviper → autocfg - KodrAus → log - rust-lang-owner → cc (matches existing libc trust entry) Imports added: - ariel-os — gives us the upstream defmt-rtt 1.0.0 -> 1.1.0 audit instead of the previously-unavailable 1.0.0 -> 1.2.0 jump. Exemption changes: - chrono bumped 0.4.40 -> 0.4.44 (430-line diff, same djc publisher). - generator bumped 0.8.8 -> 0.8.9 (loom dev-dep, safe-to-run only). - cc bumped 1.2.59 -> 1.2.63 (build-time only, GitHub Actions publisher). - New shlex 2.0.1 exemption (transitive build-only dep of cc). - Other previously-exempted crates (az, bitfield, cordyceps, embassy-embedded-hal, embassy-futures, embassy-hal-internal, embassy-sync, embassy-time-driver, embedded-hal*, embedded-io-async, embedded-storage*, find-msvc-tools, fixed, hash32, heapless, ident_case, mimxrt600-fcb, portable-atomic, syn, thiserror*, tracing*, typenum 1.18.0, valuable) were pruned automatically by cargo vet because they are now covered by trust entries, imports, or audits. --- supply-chain/audits.toml | 66 +++++ supply-chain/config.toml | 132 +--------- supply-chain/imports.lock | 538 ++++++++++++++++++++++++++++++++++---- 3 files changed, 562 insertions(+), 174 deletions(-) diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml index 6c39cbfd..01f499ca 100644 --- a/supply-chain/audits.toml +++ b/supply-chain/audits.toml @@ -7,6 +7,12 @@ criteria = "safe-to-deploy" version = "1.0.0" notes = "defmt-rtt is used for all our logging purposes. Version 1.0.0 merely stabilizes what was already available previously." +[[audits.defmt-rtt]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 1.1.0" +notes = "Adds a single public query function in_blocking_mode() that performs a relaxed atomic read of the SEGGER RTT up-channel flags bitfield. No new unsafe blocks, no new dependencies, no behavioural changes to existing APIs." + [[audits.embassy-embedded-hal]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -75,6 +81,12 @@ who = "Jerry Xie " criteria = "safe-to-deploy" version = "0.2.0" +[[audits.embedded-mcu-hal]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +delta = "0.2.0 -> 0.3.0" +notes = "Adds two new trait-only modules: i2c::target (blocking + async target-side I2C traits, mirroring the embedded-hal controller-side shape with Request/ReadStatus/WriteStatus/Error/ErrorKind) and smbus::bus (async SMBus host trait with packet-error-checking). Both consist of trait definitions, plain enums, doc comments, and small blanket impl &mut T forwarding blocks. Minor adjustments to existing nvram/time/watchdog/lib.rs modules (no new behaviour). No new unsafe blocks. No new transitive dependencies. I authored the i2c::target module and reviewed the rest of this delta firsthand before publishing 0.3.0." + [[audits.mimxrt600-fcb]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -145,18 +157,42 @@ criteria = "safe-to-run" delta = "0.1.34 -> 0.1.36" notes = "Fix record_all panic. Switch to unconditional no_std (removed stdlib.rs shim). Improved code generation at trace points. Extracted sync Mutex wrapper. Trusted publisher (hds from Tokio team)." +[[audits.typenum]] +who = "Felipe Balbi " +criteria = "safe-to-deploy" +delta = "1.20.0 -> 1.20.1" +notes = "Macro hygiene fix: the recursive expansion of `tarr!` now uses `$crate::tarr![...]` instead of a naked `tarr![...]`, so the caller no longer needs `tarr` in scope. Plus a doc_root URL bump for the new version. No runtime behaviour changes." + [[trusted.aho-corasick]] criteria = "safe-to-run" user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-03-28" end = "2027-04-07" +[[trusted.autocfg]] +criteria = "safe-to-deploy" +user-id = 539 # Josh Stone (cuviper) +start = "2019-05-22" +end = "2027-06-01" + +[[trusted.cc]] +criteria = "safe-to-run" +user-id = 55123 # rust-lang-owner +start = "2022-10-29" +end = "2027-06-01" + [[trusted.libc]] criteria = "safe-to-run" user-id = 55123 # rust-lang-owner start = "2019-01-01" end = "2027-04-07" +[[trusted.log]] +criteria = "safe-to-deploy" +user-id = 3204 # Ashley Mannix (KodrAus) +start = "2019-07-10" +end = "2027-06-01" + [[trusted.loom]] criteria = "safe-to-run" user-id = 6741 # Alice Ryhl (Darksonn) @@ -175,6 +211,24 @@ user-id = 189 # Andrew Gallant (BurntSushi) start = "2019-03-30" end = "2027-04-07" +[[trusted.syn]] +criteria = "safe-to-deploy" +user-id = 3618 # David Tolnay (dtolnay) +start = "2019-03-01" +end = "2027-06-01" + +[[trusted.thiserror]] +criteria = "safe-to-deploy" +user-id = 3618 # David Tolnay (dtolnay) +start = "2019-10-09" +end = "2027-06-01" + +[[trusted.thiserror-impl]] +criteria = "safe-to-deploy" +user-id = 3618 # David Tolnay (dtolnay) +start = "2019-10-09" +end = "2027-06-01" + [[trusted.thread_local]] criteria = "safe-to-run" user-id = 2915 # Amanieu d'Antras (Amanieu) @@ -186,3 +240,15 @@ criteria = "safe-to-run" user-id = 64539 # Kenny Kerr (kennykerr) start = "2024-01-01" end = "2027-04-07" + +[[trusted.zerocopy]] +criteria = "safe-to-deploy" +user-id = 7178 # Joshua Liebow-Feeser (joshlf) +start = "2019-02-28" +end = "2027-06-01" + +[[trusted.zerocopy-derive]] +criteria = "safe-to-deploy" +user-id = 7178 # Joshua Liebow-Feeser (joshlf) +start = "2019-02-28" +end = "2027-06-01" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 8f92a0c0..7a73aed1 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -7,6 +7,9 @@ version = "0.10" [imports.OpenDevicePartnership] url = "https://raw.githubusercontent.com/OpenDevicePartnership/rust-crate-audits/main/audits.toml" +[imports.ariel-os] +url = "https://raw.githubusercontent.com/ariel-os/ariel-os/main/supply-chain/audits.toml" + [imports.bytecode-alliance] url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml" @@ -19,28 +22,15 @@ url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml" [policy.embassy-imxrt] audit-as-crates-io = false -[[exemptions.az]] -version = "1.2.1" -criteria = "safe-to-deploy" - -[[exemptions.bitfield]] -version = "0.15.0" -criteria = "safe-to-deploy" - [[exemptions.cc]] -version = "1.2.59" +version = "1.2.63" criteria = "safe-to-run" notes = "Build dependency published via GitHub Actions by rust-lang/cc-rs team. Trusted publisher but uses GitHub team publishing which cargo-vet can't verify via trust entries." [[exemptions.chrono]] -version = "0.4.40" +version = "0.4.44" criteria = "safe-to-deploy" -[[exemptions.cordyceps]] -version = "0.3.4" -criteria = "safe-to-run" -notes = "Intrusive data structures used by embassy-executor. Published by hawkw (Eliza Weisman). 7963 lines - too large for diff audit." - [[exemptions.darling]] version = "0.20.11" criteria = "safe-to-run" @@ -53,10 +43,6 @@ criteria = "safe-to-run" version = "0.20.11" criteria = "safe-to-run" -[[exemptions.embassy-embedded-hal]] -version = "0.5.0" -criteria = "safe-to-deploy" - [[exemptions.embassy-executor]] version = "0.10.0" criteria = "safe-to-run" @@ -66,116 +52,16 @@ notes = "Embassy executor with major API changes. Published by lulf. 5609 lines version = "0.7.0" criteria = "safe-to-run" -[[exemptions.embassy-futures]] -version = "0.1.2" -criteria = "safe-to-deploy" - -[[exemptions.embassy-hal-internal]] -version = "0.3.0" -criteria = "safe-to-deploy" - -[[exemptions.embassy-sync]] -version = "0.7.2" -criteria = "safe-to-deploy" - -[[exemptions.embassy-time-driver]] -version = "0.2.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal-async]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-hal-nb]] -version = "1.0.0" -criteria = "safe-to-deploy" - -[[exemptions.embedded-io-async]] -version = "0.6.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-storage]] -version = "0.3.1" -criteria = "safe-to-deploy" - -[[exemptions.embedded-storage-async]] -version = "0.4.1" -criteria = "safe-to-deploy" - -[[exemptions.find-msvc-tools]] -version = "0.1.9" -criteria = "safe-to-run" -notes = "Build dependency of cc crate. Published via GitHub Actions by rust-lang/cc-rs team." - -[[exemptions.fixed]] -version = "1.29.0" -criteria = "safe-to-deploy" - [[exemptions.generator]] -version = "0.8.8" +version = "0.8.9" criteria = "safe-to-run" notes = "Generator library used by loom. Published by Xudong-Huang. 5916 lines." -[[exemptions.hash32]] -version = "0.3.1" -criteria = "safe-to-deploy" - -[[exemptions.heapless]] -version = "0.8.0" -criteria = "safe-to-deploy" - -[[exemptions.heapless]] -version = "0.9.2" -criteria = "safe-to-deploy" -notes = "Major version bump from 0.8. Published by sgued. 12030+/4526- lines - too large for diff audit." - -[[exemptions.ident_case]] -version = "1.0.1" -criteria = "safe-to-run" - -[[exemptions.mimxrt600-fcb]] -version = "0.2.1" -criteria = "safe-to-deploy" - [[exemptions.paste]] version = "1.0.15" criteria = "safe-to-deploy" -[[exemptions.portable-atomic]] -version = "1.11.0" -criteria = "safe-to-run" - -[[exemptions.syn]] -version = "2.0.100" -criteria = "safe-to-deploy" - -[[exemptions.thiserror]] -version = "2.0.12" -criteria = "safe-to-deploy" - -[[exemptions.thiserror-impl]] -version = "2.0.12" -criteria = "safe-to-deploy" - -[[exemptions.tracing]] -version = "0.1.44" -criteria = "safe-to-run" -notes = "Published by hds (Tokio team). 61566+ lines diff - too large for diff audit. Well-known and widely used crate." - -[[exemptions.tracing-subscriber]] -version = "0.3.23" -criteria = "safe-to-run" -notes = "Published by hds (Tokio team). 30664 lines - too large for diff audit. Well-known and widely used crate." - -[[exemptions.typenum]] -version = "1.18.0" -criteria = "safe-to-deploy" - -[[exemptions.valuable]] -version = "0.1.1" +[[exemptions.shlex]] +version = "2.0.1" criteria = "safe-to-run" -notes = "Published by taiki-e. Used by tracing-core. 5229 lines." +notes = "Shell-style word splitter pulled in transitively by the cc build dependency. Build-time only — not compiled into target firmware." diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 1abcecba..4109d72a 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -8,12 +8,26 @@ user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" +[[publisher.autocfg]] +version = "1.5.1" +when = "2026-05-22" +user-id = 539 +user-login = "cuviper" +user-name = "Josh Stone" + [[publisher.libc]] -version = "0.2.184" -when = "2026-04-01" +version = "0.2.186" +when = "2026-04-23" user-id = 55123 user-login = "rust-lang-owner" +[[publisher.log]] +version = "0.4.30" +when = "2026-05-25" +user-id = 3204 +user-login = "KodrAus" +user-name = "Ashley Mannix" + [[publisher.loom]] version = "0.7.2" when = "2024-04-23" @@ -22,8 +36,8 @@ user-login = "Darksonn" user-name = "Alice Ryhl" [[publisher.memchr]] -version = "2.8.0" -when = "2026-02-06" +version = "2.8.1" +when = "2026-05-27" user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" @@ -35,6 +49,27 @@ user-id = 189 user-login = "BurntSushi" user-name = "Andrew Gallant" +[[publisher.syn]] +version = "2.0.117" +when = "2026-02-20" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + +[[publisher.thiserror]] +version = "2.0.18" +when = "2026-01-18" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + +[[publisher.thiserror-impl]] +version = "2.0.18" +when = "2026-01-18" +user-id = 3618 +user-login = "dtolnay" +user-name = "David Tolnay" + [[publisher.thread_local]] version = "1.1.9" when = "2025-06-12" @@ -49,6 +84,34 @@ user-id = 64539 user-login = "kennykerr" user-name = "Kenny Kerr" +[[publisher.zerocopy]] +version = "0.8.50" +when = "2026-05-29" +user-id = 7178 +user-login = "joshlf" +user-name = "Joshua Liebow-Feeser" + +[[publisher.zerocopy-derive]] +version = "0.8.50" +when = "2026-05-29" +user-id = 7178 +user-login = "joshlf" +user-name = "Joshua Liebow-Feeser" + +[[audits.OpenDevicePartnership.audits.az]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.2.1" +notes = "No unsafe code. no_std library with only safe numeric cast traits. Build script probes for track_caller via rustc in OUT_DIR only. No network, no ambient capabilities. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.az]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.2.1 -> 1.3.0" +notes = "Delta audit: adds StrictCast/StrictAs/StrictCastFrom traits (future replacement for UnwrappedCast); UnwrappedCast now delegates to StrictCast; removes build.rs (track_caller now stable); adds nightly-float feature for f16/f128 behind feature gate; edition 2018->2024, MSRV 1.85.0; min_value()/max_value() -> MIN/MAX; cfg_attr(track_caller) simplified to #[track_caller]. No unsafe code, no new dependencies, no ambient capabilities. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.bare-metal]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -61,6 +124,48 @@ criteria = "safe-to-deploy" version = "0.13.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.bitfield]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.13.2 -> 0.15.0" +notes = "Delta audit: BitRange/Bit traits split into read-only and mutable variants (BitRangeMut/BitMut); added mask constant generation; clippy fixes; MSRV bump. No unsafe, no build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bytemuck]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.22.0 -> 1.23.2" +notes = "Delta 1.22.0->1.23.2: new ZeroableInOption impls for function pointer types (sound, uses guaranteed niche optimization), core::error::Error impls behind feature flag, safe derive helper module. No new unsafe blocks, no build script, no I/O. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.bytemuck]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.23.2 -> 1.25.0" +notes = "Delta 1.23.2->1.25.0: New unsafe impl AnyBitPattern for [MaybeUninit; N] (sound — any bit pattern valid for MaybeUninit arrays of AnyBitPattern types). Portable SIMD impls split by rustversion for LaneCount removal (same safety invariants). AVX512 feature gates simplified from nightly_stdsimd to stable avx512_simd. stdcall fn ptr ZeroableInOption impls correctly restricted to x86 only. New optional rustversion dep for compile-time version detection. No build script, no I/O, no new powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cfg-if]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.0 -> 1.0.3" +notes = "Delta 1.0.0->1.0.3: formatting/readability refactor of macro identifiers, removed compiler_builtins dep, updated CI. No unsafe, no build script, no imports. Pure macro_rules crate. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cfg-if]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.3 -> 1.0.4" +notes = "Delta 1.0.3->1.0.4: macro refactored from two top-level rules to one using optional else syntax, fragment specifiers changed from :meta to :tt to support cfg(true)/cfg(false), internal helper renamed from @__identity to @__temp_group with added comment. MSRV 1.32 declared. No unsafe, no build script, no proc macros, no imports. Pure no_std macro_rules crate. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.cordyceps]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.4" +notes = "Intrusive data structures crate (no_std). ~115 unsafe blocks, all necessary for intrusive linked list/queue/stack ops. Correct patterns: addr_of_mut, proper atomic orderings, Vyukov MPSC algorithm. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.cortex-m]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -85,36 +190,237 @@ criteria = "safe-to-deploy" version = "1.2.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.crunchy]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.3 -> 0.2.4" +notes = "Tiny diff to use newer core/std features via build.rs env var for path separator; no safety impact. Assisted-by: copilot-cli:GPT-5.3-Codex" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.defmt]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.100" +notes = "Compatibility shim: no_std crate that re-exports defmt 1.x items for 0.3 API compatibility. No unsafe code, no build script, no powerful imports, no logic - pure pub-use re-exports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.defmt]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "1.0.1" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.defmt]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.1 -> 1.1.0" +notes = "Delta audit: No new unsafe code. build.rs adds xtensa-esp32s2-none-elf to no_cas list. New Format impls for core::fmt::Error and Wrapping are safe. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.defmt-macros]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "1.0.1" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.defmt-macros]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.1 -> 1.1.0" +notes = "Proc-macro crate for defmt logging. Delta adds #[defmt(transparent)] and #[defmt(bound(...))] derive attributes, fixes && to || bug in env filter parse, simplifies log expansion. No new unsafe code. Generated unsafe (acquire/release guards) unchanged from prior version. Build script only reads DEFMT_LOG env var. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.defmt-parser]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "1.0.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.document-features]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.11 -> 0.2.12" +notes = "Delta audit: litrs dep bump 0.4 to 1.0, MSRV bump, trivial code. No unsafe, no new imports. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.either]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.15.0 -> 1.16.0" +notes = "Delta audit: No unsafe code changes (existing Pin unsafe unchanged, only macro rename). No build script, no proc macros, no powerful imports. Adds safe combinators and map_both! macro. No new deps. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embassy-embedded-hal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.5.0" +notes = "No unsafe, no build script, no proc macros. no_std shared bus/flash partition utilities for embedded-hal traits. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embassy-futures]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.2" +notes = "no_std future combinators. All unsafe is pin-projection and no-op RawWaker - reviewed and sound. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embassy-hal-internal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.0" +notes = "no_std HAL internals. Unsafe in atomic ring buffer (sound SPSC), peripheral singletons, cortex-m interrupt priority. Build script emits cfg flags only. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embassy-sync]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.8.0" +notes = "no_std async sync primitives. Substantial unsafe for UnsafeCell-based interiors and Send/Sync impls -- all reviewed and sound, guarded by RawMutex/critical_section. Build script only reads TARGET env var. No proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.embassy-time]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.5.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/tps6699x/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embassy-time-driver]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "no_std driver trait for embassy-time. Minimal unsafe for extern Rust FFI calls (sound via links key). Empty build.rs. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-crc-macros]] +who = "Matteo Tullo " +criteria = "safe-to-deploy" +version = "1.0.0" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.embedded-hal]] who = "Felipe Balbi " criteria = "safe-to-deploy" version = "0.2.7" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embedded-hal]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.2.7 -> 1.0.0" +notes = "Pure no_std trait crate. Complete API redesign for 1.0: removed nb-based traits, CAN module, all unsafe code. Only defines traits/enums/types for digital, I2C, SPI, PWM, delay. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-hal-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "no_std async HAL trait definitions. No unsafe in library. Build script only runs rustc --version. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-hal-nb]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "no_std trait-only crate. No unsafe, no build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-io-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.6.1" +notes = "No unsafe. Build script only detects nightly via rustc --version. Pure async trait definitions for embedded I/O. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-storage]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "Pure no_std storage abstraction traits. deny(unsafe_code), no build script, no dependencies, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-storage-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.4.1" +notes = "Pure no_std async trait definitions for NOR flash storage. No unsafe code, no build script, no powerful imports. Only dependency is embedded-storage. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.find-msvc-tools]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.9" +notes = "Full audit of find-msvc-tools 0.1.9, extracted from cc-rs. Zero dependencies. Unsafe code is COM/FFI wrappers for Windows registry, VS Setup Configuration COM interfaces, and kernel32 dynamic loading - all sound with proper Drop impls and error checking. Compile-time type check guards transmute_copy for dynamic GetMachineTypeAttributes. No build script, no proc macros, no network access. Only accesses filesystem/registry/COM to locate MSVC tooling as advertised. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.fixed]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.29.0" +notes = "no_std fixed-point number library. Unsafe limited to: bytemuck Pod/Zeroable impls on repr(transparent) types, NonZero::new_unchecked after proven-nonzero guards, unreachable_unchecked in exhaustive remainder logic. Build script probes compiler features in OUT_DIR. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.fixed]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.29.0 -> 1.31.0" +notes = "Delta audit 1.29.0->1.31.0. Unwrapped renamed to Strict; new unchecked_neg/shl/shr thin wrappers over std integer unchecked ops (sound); defmt feature delegates to Display; compat-readonly-static disables atomic caching. No new powerful imports, no build.rs logic changes. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.futures-core]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.3.31 -> 0.3.32" +notes = "Delta audit: version bump, Cargo.toml cosmetic reorder, AtomicWaker correctness fix (fetch_and to swap for cleaner state transition), ready! macro doc update. No new unsafe code, no build script, no proc macros, no powerful imports. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.futures-sink]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.3.31 -> 0.3.32" +notes = "No source code changes, only Cargo.toml metadata reordering. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.half]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +delta = "2.5.0 -> 2.6.0" +notes = "Big change seems to be a change in the repo URL, but both the old and new URL resolve to the same place so it looks like the author is still in control." +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.half]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.6.0 -> 2.7.1" +notes = "Delta audit: migrated unsafe transmutes to zerocopy::transmute! (safety improvement), added loongarch64 LSX SIMD (behind nightly feature), added Signed/Weight trait impls, added zerocopy derives on f16/bf16. New loongarch64 unsafe follows same sound MaybeUninit+intrinsics pattern. Net reduction in unsafe code. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.hash32]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "no_std 32-bit hashing (FNV, MurmurHash3). ~10 unsafe blocks in murmur3.rs for MaybeUninit buffer handling - all sound. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heapless]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.9.2" +notes = "no_std fixed-capacity data structures. Extensive unsafe for MaybeUninit buffers, lock-free queues (Vyukov MPMC, SPSC), Treiber stack pools with ABA prevention (CAS tagged pointers + ARM LLSC). All Send/Sync bounds verified correct. Build script probes for ARM LLSC. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heapless]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.9.2 -> 0.9.3" +notes = "Delta 0.9.2->0.9.3: Fixes unsoundness in Deque/HistoryBuf/IndexMap::clear when Drop panics (Guard pattern). Adds CString conversions (into_string, into_bytes, from_bytes_truncating_at_nul), Deque::pop_front_if/pop_back_if, const Vec::from_array. New transmute_copy in as_len_type is sound (closed type enum). Pointer-cast cleanups. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.ident_case]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "1.0.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.itertools]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -122,24 +428,72 @@ version = "0.11.0" notes = "Used as a dependency for embassy-imxrt." aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.litrs]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.4.1 -> 0.4.2" +notes = "Delta 0.4.1->0.4.2: Bug fixes for non-ASCII byte string escapes, removes CR LF normalization to align with spec, fixes error span for out-of-range Unicode escapes. No unsafe code, no build script, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.litrs]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.4.2 -> 1.0.0" +notes = "Delta 0.4.2->1.0.0: Adds C string literal support (CStringLit), marks Literal enum #[non_exhaustive], removes proc-macro2 from default features, bumps MSRV to 1.56, changes num_hashes from u32 to u8 with 256 max, changes Buffer::Cow/ByteCow for String to owned types (String/Vec) instead of Cow<'static,...>. Refactors escape internals: replaces Escapee trait with Unescape enum and EscapeContainer trait, moves span offset responsibility to callers. Adds nul byte checking for C strings. No unsafe code, no build script, no proc macros, no powerful imports. Pure parsing library. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.matchers]] who = "Jerry Xie " criteria = "safe-to-deploy" delta = "0.1.0 -> 0.2.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.mimxrt600-fcb]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "Pure no_std data-definition crate for MIMXRT600 flash config blocks. No unsafe, no build script. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.nu-ansi-term]] who = "Jerry Xie " criteria = "safe-to-deploy" delta = "0.46.0 -> 0.50.1" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.num_enum]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.5 -> 0.7.6" +notes = "Version bump with test infrastructure updates. No unsafe code, no build script, no powerful imports. Purely additive test changes." +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.num_enum_derive]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.7.5 -> 0.7.6" +notes = "Minor update adding byte literal support for enum discriminants. No unsafe code, no build script, no powerful imports." +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.once_cell]] who = "Felipe Balbi " criteria = "safe-to-run" delta = "1.19.0 -> 1.20.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.portable-atomic]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +version = "1.11.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.portable-atomic]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.11.1 -> 1.13.1" +notes = "Delta audit: three minor releases (1.12.0, 1.13.0, 1.13.1). New unsafe-assume-privileged feature for multi-core interrupt-disable fallback. SeqCst sequential consistency fix in lock-based fallback. x86_64 VMOVDQA detection simplified to AVX check. Arm support expanded to all 32-bit targets. PowerPC64 asm stabilized. Major interrupt module refactoring to RAII guard pattern. All unsafe changes reviewed: sound. No new ambient capabilities. Assisted-by: copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.proc-macro-error-attr2]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -152,6 +506,12 @@ criteria = "safe-to-deploy" version = "2.0.1" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.rand_core]] +who = "Billy Price " +criteria = "safe-to-deploy" +delta = "0.6.4 -> 0.9.5" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.rustc_version]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -170,12 +530,38 @@ criteria = "safe-to-deploy" version = "0.7.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.smbus-pec]] +who = "Matteo Tullo " +criteria = "safe-to-deploy" +version = "1.0.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.stable_deref_trait]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.2.0 -> 1.2.1" +notes = """ +Delta audit: metadata-only Cargo.toml changes (license format, explicit lib section). +src/lib.rs adds new unsafe impl StableDeref for Cow types - all sound since Cow::deref() +returns a stable heap or borrowed address across moves. Arc import and impls now correctly +gated behind target_has_atomic=ptr. No build script, no proc macros, no powerful imports. +Assisted-by: copilot-chat:claude-opus-4.6 +""" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.static_cell]] who = "Robert Zieba " criteria = "safe-to-deploy" version = "2.1.1" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.tracing]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.1.41 -> 0.1.44" +notes = "Delta audit: one new unsafe (FieldName::as_str using str::from_utf8_unchecked) — sound because private field is only constructed from const fn new() which processes &str input by removing r#, preserving UTF-8 validity; compile-time assert guards N. No build script (build=false). No proc macros. No powerful imports (fs/net/process). Key changes: valueset! macro split into valueset!/valueset_all! to fix record_all panic (#3432); FieldName added for raw identifier support; stdlib.rs removed in favor of #![no_std] + extern crate std; lifetime elision cleanup; doc fixes; MSRV 1.63→1.65. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.tracing-attributes]] who = "Jerry Xie " criteria = "safe-to-deploy" @@ -194,6 +580,41 @@ criteria = "safe-to-deploy" delta = "0.1.3 -> 0.2.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.tracing-subscriber]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.3.20 -> 0.3.23" +notes = "Delta audit of tracing-subscriber 0.3.20->0.3.23. Changes: (1) no_std support refactoring - imports moved from std to alloc/core, unconditional #![no_std] with extern crate std gated on feature; (2) ANSI sanitization made configurable via with_ansi_sanitization(bool), defaulting to true (secure default preserved); (3) Escape renamed to EscapeGuard with conditional sanitize flag; (4) downcast_raw calls wrapped in unsafe blocks to match trait signature change in tracing-core 0.1.35 - all call sites forward to existing safe implementations; (5) Registry enter/exit simplified - removes clone_span on enter and try_close on exit for performance; (6) Layered now propagates on_register_dispatch to inner+layer; (7) Doc fixes and test improvements. No new filesystem/network/process access. No build script. No proc macros. Unsafe changes are mechanical wrapping of existing downcast_raw calls. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.typenum]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.18.0" +notes = "Pure no_std type-level numbers crate. forbid(unsafe_code) -- zero unsafe anywhere. Build script only writes generated test code to OUT_DIR. No proc macros, no FFI, no network/filesystem/process access in library. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.typenum]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.18.0 -> 1.20.0" +notes = "Delta audit. build.rs removed (improvement). Adds tuple indexing, Binary fmt, ToInt for isize/i128/u128, 2^N-1 constants. No unsafe code (forbid). No build script, no proc macros, no powerful imports. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.unicode-ident]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.0.18 -> 1.0.24" +notes = "Delta: Unicode 16->17 table data update only. No unsafe, no build.rs, no new deps. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.valuable]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.1" +notes = "No unsafe code; build.rs only sets target atomic cfg via env; no fs/net/process capability use observed; behavior matches value-inspection purpose. Assisted-by: copilot-cli:GPT-5.3-Codex" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.vcell]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -218,6 +639,18 @@ criteria = "safe-to-run" version = "0.61.2" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" +[[audits.ariel-os.audits.defmt-rtt]] +who = "ROMemories <152802150+ROMemories@users.noreply.github.com>" +criteria = "safe-to-deploy" +delta = "1.1.0 -> 1.2.0" +notes = "Many `usize` types have been changed to `u32` for increased portability, and an arithmetic bug has been fixed and a test added for it." + +[[audits.bytecode-alliance.audits.cfg-if]] +who = "Alex Crichton " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "I am the author of this crate." + [[audits.bytecode-alliance.audits.embedded-io]] who = "Alex Crichton " criteria = "safe-to-deploy" @@ -251,12 +684,6 @@ who = "Pat Hickey " criteria = "safe-to-deploy" delta = "0.3.28 -> 0.3.31" -[[audits.bytecode-alliance.audits.log]] -who = "Alex Crichton " -criteria = "safe-to-deploy" -delta = "0.4.22 -> 0.4.27" -notes = "Lots of minor updates to macros and such, nothing touching `unsafe`" - [[audits.bytecode-alliance.audits.matchers]] who = "Pat Hickey " criteria = "safe-to-deploy" @@ -295,12 +722,10 @@ This is a standard adapter between the `log` ecosystem and the `tracing` ecosystem. There's one `unsafe` block in this crate and it's well-scoped. """ -[[audits.google.audits.autocfg]] -who = "Manish Goregaokar " +[[audits.bytecode-alliance.audits.tracing-subscriber]] +who = "Pat Hickey " criteria = "safe-to-deploy" -version = "1.4.0" -notes = "Contains no unsafe" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" +version = "0.3.17" [[audits.google.audits.bitflags]] who = "Lukasz Anforowicz " @@ -386,12 +811,6 @@ version = "1.5.0" notes = "Unsafe review in https://crrev.com/c/5838022" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.cfg-if]] -who = "George Burgess IV " -criteria = "safe-to-deploy" -version = "1.0.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - [[audits.google.audits.either]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -417,7 +836,7 @@ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_p who = "Lukasz Anforowicz " criteria = "safe-to-deploy" delta = "1.14.0 -> 1.15.0" -notes = 'The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = "std")]`.' +notes = "The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = \"std\")]`." aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.lazy_static]] @@ -433,18 +852,6 @@ delta = "1.4.0 -> 1.5.0" notes = "Unsafe review notes: https://crrev.com/c/5650836" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" -[[audits.google.audits.log]] -who = "danakj " -criteria = "safe-to-deploy" -version = "0.4.22" -notes = """ -Unsafe review in https://docs.google.com/document/d/1IXQbD1GhTRqNHIGxq6yy7qHqxeO4CwN5noMFXnqyDIM/edit?usp=sharing - -Unsafety is generally very well-documented, with one exception, which we -describe in the review doc. -""" -aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" - [[audits.google.audits.nb]] who = "George Burgess IV " criteria = "safe-to-deploy" @@ -494,8 +901,8 @@ who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.78" notes = """ -Grepped for "crypt", "cipher", "fs", "net" - there were no hits -(except for a benign "fs" hit in a doc comment) +Grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits +(except for a benign \"fs\" hit in a doc comment) Notes from the `unsafe` review can be found in https://crrev.com/c/5385745. """ @@ -607,8 +1014,8 @@ who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.35" notes = """ -Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits -(except for benign "net" hit in tests and "fs" hit in README.md) +Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits +(except for benign \"net\" hit in tests and \"fs\" hit in README.md) """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" @@ -685,7 +1092,7 @@ and there were no hits except for: * Using `unsafe` in a string: ``` - src/constfn.rs: "unsafe" => Qualifiers::Unsafe, + src/constfn.rs: \"unsafe\" => Qualifiers::Unsafe, ``` * Using `std::fs` in `build/build.rs` to write `${OUT_DIR}/version.expr` @@ -741,18 +1148,6 @@ criteria = "safe-to-run" version = "1.0.0" aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" -[[audits.google.audits.shlex]] -who = "George Burgess IV " -criteria = "safe-to-run" -version = "1.1.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - -[[audits.google.audits.shlex]] -who = "Daniel Verkamp " -criteria = "safe-to-run" -delta = "1.1.0 -> 1.3.0" -aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT" - [[audits.google.audits.smallvec]] who = "Manish Goregaokar " criteria = "safe-to-deploy" @@ -953,6 +1348,18 @@ Only functional change is to work around a bug in the negative_impls feature """ aggregated-from = "https://raw.githubusercontent.com/mozilla/cargo-vet/main/supply-chain/audits.toml" +[[audits.mozilla.audits.proc-macro2]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "1.0.94 -> 1.0.106" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + +[[audits.mozilla.audits.quote]] +who = "Jan-Erik Rediger " +criteria = "safe-to-deploy" +delta = "1.0.40 -> 1.0.45" +aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml" + [[audits.mozilla.audits.regex-automata]] who = "Benjamin VanderSloot " criteria = "safe-to-deploy" @@ -983,6 +1390,23 @@ criteria = "safe-to-deploy" delta = "0.10.0 -> 0.11.1" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.tracing]] +who = "Alex Franchuk " +criteria = "safe-to-deploy" +version = "0.1.37" +notes = """ +There's only one unsafe impl, and its purpose is to ensure correct behavior by +creating a non-Send marker type (it has nothing to do with soundness). All +dependencies make sense, and no side-effectful std functions are used. +""" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.1.37 -> 0.1.41" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.tracing-attributes]] who = "Alex Franchuk " criteria = "safe-to-deploy" @@ -1013,6 +1437,18 @@ criteria = "safe-to-deploy" delta = "0.1.30 -> 0.1.33" aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" +[[audits.mozilla.audits.tracing-subscriber]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.3.17 -> 0.3.19" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + +[[audits.mozilla.audits.tracing-subscriber]] +who = "Mark Hammond " +criteria = "safe-to-deploy" +delta = "0.3.19 -> 0.3.20" +aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml" + [[audits.mozilla.audits.windows-link]] who = "Mark Hammond " criteria = "safe-to-deploy" From 54d98fdda8bc27810a3f3d3274d97d1329e61539 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 1 Jun 2026 13:19:03 -0700 Subject: [PATCH 06/27] also update Cargo.lock --- examples/rt633/Cargo.lock | 57 ++++++++++++++----- .../rt685s-evk-performance-tracing/Cargo.lock | 57 ++++++++++++++----- 2 files changed, 88 insertions(+), 26 deletions(-) diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index 6cc37d14..e1ca4176 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -168,6 +168,15 @@ dependencies = [ "syn", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + [[package]] name = "defmt" version = "1.0.1" @@ -207,7 +216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" dependencies = [ "critical-section", - "defmt", + "defmt 1.0.1", ] [[package]] @@ -252,7 +261,7 @@ dependencies = [ "cordyceps", "cortex-m", "critical-section", - "defmt", + "defmt 1.0.1", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -290,7 +299,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt", + "defmt 1.0.1", "num-traits", ] @@ -311,7 +320,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.0.1", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -349,7 +358,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.0.1", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -364,7 +373,7 @@ checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.0.1", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -392,6 +401,12 @@ dependencies = [ "heapless", ] +[[package]] +name = "embedded-crc-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" + [[package]] name = "embedded-hal" version = "0.2.7" @@ -407,6 +422,9 @@ name = "embedded-hal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +dependencies = [ + "defmt 0.3.100", +] [[package]] name = "embedded-hal-async" @@ -414,6 +432,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" dependencies = [ + "defmt 0.3.100", "embedded-hal 1.0.0", ] @@ -459,12 +478,15 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" +checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ - "defmt", + "defmt 1.0.1", + "embedded-hal 1.0.0", + "embedded-hal-async", "num_enum", + "smbus-pec", ] [[package]] @@ -708,7 +730,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.0.1", "vcell", ] @@ -721,7 +743,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.0.1", "vcell", ] @@ -792,7 +814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt", + "defmt 1.0.1", ] [[package]] @@ -891,7 +913,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", - "defmt", + "defmt 1.0.1", "defmt-rtt", "embassy-executor", "embassy-futures", @@ -963,6 +985,15 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smbus-pec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" +dependencies = [ + "embedded-crc-macros", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/examples/rt685s-evk-performance-tracing/Cargo.lock b/examples/rt685s-evk-performance-tracing/Cargo.lock index f5ac0642..2d40880a 100644 --- a/examples/rt685s-evk-performance-tracing/Cargo.lock +++ b/examples/rt685s-evk-performance-tracing/Cargo.lock @@ -213,6 +213,15 @@ dependencies = [ "syn", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + [[package]] name = "defmt" version = "1.0.1" @@ -252,7 +261,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" dependencies = [ "critical-section", - "defmt", + "defmt 1.0.1", ] [[package]] @@ -297,7 +306,7 @@ dependencies = [ "cordyceps", "cortex-m", "critical-section", - "defmt", + "defmt 1.0.1", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -337,7 +346,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt", + "defmt 1.0.1", "num-traits", ] @@ -358,7 +367,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.0.1", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -395,7 +404,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", - "defmt", + "defmt 1.0.1", "defmt-rtt", "embassy-executor", "embassy-futures", @@ -422,7 +431,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.0.1", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -437,7 +446,7 @@ checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", - "defmt", + "defmt 1.0.1", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -465,6 +474,12 @@ dependencies = [ "heapless", ] +[[package]] +name = "embedded-crc-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1c75747a43b086df1a87fb2a889590bc0725e0abf54bba6d0c4bf7bd9e762c" + [[package]] name = "embedded-hal" version = "0.2.7" @@ -480,6 +495,9 @@ name = "embedded-hal" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" +dependencies = [ + "defmt 0.3.100", +] [[package]] name = "embedded-hal-async" @@ -487,6 +505,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" dependencies = [ + "defmt 0.3.100", "embedded-hal 1.0.0", ] @@ -532,12 +551,15 @@ dependencies = [ [[package]] name = "embedded-mcu-hal" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02b992c2b871b7fc616e4539258d92ea8b085e2f09cc0ad2862aa4d0e185ad1" +checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ - "defmt", + "defmt 1.0.1", + "embedded-hal 1.0.0", + "embedded-hal-async", "num_enum", + "smbus-pec", ] [[package]] @@ -803,7 +825,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.0.1", "vcell", ] @@ -816,7 +838,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt", + "defmt 1.0.1", "vcell", ] @@ -903,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt", + "defmt 1.0.1", ] [[package]] @@ -1089,6 +1111,15 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[package]] +name = "smbus-pec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" +dependencies = [ + "embedded-crc-macros", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" From 016d71e163381c680330d2bf0a6066aeb8369c57 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 06:03:36 -0700 Subject: [PATCH 07/27] i2c: slave: Call self.recover() directly --- src/i2c/slave.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index d388f18a..af9ade65 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -1487,7 +1487,7 @@ impl mcu_target::ErrorType for I2cSlave<'_, M> { impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { fn recover(&mut self) -> Result<()> { - I2cSlave::::recover(self) + self.recover() } fn listen(&mut self) -> Result> { @@ -1521,7 +1521,7 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { fn recover(&mut self) -> Result<()> { - I2cSlave::::recover(self) + self.recover() } fn listen(&mut self) -> Result> { @@ -1554,7 +1554,7 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { async fn recover(&mut self) -> Result<()> { - I2cSlave::::recover(self).await + self.recover().await } async fn listen(&mut self) -> Result> { @@ -1587,7 +1587,7 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { async fn recover(&mut self) -> Result<()> { - I2cSlave::::recover(self).await + self.recover(self).await } async fn listen(&mut self) -> Result> { From 746670c9da72d68df700603a728c56ba7de46d85 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 06:25:01 -0700 Subject: [PATCH 08/27] i2c::slave: enrich Command/Response and collapse trait dedup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the duplication kurtjd flagged on the PR by promoting the inherent Command and Response types into the rich shape and letting the trait impls map down from them. The state-machine bodies that used to be split between the inherent methods and four _internal helpers are now the single source of truth on the inherent path; the target trait impls are thin wrappers. Public API changes (both enums now #[non_exhaustive] so future variants do not break callers): Command::Probe { addr: Address } // was: Command::Probe Command::Read { addr: Address } // was: Command::Read Command::Write { addr: Address } // was: Command::Write Response::Stopped(usize) // STOP terminator Response::Restarted(usize) // Sr terminator Response::WriteContinued(usize) // write side: buffer drained, master still sending Response::ReadContinued(usize) // read side: buffer exhausted, master still clocking Response::ReadComplete(usize) // buffer exactly consumed at NACK+STOP Response::ReadEarlyStop(usize) // master STOP'd with bytes left in buffer Plus convenience helpers Response::bytes() and Response::is_terminal() so callers that just want the byte count and a yes/no don't have to match six variants by hand. Reuses the existing public Address enum (SevenBit(u8) / TenBit(u16)) for the Command address payload instead of introducing a new AddressKind type. AddressKind, InternalEvent, InternalTermination, write_termination_to_status, read_termination_to_status, and all four *_internal helpers (blocking + async × listen/respond_to_write/respond_to_read) are deleted. The pending: Cell field stays — it carries the RepeatedStart edge from a previous respond_to_* to the next target-trait listen() call so the trait surfaces Request::RepeatedStart as its own event. The inherent API folds the restart edge into the preceding Response::Restarted instead and never reads pending. Trait impls disambiguate inherent-vs-trait method calls via small private _impl shims (recover_impl, listen_impl, respond_to_write_impl, respond_to_read_impl). The public inherent methods delegate to those shims so the inherent surface stays public; the trait impls call the shims directly so resolution is unambiguous when both I2c and I2c are in scope. Inherent recover() stays pub on both flavours (Blocking sync, Async async). Slave-side examples updated: - i2c-slave.rs / i2c-slave-async.rs / i2c-loopback-async.rs / i2c-loopback-async-10bit.rs — match on Command::{Probe,Read,Write} { addr } and use Response::is_terminal() + Response::bytes() in the respond loops. - i2c-slave-async-target-trait.rs — comment updated to reflect that the inherent API now surfaces probes as Command::Probe { addr } too (not collapsed). Verification: - cargo build --target thumbv8m.main-none-eabihf with 5 representative feature combos + 8 minimal sample combos: clean. - cargo build --release for both mimxrt685s and mimxrt633s with full features: clean. - cargo clippy --locked -- -Dwarnings on both -os-timer and -rtc time-driver feature combos: clean. - cargo clippy --locked -- -Dwarnings -D clippy::style on the examples workspace: clean. - cargo check --tests: clean (3 new unit tests cover Response helpers and cmd_addr widening). - cargo +nightly fmt --check: clean on slave.rs and on every touched example. - cargo vet: still 'Vetting Succeeded (111 fully audited, 1 partially audited, 9 exempted)'. Net delta on src/i2c/slave.rs: -236 lines. --- .../src/bin/i2c-loopback-async-10bit.rs | 62 +- .../rt685s-evk/src/bin/i2c-loopback-async.rs | 51 +- .../src/bin/i2c-slave-async-target-trait.rs | 13 +- .../rt685s-evk/src/bin/i2c-slave-async.rs | 51 +- .../src/bin/i2c-slave-target-trait.rs | 4 +- examples/rt685s-evk/src/bin/i2c-slave.rs | 51 +- src/i2c/slave.rs | 1390 +++++++---------- 7 files changed, 707 insertions(+), 915 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs b/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs index ad084d19..caf3f207 100644 --- a/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs +++ b/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs @@ -2,15 +2,13 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::master::I2cMaster; -use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave, Response}; +use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_imxrt_examples as _; use embedded_hal_async::i2c::I2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const ADDR: u16 = 0x0123; const MASTER_BUFLEN: usize = 8; @@ -45,38 +43,48 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { let expected_buf: [u8; SLAVE_BUFLEN] = generate_buffer(); match slave.listen().await.unwrap() { - Command::Probe => { - info!("Probe, nothing to do"); + Command::Probe { addr } => { + info!("Probe @ {:?}, nothing to do", defmt::Debug2Format(&addr)); } - Command::Read => { - info!("Read"); + Command::Read { addr } => { + info!("Read @ {:?}", defmt::Debug2Format(&addr)); loop { - match slave.respond_to_read(&t_buf).await.unwrap() { - Response::Complete(n) => { - info!("Response complete read with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to read got {} bytes, more bytes to fill", n), + let resp = slave.respond_to_read(&t_buf).await.unwrap(); + if resp.is_terminal() { + info!( + "Response complete read with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to read got {} bytes, more bytes to fill", resp.bytes()); } } - Command::Write => { - info!("Write"); + Command::Write { addr } => { + info!("Write @ {:?}", defmt::Debug2Format(&addr)); loop { - match slave.respond_to_write(&mut r_buf).await.unwrap() { - Response::Complete(n) => { - info!("Response complete write with {} bytes", n); - info!("i2cm write data: {:x}", r_buf[0..n]); - - // Compare written data with expected data - // Ensures that the second byte of 10 bit address is handled properly - assert!(r_buf[0..n] == expected_buf[0..n]); - break; - } - Response::Pending(n) => info!("Response to write got {} bytes, more bytes pending", n), + let resp = slave.respond_to_write(&mut r_buf).await.unwrap(); + if resp.is_terminal() { + let n = resp.bytes(); + info!( + "Response complete write with {} bytes ({:?})", + n, + defmt::Debug2Format(&resp) + ); + info!("i2cm write data: {:x}", r_buf[0..n]); + + // Compare written data with expected data + // Ensures that the second byte of 10 bit address is handled properly + assert!(r_buf[0..n] == expected_buf[0..n]); + break; } + info!("Response to write got {} bytes, more bytes pending", resp.bytes()); } } + other => { + info!("Unhandled command variant: {:?}", defmt::Debug2Format(&other)); + } } } } diff --git a/examples/rt685s-evk/src/bin/i2c-loopback-async.rs b/examples/rt685s-evk/src/bin/i2c-loopback-async.rs index 66db3fdb..29e00f12 100644 --- a/examples/rt685s-evk/src/bin/i2c-loopback-async.rs +++ b/examples/rt685s-evk/src/bin/i2c-loopback-async.rs @@ -2,15 +2,13 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::master::{DutyCycle, I2cMaster}; -use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave, Response}; +use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_imxrt_examples as _; use embedded_hal_async::i2c::I2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const ADDR: u8 = 0x20; const MASTER_BUFLEN: usize = 8; @@ -36,33 +34,42 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { } match slave.listen().await.unwrap() { - Command::Probe => { - info!("Probe, nothing to do"); + Command::Probe { addr } => { + info!("Probe @ {:?}, nothing to do", defmt::Debug2Format(&addr)); } - Command::Read => { - info!("Read"); + Command::Read { addr } => { + info!("Read @ {:?}", defmt::Debug2Format(&addr)); loop { - match slave.respond_to_read(&t_buf).await.unwrap() { - Response::Complete(n) => { - info!("Response complete read with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to read got {} bytes, more bytes to fill", n), + let resp = slave.respond_to_read(&t_buf).await.unwrap(); + if resp.is_terminal() { + info!( + "Response complete read with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to read got {} bytes, more bytes to fill", resp.bytes()); } } - Command::Write => { - info!("Write"); + Command::Write { addr } => { + info!("Write @ {:?}", defmt::Debug2Format(&addr)); loop { - match slave.respond_to_write(&mut r_buf).await.unwrap() { - Response::Complete(n) => { - info!("Response complete write with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to write got {} bytes, more bytes pending", n), + let resp = slave.respond_to_write(&mut r_buf).await.unwrap(); + if resp.is_terminal() { + info!( + "Response complete write with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to write got {} bytes, more bytes pending", resp.bytes()); } } + other => { + info!("Unhandled command variant: {:?}", defmt::Debug2Format(&other)); + } } } } diff --git a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs index 74c79b62..82ce0b52 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs @@ -26,18 +26,16 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::slave::{Address, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_imxrt_examples as _; // Bring the target trait methods into scope so we go through the trait // instead of the inherent API. use embedded_mcu_hal::i2c::SevenBitAddress; use embedded_mcu_hal::i2c::target::Request; use embedded_mcu_hal::i2c::target::asynch::I2c as TargetI2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; @@ -70,8 +68,8 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { match req { Request::Stop(addr) => { // A probe (address-only transaction terminated by STOP) - // surfaces here. The inherent API folds this into - // `Command::Probe`. + // surfaces here. The inherent API reports the same event + // as `Command::Probe { addr }`. info!("Stop @ 0x{:02X} (probe)", addr); } Request::RepeatedStart(prev_addr) => { @@ -120,7 +118,10 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { break; } Ok(WriteStatus::Restarted(n)) => { - info!("Write restarted after {} bytes — next listen will surface RepeatedStart", n); + info!( + "Write restarted after {} bytes — next listen will surface RepeatedStart", + n + ); // Do NOT call recover() here: a Restarted is a // healthy continuation of an in-flight master // transaction (Sr + ADDR+R/W is queued on the diff --git a/examples/rt685s-evk/src/bin/i2c-slave-async.rs b/examples/rt685s-evk/src/bin/i2c-slave-async.rs index 91cae508..6855bdcc 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-async.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-async.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave, Response}; +use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; @@ -27,33 +25,42 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { } match i2c.listen().await.unwrap() { - Command::Probe => { - info!("Probe, nothing to do"); + Command::Probe { addr } => { + info!("Probe @ {:?}, nothing to do", defmt::Debug2Format(&addr)); } - Command::Read => { - info!("Read"); + Command::Read { addr } => { + info!("Read @ {:?}", defmt::Debug2Format(&addr)); loop { - match i2c.respond_to_read(&buf).await.unwrap() { - Response::Complete(n) => { - info!("Response complete read with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to read got {} bytes, more bytes to fill", n), + let resp = i2c.respond_to_read(&buf).await.unwrap(); + if resp.is_terminal() { + info!( + "Response complete read with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to read got {} bytes, more bytes to fill", resp.bytes()); } } - Command::Write => { - info!("Write"); + Command::Write { addr } => { + info!("Write @ {:?}", defmt::Debug2Format(&addr)); loop { - match i2c.respond_to_write(&mut buf).await.unwrap() { - Response::Complete(n) => { - info!("Response complete write with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to write got {} bytes, more bytes pending", n), + let resp = i2c.respond_to_write(&mut buf).await.unwrap(); + if resp.is_terminal() { + info!( + "Response complete write with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to write got {} bytes, more bytes pending", resp.bytes()); } } + other => { + info!("Unhandled command variant: {:?}", defmt::Debug2Format(&other)); + } } } } diff --git a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs index 5bdb6e53..1239396e 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs @@ -13,15 +13,13 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::Blocking; use embassy_imxrt::i2c::slave::{Address, I2cSlave}; -use embassy_imxrt_examples as _; use embedded_mcu_hal::i2c::SevenBitAddress; use embedded_mcu_hal::i2c::target::Request; use embedded_mcu_hal::i2c::target::blocking::I2c as TargetI2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/i2c-slave.rs b/examples/rt685s-evk/src/bin/i2c-slave.rs index a56b4166..e716c788 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::Blocking; -use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave, Response}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; @@ -22,33 +20,42 @@ async fn slave_service(i2c: I2cSlave<'static, Blocking>) { } match i2c.listen().unwrap() { - Command::Probe => { - info!("Probe, nothing to do"); + Command::Probe { addr } => { + info!("Probe @ {:?}, nothing to do", defmt::Debug2Format(&addr)); } - Command::Read => { - info!("Read"); + Command::Read { addr } => { + info!("Read @ {:?}", defmt::Debug2Format(&addr)); loop { - match i2c.respond_to_read(&buf).unwrap() { - Response::Complete(n) => { - info!("Response complete read with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to read got {} bytes, more bytes to fill", n), + let resp = i2c.respond_to_read(&buf).unwrap(); + if resp.is_terminal() { + info!( + "Response complete read with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to read got {} bytes, more bytes to fill", resp.bytes()); } } - Command::Write => { - info!("Write"); + Command::Write { addr } => { + info!("Write @ {:?}", defmt::Debug2Format(&addr)); loop { - match i2c.respond_to_write(&mut buf).unwrap() { - Response::Complete(n) => { - info!("Response complete write with {} bytes", n); - break; - } - Response::Pending(n) => info!("Response to write got {} bytes, more bytes pending", n), + let resp = i2c.respond_to_write(&mut buf).unwrap(); + if resp.is_terminal() { + info!( + "Response complete write with {} bytes ({:?})", + resp.bytes(), + defmt::Debug2Format(&resp) + ); + break; } + info!("Response to write got {} bytes, more bytes pending", resp.bytes()); } } + other => { + info!("Unhandled command variant: {:?}", defmt::Debug2Format(&other)); + } } } } diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index af9ade65..ccadb85b 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -1,29 +1,51 @@ //! Implements I2C function support over flexcomm + gpios //! +//! # Inherent API +//! +//! The [`I2cSlave`] driver exposes a small inherent API: +//! +//! - [`I2cSlave::listen`] — wait for the next [`Command`] from the +//! controller. The variant carries the matched [`Address`]. +//! - [`I2cSlave::respond_to_write`] — drain incoming bytes for a Write. +//! - [`I2cSlave::respond_to_read`] — supply outgoing bytes for a Read. +//! - [`I2cSlave::recover`] — re-baseline the peripheral after a wedged +//! or cancelled transfer. +//! +//! Both `respond_to_*` calls return a [`Response`] that describes how the +//! transaction terminated. The variants distinguish stop, repeated start, +//! buffer exhaustion, and read-side early-stop, so callers can react +//! precisely instead of papering over the difference. The convenience +//! helpers [`Response::bytes`] and [`Response::is_terminal`] handle the +//! common "I just want the count and whether to loop" case. +//! +//! Probes (zero-byte writes — the controller sends `ADDR+W` and +//! immediately STOPs) surface as [`Command::Probe`] with the matched +//! address attached; there is no following `respond_to_write` call. +//! //! # Target trait impls //! -//! Besides the inherent [`I2cSlave::listen`] / [`I2cSlave::respond_to_write`] / -//! [`I2cSlave::respond_to_read`] API that returns [`Command`] / [`Response`], -//! this driver also implements the [`embedded_mcu_hal::i2c::target`] traits: +//! Besides the inherent API, this driver also implements the +//! [`embedded_mcu_hal::i2c::target`] traits: //! -//! - [`embedded_mcu_hal::i2c::target::blocking::I2c`] for `I2cSlave<'_, Blocking>` -//! - [`embedded_mcu_hal::i2c::target::asynch::I2c`] for `I2cSlave<'_, Async>` +//! - [`embedded_mcu_hal::i2c::target::blocking::I2c`] for +//! `I2cSlave<'_, Blocking>` +//! - [`embedded_mcu_hal::i2c::target::asynch::I2c`] for +//! `I2cSlave<'_, Async>` //! -//! Both flavours are implemented twice — once for `SevenBitAddress` and once -//! for `TenBitAddress`. The implementation that matches the address mode -//! configured at construction time succeeds; the mismatched implementation -//! returns [`Error::UnsupportedConfiguration`], which maps to -//! [`ErrorKind::Other`](embedded_mcu_hal::i2c::target::ErrorKind::Other). +//! Both flavours are implemented twice — once for `SevenBitAddress` and +//! once for `TenBitAddress`. The implementation that matches the address +//! mode configured at construction time succeeds; the mismatched +//! implementation returns [`Error::UnsupportedConfiguration`], which maps +//! to [`ErrorKind::Other`](embedded_mcu_hal::i2c::target::ErrorKind::Other). //! -//! The trait API surfaces extra information that the inherent API collapses: -//! the matched address payload on every event, restart and stop edges as -//! distinct [`Request`](embedded_mcu_hal::i2c::target::Request) variants, and -//! a `recover` method to re-baseline the peripheral after a cancelled -//! `respond_*` future. Probes (zero-byte writes) surface as -//! [`Request::Stop`](embedded_mcu_hal::i2c::target::Request::Stop) directly, -//! with no preceding `Write` event — the strict interpretation that a probe -//! is just an address-then-STOP on the wire. - +//! The trait API additionally surfaces a repeated-START as its own +//! [`Request::RepeatedStart`](embedded_mcu_hal::i2c::target::Request::RepeatedStart) +//! event on the next `listen` call. The inherent API folds restarts into +//! the preceding [`Response::Restarted`] instead — the next inherent +//! `listen` resumes hardware polling and reports the new direction +//! directly. + +use core::cell::Cell; use core::future::{Future, poll_fn}; use core::marker::PhantomData; use core::sync::atomic::Ordering; @@ -50,7 +72,7 @@ pub enum AddressError { } /// I2C address type -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Address { /// 7-bit address SevenBit(u8), @@ -133,96 +155,138 @@ impl TenBitAddressInfo { } } -/// Command from master -pub enum Command { - /// I2C probe with no data - Probe, - - /// I2C Read - Read, - - /// I2C Write - Write, -} - -/// Result of response functions -pub enum Response { - /// I2C transaction complete with this amount of bytes - Complete(usize), - - /// I2C transaction pending with this amount of bytes completed so far - Pending(usize), -} - -/// Bus event observed by the internal `listen` helpers. +/// Command observed by the slave on the I2C bus. /// -/// Richer than the public [`Command`] — it preserves the matched address -/// so the trait-impl wrapper can populate -/// [`Request`](embedded_mcu_hal::i2c::target::Request)'s `A` payload, and it -/// distinguishes the probe (address-only, terminated by STOP) from a -/// data-bearing write/read. -#[derive(Copy, Clone, Debug)] -enum InternalEvent { - /// 0-byte write transaction (address-only ACK then STOP). - Probe { addr: AddressKind }, - /// Controller is about to read from this target. - Read { addr: AddressKind }, - /// Controller is about to write to this target. - Write { addr: AddressKind }, -} - -/// Concrete address (7- or 10-bit) that just matched on the bus. -#[derive(Copy, Clone, Debug)] -enum AddressKind { - Seven(u8), - Ten(u16), +/// Returned by [`I2cSlave::listen`]. Each variant carries the [`Address`] +/// that matched on the wire so applications with multiple register files +/// or address aliases can dispatch on it. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum Command { + /// A probe: the controller sent `ADDR+W` and immediately followed + /// with `STOP` (or with the second 10-bit address byte mismatching). + /// No `respond_to_*` call is required. The carried [`Address`] is the + /// configured address that matched. + Probe { + /// Address that was matched. + addr: Address, + }, + /// Controller is about to read from this target. Service it with + /// [`I2cSlave::respond_to_read`]. + Read { + /// Address that was matched. + addr: Address, + }, + /// Controller is about to write to this target. Service it with + /// [`I2cSlave::respond_to_write`]. + Write { + /// Address that was matched. + addr: Address, + }, } -/// Reason a `respond_to_*` inner helper returned. +/// Outcome of a [`I2cSlave::respond_to_read`] or +/// [`I2cSlave::respond_to_write`] call. /// -/// Richer than the public [`Response`] — distinguishes Stop vs Restart vs -/// BufferFull vs NeedMore vs EarlyStop, all observable from the existing -/// `slvdesel` / `slvstate` / buffer-exhaustion signals. -#[derive(Copy, Clone, Debug)] -enum InternalTermination { - /// Controller issued a `STOP`. `n` = bytes moved. +/// The variants distinguish every observable termination reason so callers +/// can decide between continuing with a fresh buffer, looping back to +/// [`I2cSlave::listen`] for the next event, or returning to the +/// application loop. The convenience helpers [`Response::bytes`] and +/// [`Response::is_terminal`] cover the "just give me the count and tell +/// me if we're done" case without forcing every call site to match on all +/// six variants. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum Response { + /// Controller issued `STOP`. The transaction is complete. `n` is the + /// number of bytes moved. Stopped(usize), - /// Controller issued a repeated `START`. `n` = bytes moved. + /// Controller issued a repeated `START` (`Sr`). The transaction is + /// complete from this `respond_*` call's perspective; the next + /// [`I2cSlave::listen`] will report the new direction. `n` is the + /// number of bytes moved before the restart. Restarted(usize), - /// Supplied buffer was exhausted, controller still expects more - /// (writing more bytes / clocking more reads). `n` = buffer length. - Continued(usize), - /// Read-only: buffer fully consumed at exactly the moment the - /// controller NACK+STOPped. `n` = buffer length. + /// Write side: the supplied buffer was filled before the controller + /// terminated the transfer. Call [`I2cSlave::respond_to_write`] again + /// with another buffer to continue draining. `n` equals the buffer + /// length. + WriteContinued(usize), + /// Read side: the supplied buffer was fully consumed but the + /// controller is still clocking more bytes. Call + /// [`I2cSlave::respond_to_read`] again with another buffer to continue + /// supplying data. `n` equals the buffer length. + ReadContinued(usize), + /// Read side: the supplied buffer was fully consumed at exactly the + /// moment the controller NACKed and stopped. `n` equals the buffer + /// length. ReadComplete(usize), - /// Read-only: controller terminated with STOP (or Sr) before the - /// buffer ran out. `n` = bytes moved. + /// Read side: the controller stopped (or restarted) before the + /// supplied buffer was drained. `n` is the number of bytes the + /// controller clocked out. ReadEarlyStop(usize), } -/// Edge event queued by a previous `respond_to_*` for the next `listen` call. +impl Response { + /// Number of bytes moved on the wire during the call that produced + /// this response. + #[must_use] + pub const fn bytes(self) -> usize { + match self { + Self::Stopped(n) + | Self::Restarted(n) + | Self::WriteContinued(n) + | Self::ReadContinued(n) + | Self::ReadComplete(n) + | Self::ReadEarlyStop(n) => n, + } + } + + /// Whether the transaction is complete from the slave's perspective. + /// + /// Returns `true` for [`Stopped`], [`Restarted`], [`ReadComplete`], + /// and [`ReadEarlyStop`]; `false` for [`WriteContinued`] and + /// [`ReadContinued`], which indicate the caller should supply another + /// buffer to continue the in-flight transaction. + /// + /// [`Stopped`]: Self::Stopped + /// [`Restarted`]: Self::Restarted + /// [`ReadComplete`]: Self::ReadComplete + /// [`ReadEarlyStop`]: Self::ReadEarlyStop + /// [`WriteContinued`]: Self::WriteContinued + /// [`ReadContinued`]: Self::ReadContinued + #[must_use] + pub const fn is_terminal(self) -> bool { + match self { + Self::Stopped(_) | Self::Restarted(_) | Self::ReadComplete(_) | Self::ReadEarlyStop(_) => true, + Self::WriteContinued(_) | Self::ReadContinued(_) => false, + } + } +} + +/// Edge event queued by a previous `respond_to_*` for the next trait-level +/// `listen` call. /// -/// Only used by the trait impls — the inherent `listen` ignores this field. -/// Today only `RepeatedStart` is ever queued; a `STOP` terminator is folded -/// into the `WriteStatus::Stopped` / `ReadStatus::Complete` payload of the -/// preceding `respond_to_*` call rather than synthesised as a separate -/// `listen` event. +/// Only used by the target trait impls — the inherent `listen` ignores +/// this field because it folds the restart edge into the preceding +/// [`Response::Restarted`] instead. #[derive(Copy, Clone, Debug)] enum EdgeKind { - /// Surface a [`Request::RepeatedStart`](mcu_target::Request::RepeatedStart) - /// at the next `listen`, then resume hardware polling for the next + /// Surface a + /// [`Request::RepeatedStart`](mcu_target::Request::RepeatedStart) at + /// the next trait `listen`, then resume hardware polling for the next /// addressed event. RepeatedStart, } /// Per-driver pending-edge bookkeeping for the target trait impls. /// -/// Tracks the address that matched on the most recent addressed event and -/// any restart/stop edge that should be surfaced before the next hardware -/// poll. Populated only by the target trait helpers. +/// Tracks the address that matched on the most recent addressed event +/// (for the trait's `Request` payload) and any restart edge that +/// should be surfaced before the next hardware poll. The inherent API +/// never reads this state. #[derive(Copy, Clone, Debug, Default)] struct PendingEdge { - last_addr: Option, + last_addr: Option
, pending: Option, } @@ -233,14 +297,18 @@ pub struct I2cSlave<'a, M: Mode> { _phantom: PhantomData, dma_ch: Option>, ten_bit_info: Option, - /// Address configured at construction time. Used by the target trait - /// impls to populate the `A` payload on - /// [`Request`](mcu_target::Request) variants and to enforce the - /// runtime address-mode check. + /// Address configured at construction time. Carried verbatim on every + /// [`Command`] returned by [`I2cSlave::listen`] and used by the + /// target trait impls to populate the `A` payload on + /// [`Request`](mcu_target::Request) and to enforce the runtime + /// address-mode check. configured_address: Address, - /// Pending edge bookkeeping for the target trait impls. The inherent - /// API does not touch this field. - pending: core::cell::Cell, + /// Pending-edge bookkeeping for the target trait impls. Set by + /// [`I2cSlave::respond_to_write`] / [`I2cSlave::respond_to_read`] + /// when they observe a repeated-START terminator; drained by the + /// trait `listen` wrapper. The inherent `listen` does not touch this + /// field. + pending: Cell, } impl<'a, M: Mode> I2cSlave<'a, M> { @@ -313,9 +381,52 @@ impl<'a, M: Mode> I2cSlave<'a, M> { dma_ch, ten_bit_info, configured_address: address, - pending: core::cell::Cell::new(PendingEdge::default()), + pending: Cell::new(PendingEdge::default()), + }) + } + + /// Record the address that just matched on the bus. Set by every + /// successful `listen` so subsequent trait-level edge surfacing + /// (`RepeatedStart`) can attach the right address. + fn note_match(&self, addr: Address) { + let mut p = self.pending.get(); + p.last_addr = Some(addr); + self.pending.set(p); + } + + /// Queue an edge to be surfaced at the next trait-level `listen`. + fn queue_edge(&self, edge: EdgeKind) { + let mut p = self.pending.get(); + p.pending = Some(edge); + self.pending.set(p); + } + + /// Drain any queued edge, returning the matching + /// [`mcu_target::Request`]. Returns `None` if there is no queued edge. + fn drain_edge(&self, addr_for: fn(Address) -> A) -> Option> + where + A: embedded_hal_1::i2c::AddressMode, + { + let mut p = self.pending.get(); + let edge = p.pending.take()?; + let addr = p.last_addr.unwrap_or(self.configured_address); + self.pending.set(p); + Some(match edge { + EdgeKind::RepeatedStart => mcu_target::Request::RepeatedStart(addr_for(addr)), }) } + + /// Address-mode runtime guard. Returns + /// `Err(UnsupportedConfiguration)` when the trait impl being driven + /// does not match the address type the slave was constructed with. + fn require_address_kind(&self, want_ten_bit: bool) -> Result<()> { + let is_ten_bit = self.ten_bit_info.is_some(); + if is_ten_bit == want_ten_bit { + Ok(()) + } else { + Err(Error::UnsupportedConfiguration) + } + } } impl<'a> I2cSlave<'a, Blocking> { @@ -378,9 +489,21 @@ impl<'a> I2cSlave<'a, Async> { } impl I2cSlave<'_, Blocking> { - /// Listen for commands from the I2C Master. + /// Listen for the next [`Command`] from the I2C controller. + /// + /// Blocks until the bus produces an addressed event for this target. + /// The returned [`Command`] carries the matched [`Address`]. pub fn listen(&self) -> Result { + self.listen_impl() + } + + /// Inherent implementation of [`Self::listen`]. The trait-impl + /// wrappers call this directly to avoid `inherent-vs-trait`-method + /// ambiguity when both `I2c` and `I2c` + /// impls are in scope. + fn listen_impl(&self) -> Result { let i2c = self.info.regs; + let addr = self.configured_address; self.block_until_addressed()?; @@ -395,7 +518,8 @@ impl I2cSlave<'_, Blocking> { } else { // If the second byte of the 10 bit address is not received, then nack the address. i2c.slvctl().write(|w| w.slvnack().nack()); - return Ok(Command::Probe); + self.note_match(addr); + return Ok(Command::Probe { addr }); } // Check slave is still selected, master has not sent a stop @@ -409,7 +533,8 @@ impl I2cSlave<'_, Blocking> { } else { // If the first byte of the 10 bit address is not received again, then nack the address. i2c.slvctl().write(|w| w.slvnack().nack()); - return Ok(Command::Probe); + self.note_match(addr); + return Ok(Command::Probe { addr }); } // Check slave is ready for transmit if !i2c.stat().read().slvstate().is_slave_transmit() { @@ -428,20 +553,32 @@ impl I2cSlave<'_, Blocking> { if i2c.stat().read().slvdesel().is_deselected() { // Clear the deselected bit i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(Command::Probe); + self.note_match(addr); + return Ok(Command::Probe { addr }); } let state = i2c.stat().read().slvstate().variant(); + self.note_match(addr); match state { - Some(Slvstate::SlaveReceive) => Ok(Command::Write), - Some(Slvstate::SlaveTransmit) => Ok(Command::Read), + Some(Slvstate::SlaveReceive) => Ok(Command::Write { addr }), + Some(Slvstate::SlaveTransmit) => Ok(Command::Read { addr }), _ => Err(TransferError::OtherBusError.into()), } } - /// Respond to write command from master + /// Drain incoming bytes for a [`Command::Write`]. + /// + /// Fills the supplied buffer in order. Returns when the controller + /// terminates the transfer (stop, repeated start) or when the buffer + /// is full. pub fn respond_to_write(&self, buf: &mut [u8]) -> Result { + self.respond_to_write_impl(buf) + } + + /// Inherent implementation of [`Self::respond_to_write`]. + fn respond_to_write_impl(&self, buf: &mut [u8]) -> Result { let i2c = self.info.regs; + let buf_len = buf.len(); let mut xfer_count: usize = 0; for b in buf { @@ -472,22 +609,37 @@ impl I2cSlave<'_, Blocking> { if stat.slvdesel().is_deselected() { // Clear the deselect bit i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(Response::Complete(xfer_count)); + return Ok(Response::Stopped(xfer_count)); } else if stat.slvstate().is_slave_address() { - // Handle restart - return Ok(Response::Complete(xfer_count)); + // Handle restart — record the edge for any trait caller; the + // inherent `listen` will see the slave-address state directly + // on its next call. + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(xfer_count)); } else if stat.slvstate().is_slave_receive() { - // Master still wants to send more data, transaction incomplete - return Ok(Response::Pending(xfer_count)); + // Buffer exhausted; controller is still sending — caller + // should supply another buffer to continue draining. + debug_assert_eq!(xfer_count, buf_len); + return Ok(Response::WriteContinued(xfer_count)); } // We should not get here Err(TransferError::ReadFail.into()) } - /// Respond to read command from master + /// Supply outgoing bytes for a [`Command::Read`]. + /// + /// Sends bytes from the supplied buffer in order. Returns when the + /// controller terminates the transfer (NACK+stop, repeated start) or + /// when the buffer is exhausted. pub fn respond_to_read(&self, buf: &[u8]) -> Result { + self.respond_to_read_impl(buf) + } + + /// Inherent implementation of [`Self::respond_to_read`]. + fn respond_to_read_impl(&self, buf: &[u8]) -> Result { let i2c = self.info.regs; + let buf_len = buf.len(); let mut xfer_count: usize = 0; for b in buf { @@ -520,24 +672,91 @@ impl I2cSlave<'_, Blocking> { if stat.slvdesel().is_deselected() { // clear the deselect bit i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(Response::Complete(xfer_count)); + // Distinguish ReadComplete (buffer exactly exhausted at + // NACK+STOP) from ReadEarlyStop (controller stopped with + // bytes still in the buffer). + if xfer_count == buf_len { + return Ok(Response::ReadComplete(xfer_count)); + } else { + return Ok(Response::ReadEarlyStop(xfer_count)); + } } else if stat.slvstate().is_slave_address() { - // Handle restart after read - return Ok(Response::Complete(xfer_count)); + // Handle restart after read — queue the edge for any trait + // caller and report the restart to the inherent caller. + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(xfer_count)); } else if stat.slvstate().is_slave_transmit() { - // Master is still expecting data, transaction incomplete - return Ok(Response::Pending(xfer_count)); + // Buffer exhausted but master still expects bytes — caller + // should supply another buffer to continue. + debug_assert_eq!(xfer_count, buf_len); + return Ok(Response::ReadContinued(xfer_count)); } // We should not get here Err(TransferError::WriteFail.into()) } + + /// Bring the slave back to a known-clean state after a wedged + /// transfer or an internally cancelled transaction. + /// + /// Disables DMA arming, NAKs any pending byte, clears the deselect + /// latch, drops any pending remediation flag, and clears the queued + /// edge bookkeeping. The configured address(es) and `slven` bit are + /// preserved — the next [`I2cSlave::listen`] will accept a fresh + /// transaction without re-initialising the driver. + pub fn recover(&self) -> Result<()> { + self.recover_impl() + } + + /// Inherent implementation of [`Self::recover`]. The trait-impl + /// wrappers call this directly to avoid the + /// `inherent-vs-trait`-method ambiguity that arises when both + /// `embedded_mcu_hal::i2c::target::blocking::I2c` + /// and `` are in scope. + fn recover_impl(&self) -> Result<()> { + let i2c = self.info.regs; + critical_section::with(|_| { + // Drop any latent DMA arming. + i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); + // Disable interrupts we may have left enabled. + i2c.intenclr() + .write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit()); + + // NAK an in-flight pending byte if any; this is harmless when + // we are not in the pending state. + if i2c.stat().read().slvpending().is_pending() { + i2c.slvctl().write(|w| w.slvnack().set_bit()); + } + + // Clear the deselect latch if set. + if i2c.stat().read().slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + } + + // Drop any pending remediation. + self.info.remediation.store(REMEDIATON_NONE, Ordering::Release); + }); + + // Drop our software bookkeeping. + self.pending.set(PendingEdge::default()); + Ok(()) + } } impl I2cSlave<'_, Async> { - /// Listen for commands from the I2C Master asynchronously + /// Listen for the next [`Command`] from the I2C controller, asynchronously. + /// + /// The returned future resolves when the bus produces an addressed + /// event for this target. The [`Command`] carries the matched + /// [`Address`]. pub async fn listen(&mut self) -> Result { + self.listen_impl().await + } + + /// Inherent implementation of [`Self::listen`]. + async fn listen_impl(&mut self) -> Result { let i2c = self.info.regs; + let addr = self.configured_address; // Disable DMA i2c.slvctl().write(|w| w.slvdma().disabled()); @@ -567,7 +786,8 @@ impl I2cSlave<'_, Async> { } else { // If the second byte of the 10 bit address is not received, then nack the address. i2c.slvctl().write(|w| w.slvnack().nack()); - return Ok(Command::Probe); + self.note_match(addr); + return Ok(Command::Probe { addr }); } // Check slave is still selected, master has not sent a stop @@ -581,7 +801,8 @@ impl I2cSlave<'_, Async> { } else { // If the first byte of the 10 bit address is not received again, then nack the address. i2c.slvctl().write(|w| w.slvnack().nack()); - return Ok(Command::Probe); + self.note_match(addr); + return Ok(Command::Probe { addr }); } // Check slave is ready for transmit if !i2c.stat().read().slvstate().is_slave_transmit() { @@ -600,19 +821,30 @@ impl I2cSlave<'_, Async> { if i2c.stat().read().slvdesel().is_deselected() { // Clear the deselected bit i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(Command::Probe); + self.note_match(addr); + return Ok(Command::Probe { addr }); } let state = i2c.stat().read().slvstate().variant(); + self.note_match(addr); match state { - Some(Slvstate::SlaveReceive) => Ok(Command::Write), - Some(Slvstate::SlaveTransmit) => Ok(Command::Read), + Some(Slvstate::SlaveReceive) => Ok(Command::Write { addr }), + Some(Slvstate::SlaveTransmit) => Ok(Command::Read { addr }), _ => Err(TransferError::OtherBusError.into()), } } - /// Respond to write command from master + /// Drain incoming bytes for a [`Command::Write`], asynchronously. + /// + /// DMA-driven. Fills the supplied buffer in order. Returns when the + /// controller terminates the transfer (stop, repeated start) or when + /// the buffer is full. pub async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + self.respond_to_write_impl(buf).await + } + + /// Inherent implementation of [`Self::respond_to_write`]. + async fn respond_to_write_impl(&mut self, buf: &mut [u8]) -> Result { let i2c = self.info.regs; let buf_len = buf.len(); @@ -621,7 +853,7 @@ impl I2cSlave<'_, Async> { if !stat.slvstate().is_slave_receive() { // 0 byte write if stat.slvdesel().is_deselected() { - return Ok(Response::Complete(0)); + return Ok(Response::Stopped(0)); } return Err(TransferError::ReadFail.into()); } @@ -676,30 +908,36 @@ impl I2cSlave<'_, Async> { nak_guard.defuse(); let xfer_count = self.abort_dma(buf_len)?; let stat = i2c.stat().read(); - // We got a stop from master, either way this transaction is - // completed if stat.slvdesel().is_deselected() { // Clear the deselected bit i2c.stat().write(|w| w.slvdesel().deselected()); - - return Ok(Response::Complete(xfer_count)); + return Ok(Response::Stopped(xfer_count)); } else if stat.slvstate().is_slave_address() { - // We are addressed again, so this must be a restart - return Ok(Response::Complete(xfer_count)); + // Restart — queue the edge for any trait caller. + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(xfer_count)); } else if stat.slvstate().is_slave_receive() { - // That was a partial transaction, the master wants to send more - // data - return Ok(Response::Pending(xfer_count)); + // Buffer was filled before the controller terminated; caller + // should supply another buffer to continue. + return Ok(Response::WriteContinued(xfer_count)); } Err(TransferError::ReadFail.into()) } - /// Respond to read command from master - /// User must provide enough data to complete the transaction or else - /// we will get stuck in this function + /// Supply outgoing bytes for a [`Command::Read`], asynchronously. + /// + /// DMA-driven. Sends bytes from the supplied buffer in order. Returns + /// when the controller terminates the transfer (NACK+stop, repeated + /// start) or when the buffer is exhausted. pub async fn respond_to_read(&mut self, buf: &[u8]) -> Result { + self.respond_to_read_impl(buf).await + } + + /// Inherent implementation of [`Self::respond_to_read`]. + async fn respond_to_read_impl(&mut self, buf: &[u8]) -> Result { let i2c = self.info.regs; + let buf_len = buf.len(); // Verify that we are ready for transmit if !i2c.stat().read().slvstate().is_slave_transmit() { @@ -739,33 +977,92 @@ impl I2cSlave<'_, Async> { if stat.slvpending().is_pending() { return Poll::Ready(()); } + // DMA drained but the master still expects more bytes. + // Without this guard the future sleeps forever because the + // hardware does not raise slvpending once DMA is armed and + // running dry — SDA just floats and the master clocks 0xFF + // until something else aborts the transaction. + if !dma_channel.is_active() && stat.slvstate().is_slave_transmit() { + return Poll::Ready(()); + } Poll::Pending }) .await; // Complete DMA transaction and get transfer count - let xfer_count = self.abort_dma(buf.len())?; + let xfer_count = self.abort_dma(buf_len)?; let stat = i2c.stat().read(); - // we got a nack or a stop from master, either way this transaction is - // completed if stat.slvdesel().is_deselected() { // clear the deselect bit i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(Response::Complete(xfer_count)); + // Distinguish ReadComplete (buffer exactly exhausted) from + // ReadEarlyStop (controller stopped with bytes still in the + // buffer). + if xfer_count == buf_len { + return Ok(Response::ReadComplete(xfer_count)); + } else { + return Ok(Response::ReadEarlyStop(xfer_count)); + } } else if stat.slvpending().is_pending() || stat.slvstate().is_slave_address() { - // Handle restart after read as well as the cases where - // slave deselected is not set in response to a master nack - // then the next transaction starts the slave state goes into - // pending + addressed. - return Ok(Response::Complete(xfer_count)); + // Restart (or NACK-then-next-address) — queue the edge for + // any trait caller and report the restart to the inherent + // caller. + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(xfer_count)); + } else if stat.slvstate().is_slave_transmit() { + // DMA drained while the master is still clocking — caller + // should supply another buffer to continue. + debug_assert_eq!(xfer_count, buf_len); + return Ok(Response::ReadContinued(xfer_count)); } - // We should not get here Err(TransferError::WriteFail.into()) } + /// Bring the slave back to a known-clean state after a wedged or + /// cancelled transfer. + /// + /// Aborts any in-flight DMA, NAKs any pending byte, disables DMA + /// arming, clears the deselect latch, drops any pending remediation, + /// and clears any queued edge bookkeeping. The configured address(es) + /// and `slven` bit are preserved. + pub async fn recover(&mut self) -> Result<()> { + self.recover_impl().await + } + + /// Inherent implementation of [`Self::recover`]. The trait-impl + /// wrappers call this directly to avoid the + /// `inherent-vs-trait`-method ambiguity that arises when both + /// `embedded_mcu_hal::i2c::target::asynch::I2c` and + /// `` are in scope. + async fn recover_impl(&mut self) -> Result<()> { + let i2c = self.info.regs; + if let Some(dma) = self.dma_ch.as_ref() + && dma.is_active() + { + dma.abort(); + } + + critical_section::with(|_| { + i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); + i2c.intenclr() + .write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit()); + + if i2c.stat().read().slvpending().is_pending() { + i2c.slvctl().write(|w| w.slvnack().set_bit()); + } + if i2c.stat().read().slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + } + self.info.remediation.store(REMEDIATON_NONE, Ordering::Release); + }); + + self.pending.set(PendingEdge::default()); + Ok(()) + } + fn poll_sw_action(&self) -> impl Future + use<'_> { let i2c = self.info.regs; @@ -851,669 +1148,135 @@ impl Drop for NakGuard { // embedded-mcu-hal::i2c::target trait impls // --------------------------------------------------------------------------- // -// The traits are layered on top of the existing inherent API. They preserve -// the inherent API verbatim and add: -// -// - A `recover` method to re-baseline the peripheral after a cancelled or -// wedged transfer. -// - A `Request` shape that carries the matched address and surfaces -// `RepeatedStart` / `Stop` as their own events (synthesised from the -// peripheral's `slvdesel` / `slvstate` bits). -// - `ReadStatus` / `WriteStatus` enums that distinguish the three possible -// terminators per direction. +// The traits layer on top of the inherent API: each trait method calls +// the inherent method and maps the result into the corresponding trait +// shape. The address-mode generic is handled by writing two separate +// trait impl blocks per flavour (one for `SevenBitAddress`, one for +// `TenBitAddress`); at runtime the impl checks the address type +// configured at construction time and returns `ErrorKind::Other` from +// `listen` if the wrong impl was invoked. // -// The address-mode generic is handled by writing two separate trait impl -// blocks per flavour (one for `SevenBitAddress`, one for `TenBitAddress`). -// At runtime the impl checks the address type configured at construction -// time and returns `ErrorKind::Other` from `listen` if the wrong impl was -// invoked. - -impl AddressKind { - fn as_u8(self) -> Option { - match self { - Self::Seven(a) => Some(a), - Self::Ten(_) => None, - } - } +// The single piece of state that exists *only* for the trait is the +// `pending: Cell` field on `I2cSlave`: it carries a +// `RepeatedStart` edge from a previous `respond_to_*` to the next trait +// `listen` call, so the trait surfaces `Request::RepeatedStart` as its +// own event rather than folding it into the preceding response. The +// inherent API never reads this field. - fn as_u16(self) -> u16 { - match self { - Self::Seven(a) => a as u16, - Self::Ten(a) => a, +impl mcu_target::Error for Error { + fn kind(&self) -> mcu_target::ErrorKind { + match *self { + Self::UnsupportedConfiguration => mcu_target::ErrorKind::Other, + Self::Transfer(e) => match e { + TransferError::Timeout => mcu_target::ErrorKind::Other, + TransferError::ReadFail | TransferError::WriteFail => { + mcu_target::ErrorKind::NoAcknowledge(mcu_target::NoAcknowledgeSource::Data) + } + TransferError::AddressNack => { + mcu_target::ErrorKind::NoAcknowledge(mcu_target::NoAcknowledgeSource::Address) + } + TransferError::ArbitrationLoss => mcu_target::ErrorKind::ArbitrationLoss, + TransferError::StartStopError | TransferError::OtherBusError => mcu_target::ErrorKind::Bus, + }, } } } -impl I2cSlave<'_, M> { - /// Snapshot of the configured address as an [`AddressKind`]. - fn matched_address(&self) -> AddressKind { - match self.configured_address { - Address::SevenBit(a) => AddressKind::Seven(a), - Address::TenBit(a) => AddressKind::Ten(a), - } - } +impl mcu_target::ErrorType for I2cSlave<'_, M> { + type Error = Error; +} - /// Update the pending-edge bookkeeping with the address that just - /// matched on the bus. Called from every successful inner-listen. - fn note_match(&self, addr: AddressKind) { - let mut p = self.pending.get(); - p.last_addr = Some(addr); - self.pending.set(p); +/// Map the address from a [`Command`] to the trait-side `u8` payload +/// (used by the `SevenBitAddress` impls). Returns `0` for the +/// impossible-here `TenBit` case — the address-mode guard in the impl +/// rejects mismatched callers before we ever reach this mapping. +fn cmd_addr_u8(addr: Address) -> u8 { + match addr { + Address::SevenBit(a) => a, + Address::TenBit(_) => 0, } +} - /// Queue an edge to be surfaced at the next trait-level `listen`. - fn queue_edge(&self, edge: EdgeKind) { - let mut p = self.pending.get(); - p.pending = Some(edge); - self.pending.set(p); +/// Map the address from a [`Command`] to the trait-side `u16` payload +/// (used by the `TenBitAddress` impls). 7-bit addresses widen losslessly. +fn cmd_addr_u16(addr: Address) -> u16 { + match addr { + Address::SevenBit(a) => a as u16, + Address::TenBit(a) => a, } +} - /// Drain any queued edge, returning the matching - /// [`mcu_target::Request`]. Returns `None` if there is no queued edge. - fn drain_edge(&self, addr_for: fn(AddressKind) -> A) -> Option> - where - A: embedded_hal_1::i2c::AddressMode, - { - let mut p = self.pending.get(); - let edge = p.pending.take()?; - let addr = p.last_addr.unwrap_or_else(|| self.matched_address()); - self.pending.set(p); - Some(match edge { - EdgeKind::RepeatedStart => mcu_target::Request::RepeatedStart(addr_for(addr)), - }) +/// Map a [`Command`] to a [`mcu_target::Request`]. +fn cmd_to_request_u8(cmd: Command) -> mcu_target::Request { + match cmd { + Command::Probe { addr } => mcu_target::Request::Stop(cmd_addr_u8(addr)), + Command::Read { addr } => mcu_target::Request::Read(cmd_addr_u8(addr)), + Command::Write { addr } => mcu_target::Request::Write(cmd_addr_u8(addr)), } +} - /// Address-mode runtime guard. Returns `Err(UnsupportedConfiguration)` - /// when the trait impl being driven does not match the configured - /// address type — for example, calling the - /// `I2c` impl on a slave constructed with a 10-bit - /// address. - fn require_address_kind(&self, want_ten_bit: bool) -> Result<()> { - let is_ten_bit = self.ten_bit_info.is_some(); - if is_ten_bit == want_ten_bit { - Ok(()) - } else { - Err(Error::UnsupportedConfiguration) - } +/// Map a [`Command`] to a [`mcu_target::Request`]. +fn cmd_to_request_u16(cmd: Command) -> mcu_target::Request { + match cmd { + Command::Probe { addr } => mcu_target::Request::Stop(cmd_addr_u16(addr)), + Command::Read { addr } => mcu_target::Request::Read(cmd_addr_u16(addr)), + Command::Write { addr } => mcu_target::Request::Write(cmd_addr_u16(addr)), } } -/// Convert a [`InternalTermination`] coming out of a write helper to the -/// corresponding [`mcu_target::WriteStatus`], queuing any edge that needs -/// to be surfaced at the next `listen`. -fn write_termination_to_status(slave: &I2cSlave<'_, M>, term: InternalTermination) -> mcu_target::WriteStatus { - match term { - InternalTermination::Stopped(n) => { - // Stopped is implicit in the WriteStatus itself; no edge to queue. - mcu_target::WriteStatus::Stopped(n) - } - InternalTermination::Restarted(n) => { - slave.queue_edge(EdgeKind::RepeatedStart); - mcu_target::WriteStatus::Restarted(n) - } - InternalTermination::Continued(n) => mcu_target::WriteStatus::BufferFull(n), - // Read-only terminators should never come out of a write helper. - InternalTermination::ReadComplete(n) | InternalTermination::ReadEarlyStop(n) => { +/// Map a [`Response`] returned by `respond_to_write` to the trait-side +/// [`mcu_target::WriteStatus`]. +fn resp_to_write_status(r: Response) -> mcu_target::WriteStatus { + match r { + Response::Stopped(n) => mcu_target::WriteStatus::Stopped(n), + Response::Restarted(n) => mcu_target::WriteStatus::Restarted(n), + Response::WriteContinued(n) => mcu_target::WriteStatus::BufferFull(n), + // Read-only terminators should never come out of respond_to_write; + // fall back to Stopped to keep the trait surface total. + Response::ReadContinued(n) | Response::ReadComplete(n) | Response::ReadEarlyStop(n) => { mcu_target::WriteStatus::Stopped(n) } } } -/// Convert a [`InternalTermination`] coming out of a read helper to the -/// corresponding [`mcu_target::ReadStatus`], queuing any edge that needs to -/// be surfaced at the next `listen`. -fn read_termination_to_status(slave: &I2cSlave<'_, M>, term: InternalTermination) -> mcu_target::ReadStatus { - match term { - InternalTermination::ReadComplete(n) => mcu_target::ReadStatus::Complete(n), - InternalTermination::ReadEarlyStop(n) => mcu_target::ReadStatus::EarlyStop(n), - InternalTermination::Continued(n) => mcu_target::ReadStatus::NeedMore(n), - InternalTermination::Restarted(n) => { - slave.queue_edge(EdgeKind::RepeatedStart); +/// Map a [`Response`] returned by `respond_to_read` to the trait-side +/// [`mcu_target::ReadStatus`]. +fn resp_to_read_status(r: Response) -> mcu_target::ReadStatus { + match r { + Response::ReadComplete(n) => mcu_target::ReadStatus::Complete(n), + Response::ReadContinued(n) => mcu_target::ReadStatus::NeedMore(n), + Response::ReadEarlyStop(n) | Response::Restarted(n) | Response::Stopped(n) => { mcu_target::ReadStatus::EarlyStop(n) } - InternalTermination::Stopped(n) => mcu_target::ReadStatus::EarlyStop(n), + // Write-only terminator — should never come out of respond_to_read. + Response::WriteContinued(n) => mcu_target::ReadStatus::EarlyStop(n), } } -// ----- Internal helpers: Blocking -------------------------------------------- - -impl I2cSlave<'_, Blocking> { - /// Trait-internal `listen` for the blocking flavour. Mirrors the - /// inherent `listen` but tracks the matched address. - fn listen_internal(&self) -> Result { - let i2c = self.info.regs; +// ----- Blocking I2c impl ------------------------------------ - self.block_until_addressed()?; +impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { + fn recover(&mut self) -> Result<()> { + self.recover_impl() + } - // Block until we know it is read or write - self.poll()?; - - if let Some(ten_bit_address) = self.ten_bit_info { - // For 10 bit address, the first byte received is the second byte of the address - if i2c.slvdat().read().data().bits() == ten_bit_address.second_byte { - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - self.poll()?; - } else { - // If the second byte of the 10 bit address is not received, then nack the address. - i2c.slvctl().write(|w| w.slvnack().nack()); - let addr = self.matched_address(); - self.note_match(addr); - return Ok(InternalEvent::Probe { addr }); - } - - // Check slave is still selected, master has not sent a stop - if i2c.stat().read().slvsel().is_selected() { - // Check for a restart - if i2c.stat().read().slvstate().is_slave_address() { - // Check if first byte of 10 bit address is received again with read bit set - if i2c.slvdat().read().data().bits() == ten_bit_address.first_byte | 1 { - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - self.poll()?; - } else { - // If the first byte of the 10 bit address is not received again, then nack the address. - i2c.slvctl().write(|w| w.slvnack().nack()); - let addr = self.matched_address(); - self.note_match(addr); - return Ok(InternalEvent::Probe { addr }); - } - // Check slave is ready for transmit - if !i2c.stat().read().slvstate().is_slave_transmit() { - return Err(TransferError::WriteFail.into()); - } - } else { - // Check slave is ready to receive - if !i2c.stat().read().slvstate().is_slave_receive() { - return Err(TransferError::ReadFail.into()); - } - } - } - } - - // We are already deselected, so it must be an 0 byte write transaction - if i2c.stat().read().slvdesel().is_deselected() { - // Clear the deselected bit - i2c.stat().write(|w| w.slvdesel().deselected()); - let addr = self.matched_address(); - self.note_match(addr); - return Ok(InternalEvent::Probe { addr }); - } - - let state = i2c.stat().read().slvstate().variant(); - let addr = self.matched_address(); - self.note_match(addr); - match state { - Some(Slvstate::SlaveReceive) => Ok(InternalEvent::Write { addr }), - Some(Slvstate::SlaveTransmit) => Ok(InternalEvent::Read { addr }), - _ => Err(TransferError::OtherBusError.into()), - } - } - - /// Trait-internal `respond_to_write` for the blocking flavour. - fn respond_to_write_internal(&self, buf: &mut [u8]) -> Result { - let i2c = self.info.regs; - let mut xfer_count: usize = 0; - let buf_len = buf.len(); - - for b in buf { - //poll until something happens - self.poll()?; - - let stat = i2c.stat().read(); - // if master send stop, we are done - if stat.slvdesel().is_deselected() { - break; - } - // if master send a restart, we are done - if stat.slvstate().is_slave_address() { - break; - } - - if !stat.slvstate().is_slave_receive() { - return Err(TransferError::ReadFail.into()); - } - - // Now we can safely read the next byte - *b = i2c.slvdat().read().data().bits(); - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - xfer_count += 1; - } - - let stat = i2c.stat().read(); - if stat.slvdesel().is_deselected() { - // Clear the deselect bit - i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(InternalTermination::Stopped(xfer_count)); - } else if stat.slvstate().is_slave_address() { - // Handle restart - return Ok(InternalTermination::Restarted(xfer_count)); - } else if stat.slvstate().is_slave_receive() { - // Buffer exhausted; master is still sending — treat as - // "continue with more buffer" (BufferFull at trait level, - // Pending at the inherent-API level). - debug_assert_eq!(xfer_count, buf_len); - return Ok(InternalTermination::Continued(xfer_count)); - } - - // We should not get here - Err(TransferError::ReadFail.into()) - } - - /// Trait-internal `respond_to_read` for the blocking flavour. - fn respond_to_read_internal(&self, buf: &[u8]) -> Result { - let i2c = self.info.regs; - let mut xfer_count: usize = 0; - let buf_len = buf.len(); - - for b in buf { - // Block until something happens - self.poll()?; - - let stat = i2c.stat().read(); - // if master send nack or stop, we are done - if stat.slvdesel().is_deselected() { - break; - } - // if master send restart, we are done - if stat.slvstate().is_slave_address() { - break; - } - - // Verify that we are ready for write - if !stat.slvstate().is_slave_transmit() { - return Err(TransferError::WriteFail.into()); - } - - i2c.slvdat().write(|w| - // SAFETY: unsafe only here due to use of bits() - unsafe{w.data().bits(*b)}); - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - xfer_count += 1; - } - - let stat = i2c.stat().read(); - if stat.slvdesel().is_deselected() { - // clear the deselect bit - i2c.stat().write(|w| w.slvdesel().deselected()); - // Distinguish Complete (buffer exactly exhausted) vs EarlyStop - // (buffer had bytes left when controller stopped). - if xfer_count == buf_len { - return Ok(InternalTermination::ReadComplete(xfer_count)); - } else { - return Ok(InternalTermination::ReadEarlyStop(xfer_count)); - } - } else if stat.slvstate().is_slave_address() { - // Handle restart after read - return Ok(InternalTermination::Restarted(xfer_count)); - } else if stat.slvstate().is_slave_transmit() { - // Buffer exhausted but master still expects bytes — NeedMore. - debug_assert_eq!(xfer_count, buf_len); - return Ok(InternalTermination::Continued(xfer_count)); - } - - // We should not get here - Err(TransferError::WriteFail.into()) - } - - /// Bring the slave back to a known-clean state after a wedged or - /// cancelled transfer. - /// - /// See [`mcu_target::blocking::I2c::recover`] for the contract. - /// - /// Performs: NAK any pending byte, disable DMA arming, clear the - /// deselect latch, drop any pending remediation, and drop any queued - /// edge bookkeeping. The configured address(es) and `slven` bit are - /// preserved. - pub fn recover(&self) -> Result<()> { - let i2c = self.info.regs; - critical_section::with(|_| { - // Drop any latent DMA arming. - i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); - // Disable interrupts we may have left enabled. - i2c.intenclr() - .write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit()); - - // NAK an in-flight pending byte if any; this is harmless when - // we are not in the pending state. - if i2c.stat().read().slvpending().is_pending() { - i2c.slvctl().write(|w| w.slvnack().set_bit()); - } - - // Clear the deselect latch if set. - if i2c.stat().read().slvdesel().is_deselected() { - i2c.stat().write(|w| w.slvdesel().deselected()); - } - - // Drop any pending remediation. - self.info.remediation.store(REMEDIATON_NONE, Ordering::Release); - }); - - // Drop our software bookkeeping. - self.pending.set(PendingEdge::default()); - Ok(()) - } -} - -// ----- Internal helpers: Async ----------------------------------------------- - -impl I2cSlave<'_, Async> { - /// Trait-internal `listen` for the async flavour. Mirrors the inherent - /// async `listen` but tracks the matched address. - async fn listen_internal(&mut self) -> Result { - let i2c = self.info.regs; - - // Disable DMA - i2c.slvctl().write(|w| w.slvdma().disabled()); - - // Check whether we already have a matched address and just waiting - // for software ack/nack - if !i2c.stat().read().slvpending().is_pending() { - self.poll_sw_action().await; - } - - if i2c.stat().read().slvstate().is_slave_address() { - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - } - - // Poll for HW to transitioning from addressed to receive/transmit - self.poll_sw_action().await; - - if let Some(ten_bit_address) = self.ten_bit_info { - // For 10 bit address, the first byte received is the second byte of the address - if i2c.slvdat().read().data().bits() == ten_bit_address.second_byte { - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - self.poll_sw_action().await; - } else { - // If the second byte of the 10 bit address is not received, then nack the address. - i2c.slvctl().write(|w| w.slvnack().nack()); - let addr = self.matched_address(); - self.note_match(addr); - return Ok(InternalEvent::Probe { addr }); - } - - // Check slave is still selected, master has not sent a stop - if i2c.stat().read().slvsel().is_selected() { - // Check for a restart - if i2c.stat().read().slvstate().is_slave_address() { - // Check if first byte of 10 bit address is received again with read bit set - if i2c.slvdat().read().data().bits() == ten_bit_address.first_byte | 1 { - i2c.slvctl().write(|w| w.slvcontinue().continue_()); - self.poll_sw_action().await; - } else { - // If the first byte of the 10 bit address is not received again, then nack the address. - i2c.slvctl().write(|w| w.slvnack().nack()); - let addr = self.matched_address(); - self.note_match(addr); - return Ok(InternalEvent::Probe { addr }); - } - // Check slave is ready for transmit - if !i2c.stat().read().slvstate().is_slave_transmit() { - return Err(TransferError::WriteFail.into()); - } - } else { - // Check slave is ready to receive - if !i2c.stat().read().slvstate().is_slave_receive() { - return Err(TransferError::ReadFail.into()); - } - } - } - } - - // We are deselected, so it must be an 0 byte write transaction - if i2c.stat().read().slvdesel().is_deselected() { - // Clear the deselected bit - i2c.stat().write(|w| w.slvdesel().deselected()); - let addr = self.matched_address(); - self.note_match(addr); - return Ok(InternalEvent::Probe { addr }); - } - - let state = i2c.stat().read().slvstate().variant(); - let addr = self.matched_address(); - self.note_match(addr); - match state { - Some(Slvstate::SlaveReceive) => Ok(InternalEvent::Write { addr }), - Some(Slvstate::SlaveTransmit) => Ok(InternalEvent::Read { addr }), - _ => Err(TransferError::OtherBusError.into()), - } - } - - /// Trait-internal `respond_to_write` for the async flavour. - async fn respond_to_write_internal(&mut self, buf: &mut [u8]) -> Result { - let i2c = self.info.regs; - let buf_len = buf.len(); - - // Verify that we are ready for write - let stat = i2c.stat().read(); - if !stat.slvstate().is_slave_receive() { - // 0 byte write - if stat.slvdesel().is_deselected() { - return Ok(InternalTermination::Stopped(0)); - } - return Err(TransferError::ReadFail.into()); - } - - let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; - - // Enable DMA - i2c.slvctl().write(|w| w.slvdma().enabled()); - - // Enable interrupt - i2c.intenset() - .write(|w| w.slvpendingen().enabled().slvdeselen().enabled()); - - let options = dma::transfer::TransferOptions::default(); - let _transfer = dma_channel.read_from_peripheral(i2c.slvdat().as_ptr() as *mut u8, buf, options); - - let nak_guard = NakGuard { info: self.info }; - let _dma_guard = OnDrop::new(|| { - i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); - }); - - poll_fn(|cx| { - let i2c = self.info.regs; - - self.info.waker.register(cx.waker()); - dma_channel.get_waker().register(cx.waker()); - - let stat = i2c.stat().read(); - if stat.slvdesel().is_deselected() { - return Poll::Ready(()); - } - if stat.slvpending().is_pending() { - return Poll::Ready(()); - } - if !dma_channel.is_active() && stat.slvstate().is_slave_receive() { - return Poll::Ready(()); - } - - Poll::Pending - }) - .await; - - nak_guard.defuse(); - let xfer_count = self.abort_dma(buf_len)?; - let stat = i2c.stat().read(); - if stat.slvdesel().is_deselected() { - i2c.stat().write(|w| w.slvdesel().deselected()); - return Ok(InternalTermination::Stopped(xfer_count)); - } else if stat.slvstate().is_slave_address() { - return Ok(InternalTermination::Restarted(xfer_count)); - } else if stat.slvstate().is_slave_receive() { - return Ok(InternalTermination::Continued(xfer_count)); - } - - Err(TransferError::ReadFail.into()) - } - - /// Trait-internal `respond_to_read` for the async flavour. - async fn respond_to_read_internal(&mut self, buf: &[u8]) -> Result { - let i2c = self.info.regs; - let buf_len = buf.len(); - - if !i2c.stat().read().slvstate().is_slave_transmit() { - return Err(TransferError::WriteFail.into()); - } - - i2c.slvctl().write(|w| w.slvdma().enabled()); - - i2c.intenset() - .write(|w| w.slvpendingen().enabled().slvdeselen().enabled()); - - let options = dma::transfer::TransferOptions::default(); - let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; - - let _transfer = dma_channel.write_to_peripheral(buf, i2c.slvdat().as_ptr() as *mut u8, options); - - let _dma_guard = OnDrop::new(|| { - i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); - }); - - poll_fn(|cx| { - let i2c = self.info.regs; - - self.info.waker.register(cx.waker()); - dma_channel.get_waker().register(cx.waker()); - - let stat = i2c.stat().read(); - if stat.slvdesel().is_deselected() { - return Poll::Ready(()); - } - if stat.slvpending().is_pending() { - return Poll::Ready(()); - } - // DMA drained but the master still expects more bytes. - // Without this guard the future sleeps forever because the - // hardware does not raise slvpending once DMA is armed and - // running dry — SDA just floats and the master clocks 0xFF - // until something else aborts the transaction. - if !dma_channel.is_active() && stat.slvstate().is_slave_transmit() { - return Poll::Ready(()); - } - - Poll::Pending - }) - .await; - - let xfer_count = self.abort_dma(buf_len)?; - let stat = i2c.stat().read(); - - if stat.slvdesel().is_deselected() { - i2c.stat().write(|w| w.slvdesel().deselected()); - if xfer_count == buf_len { - return Ok(InternalTermination::ReadComplete(xfer_count)); - } else { - return Ok(InternalTermination::ReadEarlyStop(xfer_count)); - } - } else if stat.slvpending().is_pending() || stat.slvstate().is_slave_address() { - // Restart (or NACK-then-next-address) — surface as Restart so - // the next `listen` reports the RepeatedStart edge. - return Ok(InternalTermination::Restarted(xfer_count)); - } else if stat.slvstate().is_slave_transmit() { - // DMA drained while the master is still clocking — surface - // NeedMore so the caller can supply another buffer. The - // caller's next `respond_to_read` call re-arms DMA without - // dropping the transaction. - debug_assert_eq!(xfer_count, buf_len); - return Ok(InternalTermination::Continued(xfer_count)); - } - - Err(TransferError::WriteFail.into()) - } - - /// Bring the slave back to a known-clean state after a wedged or - /// cancelled transfer. - /// - /// See [`mcu_target::asynch::I2c::recover`] for the contract. - /// - /// Aborts any in-flight DMA, NAKs any pending byte, disables DMA - /// arming, clears the deselect latch, drops any pending remediation, - /// and clears any queued edge bookkeeping. The configured address(es) - /// and `slven` bit are preserved. The async flavour additionally awaits - /// completion of the remediation pump so the returned future is the - /// canonical join point. - pub async fn recover(&mut self) -> Result<()> { - let i2c = self.info.regs; - if let Some(dma) = self.dma_ch.as_ref() - && dma.is_active() - { - dma.abort(); - } - - critical_section::with(|_| { - i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); - i2c.intenclr() - .write(|w| w.slvpendingclr().set_bit().slvdeselclr().set_bit()); - - if i2c.stat().read().slvpending().is_pending() { - i2c.slvctl().write(|w| w.slvnack().set_bit()); - } - if i2c.stat().read().slvdesel().is_deselected() { - i2c.stat().write(|w| w.slvdesel().deselected()); - } - self.info.remediation.store(REMEDIATON_NONE, Ordering::Release); - }); - - self.pending.set(PendingEdge::default()); - Ok(()) - } -} - -// ----- Error / ErrorType impls ----------------------------------------------- - -impl mcu_target::Error for Error { - fn kind(&self) -> mcu_target::ErrorKind { - match *self { - Self::UnsupportedConfiguration => mcu_target::ErrorKind::Other, - Self::Transfer(e) => match e { - TransferError::Timeout => mcu_target::ErrorKind::Other, - TransferError::ReadFail | TransferError::WriteFail => { - mcu_target::ErrorKind::NoAcknowledge(mcu_target::NoAcknowledgeSource::Data) - } - TransferError::AddressNack => { - mcu_target::ErrorKind::NoAcknowledge(mcu_target::NoAcknowledgeSource::Address) - } - TransferError::ArbitrationLoss => mcu_target::ErrorKind::ArbitrationLoss, - TransferError::StartStopError | TransferError::OtherBusError => mcu_target::ErrorKind::Bus, - }, - } - } -} - -impl mcu_target::ErrorType for I2cSlave<'_, M> { - type Error = Error; -} - -// ----- Blocking I2c impl ------------------------------------ - -impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { - fn recover(&mut self) -> Result<()> { - self.recover() - } - - fn listen(&mut self) -> Result> { - self.require_address_kind(false)?; + fn listen(&mut self) -> Result> { + self.require_address_kind(false)?; // Drain any queued edge first. - if let Some(req) = self.drain_edge::(|a| a.as_u8().unwrap_or(0)) { + if let Some(req) = self.drain_edge::(cmd_addr_u8) { return Ok(req); } - let ev = self.listen_internal()?; - Ok(match ev { - InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u8().unwrap_or(0)), - InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u8().unwrap_or(0)), - InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u8().unwrap_or(0)), - }) + Ok(cmd_to_request_u8(self.listen_impl()?)) } fn respond_to_read(&mut self, buf: &[u8]) -> Result { - let term = self.respond_to_read_internal(buf)?; - Ok(read_termination_to_status(self, term)) + Ok(resp_to_read_status(self.respond_to_read_impl(buf)?)) } fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - let term = self.respond_to_write_internal(buf)?; - Ok(write_termination_to_status(self, term)) + Ok(resp_to_write_status(self.respond_to_write_impl(buf)?)) } } @@ -1521,32 +1284,25 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { fn recover(&mut self) -> Result<()> { - self.recover() + self.recover_impl() } fn listen(&mut self) -> Result> { self.require_address_kind(true)?; - if let Some(req) = self.drain_edge::(|a| a.as_u16()) { + if let Some(req) = self.drain_edge::(cmd_addr_u16) { return Ok(req); } - let ev = self.listen_internal()?; - Ok(match ev { - InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u16()), - InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u16()), - InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u16()), - }) + Ok(cmd_to_request_u16(self.listen_impl()?)) } fn respond_to_read(&mut self, buf: &[u8]) -> Result { - let term = self.respond_to_read_internal(buf)?; - Ok(read_termination_to_status(self, term)) + Ok(resp_to_read_status(self.respond_to_read_impl(buf)?)) } fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - let term = self.respond_to_write_internal(buf)?; - Ok(write_termination_to_status(self, term)) + Ok(resp_to_write_status(self.respond_to_write_impl(buf)?)) } } @@ -1554,32 +1310,25 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { async fn recover(&mut self) -> Result<()> { - self.recover().await + self.recover_impl().await } async fn listen(&mut self) -> Result> { self.require_address_kind(false)?; - if let Some(req) = self.drain_edge::(|a| a.as_u8().unwrap_or(0)) { + if let Some(req) = self.drain_edge::(cmd_addr_u8) { return Ok(req); } - let ev = self.listen_internal().await?; - Ok(match ev { - InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u8().unwrap_or(0)), - InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u8().unwrap_or(0)), - InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u8().unwrap_or(0)), - }) + Ok(cmd_to_request_u8(self.listen_impl().await?)) } async fn respond_to_read(&mut self, buf: &[u8]) -> Result { - let term = self.respond_to_read_internal(buf).await?; - Ok(read_termination_to_status(self, term)) + Ok(resp_to_read_status(self.respond_to_read_impl(buf).await?)) } async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - let term = self.respond_to_write_internal(buf).await?; - Ok(write_termination_to_status(self, term)) + Ok(resp_to_write_status(self.respond_to_write_impl(buf).await?)) } } @@ -1587,32 +1336,25 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { async fn recover(&mut self) -> Result<()> { - self.recover(self).await + self.recover_impl().await } async fn listen(&mut self) -> Result> { self.require_address_kind(true)?; - if let Some(req) = self.drain_edge::(|a| a.as_u16()) { + if let Some(req) = self.drain_edge::(cmd_addr_u16) { return Ok(req); } - let ev = self.listen_internal().await?; - Ok(match ev { - InternalEvent::Probe { addr } => mcu_target::Request::Stop(addr.as_u16()), - InternalEvent::Read { addr } => mcu_target::Request::Read(addr.as_u16()), - InternalEvent::Write { addr } => mcu_target::Request::Write(addr.as_u16()), - }) + Ok(cmd_to_request_u16(self.listen_impl().await?)) } async fn respond_to_read(&mut self, buf: &[u8]) -> Result { - let term = self.respond_to_read_internal(buf).await?; - Ok(read_termination_to_status(self, term)) + Ok(resp_to_read_status(self.respond_to_read_impl(buf).await?)) } async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - let term = self.respond_to_write_internal(buf).await?; - Ok(write_termination_to_status(self, term)) + Ok(resp_to_write_status(self.respond_to_write_impl(buf).await?)) } } @@ -1660,13 +1402,35 @@ mod tests { } #[test] - fn address_kind_round_trips() { - let seven = AddressKind::Seven(0x42); - assert_eq!(seven.as_u8(), Some(0x42)); - assert_eq!(seven.as_u16(), 0x42); - - let ten = AddressKind::Ten(0x1AB); - assert_eq!(ten.as_u8(), None); - assert_eq!(ten.as_u16(), 0x1AB); + fn response_bytes_returns_payload_for_every_variant() { + assert_eq!(Response::Stopped(3).bytes(), 3); + assert_eq!(Response::Restarted(5).bytes(), 5); + assert_eq!(Response::WriteContinued(8).bytes(), 8); + assert_eq!(Response::ReadContinued(8).bytes(), 8); + assert_eq!(Response::ReadComplete(7).bytes(), 7); + assert_eq!(Response::ReadEarlyStop(2).bytes(), 2); + } + + #[test] + fn response_is_terminal_distinguishes_continuation_from_completion() { + assert!(Response::Stopped(0).is_terminal()); + assert!(Response::Restarted(0).is_terminal()); + assert!(Response::ReadComplete(0).is_terminal()); + assert!(Response::ReadEarlyStop(0).is_terminal()); + assert!(!Response::WriteContinued(0).is_terminal()); + assert!(!Response::ReadContinued(0).is_terminal()); + } + + #[test] + fn cmd_addr_widens_seven_bit_losslessly() { + let addr = Address::SevenBit(0x42); + assert_eq!(cmd_addr_u8(addr), 0x42); + assert_eq!(cmd_addr_u16(addr), 0x42); + } + + #[test] + fn cmd_addr_u16_passes_through_ten_bit() { + let addr = Address::TenBit(0x1AB); + assert_eq!(cmd_addr_u16(addr), 0x1AB); } } From 24bf0f3d554a1dfc3c438d12ec30d54168a396ea Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 06:31:13 -0700 Subject: [PATCH 09/27] i2c::slave: drop _impl shims, use UFCS in trait impl call sites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the four private *_impl wrappers (listen_impl, respond_to_write_impl, respond_to_read_impl, recover_impl) on both Blocking and Async with direct UFCS calls in the trait impl bodies: I2cSlave::::listen(self) I2cSlave::::respond_to_read(self, buf).await I2cSlave::::recover(self).await The shims existed only to disambiguate inherent-vs-trait method resolution when both I2c and I2c impls are in scope. Spelling the inherent receiver type explicitly via UFCS removes that ambiguity at the call site without an extra layer of indirection. The public inherent methods (listen, respond_to_write, respond_to_read, recover) are now the only definitions; the trait impls call them directly. Verification: cargo build (debug + release, mimxrt685s + mimxrt633s, full and minimal feature combos), cargo clippy --locked -- -Dwarnings on both -os-timer and -rtc time-driver flavours, examples workspace clippy --locked with style/perf/correctness lints, cargo check --tests, cargo +nightly fmt --check, cargo vet --locked --frozen — all clean. Net delta on src/i2c/slave.rs: -43 lines (1436 -> 1393); -279 lines vs the original PR baseline. --- src/i2c/slave.rs | 91 +++++++++++++----------------------------------- 1 file changed, 24 insertions(+), 67 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index ccadb85b..57411b6e 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -494,14 +494,6 @@ impl I2cSlave<'_, Blocking> { /// Blocks until the bus produces an addressed event for this target. /// The returned [`Command`] carries the matched [`Address`]. pub fn listen(&self) -> Result { - self.listen_impl() - } - - /// Inherent implementation of [`Self::listen`]. The trait-impl - /// wrappers call this directly to avoid `inherent-vs-trait`-method - /// ambiguity when both `I2c` and `I2c` - /// impls are in scope. - fn listen_impl(&self) -> Result { let i2c = self.info.regs; let addr = self.configured_address; @@ -572,11 +564,6 @@ impl I2cSlave<'_, Blocking> { /// terminates the transfer (stop, repeated start) or when the buffer /// is full. pub fn respond_to_write(&self, buf: &mut [u8]) -> Result { - self.respond_to_write_impl(buf) - } - - /// Inherent implementation of [`Self::respond_to_write`]. - fn respond_to_write_impl(&self, buf: &mut [u8]) -> Result { let i2c = self.info.regs; let buf_len = buf.len(); let mut xfer_count: usize = 0; @@ -633,11 +620,6 @@ impl I2cSlave<'_, Blocking> { /// controller terminates the transfer (NACK+stop, repeated start) or /// when the buffer is exhausted. pub fn respond_to_read(&self, buf: &[u8]) -> Result { - self.respond_to_read_impl(buf) - } - - /// Inherent implementation of [`Self::respond_to_read`]. - fn respond_to_read_impl(&self, buf: &[u8]) -> Result { let i2c = self.info.regs; let buf_len = buf.len(); let mut xfer_count: usize = 0; @@ -705,15 +687,6 @@ impl I2cSlave<'_, Blocking> { /// preserved — the next [`I2cSlave::listen`] will accept a fresh /// transaction without re-initialising the driver. pub fn recover(&self) -> Result<()> { - self.recover_impl() - } - - /// Inherent implementation of [`Self::recover`]. The trait-impl - /// wrappers call this directly to avoid the - /// `inherent-vs-trait`-method ambiguity that arises when both - /// `embedded_mcu_hal::i2c::target::blocking::I2c` - /// and `` are in scope. - fn recover_impl(&self) -> Result<()> { let i2c = self.info.regs; critical_section::with(|_| { // Drop any latent DMA arming. @@ -750,11 +723,6 @@ impl I2cSlave<'_, Async> { /// event for this target. The [`Command`] carries the matched /// [`Address`]. pub async fn listen(&mut self) -> Result { - self.listen_impl().await - } - - /// Inherent implementation of [`Self::listen`]. - async fn listen_impl(&mut self) -> Result { let i2c = self.info.regs; let addr = self.configured_address; @@ -840,11 +808,6 @@ impl I2cSlave<'_, Async> { /// controller terminates the transfer (stop, repeated start) or when /// the buffer is full. pub async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - self.respond_to_write_impl(buf).await - } - - /// Inherent implementation of [`Self::respond_to_write`]. - async fn respond_to_write_impl(&mut self, buf: &mut [u8]) -> Result { let i2c = self.info.regs; let buf_len = buf.len(); @@ -931,11 +894,6 @@ impl I2cSlave<'_, Async> { /// when the controller terminates the transfer (NACK+stop, repeated /// start) or when the buffer is exhausted. pub async fn respond_to_read(&mut self, buf: &[u8]) -> Result { - self.respond_to_read_impl(buf).await - } - - /// Inherent implementation of [`Self::respond_to_read`]. - async fn respond_to_read_impl(&mut self, buf: &[u8]) -> Result { let i2c = self.info.regs; let buf_len = buf.len(); @@ -1029,15 +987,6 @@ impl I2cSlave<'_, Async> { /// and clears any queued edge bookkeeping. The configured address(es) /// and `slven` bit are preserved. pub async fn recover(&mut self) -> Result<()> { - self.recover_impl().await - } - - /// Inherent implementation of [`Self::recover`]. The trait-impl - /// wrappers call this directly to avoid the - /// `inherent-vs-trait`-method ambiguity that arises when both - /// `embedded_mcu_hal::i2c::target::asynch::I2c` and - /// `` are in scope. - async fn recover_impl(&mut self) -> Result<()> { let i2c = self.info.regs; if let Some(dma) = self.dma_ch.as_ref() && dma.is_active() @@ -1257,7 +1206,7 @@ fn resp_to_read_status(r: Response) -> mcu_target::ReadStatus { impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { fn recover(&mut self) -> Result<()> { - self.recover_impl() + I2cSlave::::recover(self) } fn listen(&mut self) -> Result> { @@ -1268,15 +1217,15 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { return Ok(req); } - Ok(cmd_to_request_u8(self.listen_impl()?)) + Ok(cmd_to_request_u8(I2cSlave::::listen(self)?)) } fn respond_to_read(&mut self, buf: &[u8]) -> Result { - Ok(resp_to_read_status(self.respond_to_read_impl(buf)?)) + Ok(resp_to_read_status(I2cSlave::::respond_to_read(self, buf)?)) } fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - Ok(resp_to_write_status(self.respond_to_write_impl(buf)?)) + Ok(resp_to_write_status(I2cSlave::::respond_to_write(self, buf)?)) } } @@ -1284,7 +1233,7 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { fn recover(&mut self) -> Result<()> { - self.recover_impl() + I2cSlave::::recover(self) } fn listen(&mut self) -> Result> { @@ -1294,15 +1243,15 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { return Ok(req); } - Ok(cmd_to_request_u16(self.listen_impl()?)) + Ok(cmd_to_request_u16(I2cSlave::::listen(self)?)) } fn respond_to_read(&mut self, buf: &[u8]) -> Result { - Ok(resp_to_read_status(self.respond_to_read_impl(buf)?)) + Ok(resp_to_read_status(I2cSlave::::respond_to_read(self, buf)?)) } fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - Ok(resp_to_write_status(self.respond_to_write_impl(buf)?)) + Ok(resp_to_write_status(I2cSlave::::respond_to_write(self, buf)?)) } } @@ -1310,7 +1259,7 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { async fn recover(&mut self) -> Result<()> { - self.recover_impl().await + I2cSlave::::recover(self).await } async fn listen(&mut self) -> Result> { @@ -1320,15 +1269,19 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { return Ok(req); } - Ok(cmd_to_request_u8(self.listen_impl().await?)) + Ok(cmd_to_request_u8(I2cSlave::::listen(self).await?)) } async fn respond_to_read(&mut self, buf: &[u8]) -> Result { - Ok(resp_to_read_status(self.respond_to_read_impl(buf).await?)) + Ok(resp_to_read_status( + I2cSlave::::respond_to_read(self, buf).await?, + )) } async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - Ok(resp_to_write_status(self.respond_to_write_impl(buf).await?)) + Ok(resp_to_write_status( + I2cSlave::::respond_to_write(self, buf).await?, + )) } } @@ -1336,7 +1289,7 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { async fn recover(&mut self) -> Result<()> { - self.recover_impl().await + I2cSlave::::recover(self).await } async fn listen(&mut self) -> Result> { @@ -1346,15 +1299,19 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { return Ok(req); } - Ok(cmd_to_request_u16(self.listen_impl().await?)) + Ok(cmd_to_request_u16(I2cSlave::::listen(self).await?)) } async fn respond_to_read(&mut self, buf: &[u8]) -> Result { - Ok(resp_to_read_status(self.respond_to_read_impl(buf).await?)) + Ok(resp_to_read_status( + I2cSlave::::respond_to_read(self, buf).await?, + )) } async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { - Ok(resp_to_write_status(self.respond_to_write_impl(buf).await?)) + Ok(resp_to_write_status( + I2cSlave::::respond_to_write(self, buf).await?, + )) } } From 413a947f33e62278b0729cef99ceac5c2ecc6e46 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 06:32:47 -0700 Subject: [PATCH 10/27] cargo +nightly fmt: examples --- examples/rt633/src/bin/espi.rs | 4 +--- examples/rt633/src/bin/flexspi-nor-flash.rs | 4 +--- examples/rt633/src/lib.rs | 3 +-- .../rt685s-evk-performance-tracing/src/bin/multiple-tasks.rs | 1 + examples/rt685s-evk/src/bin/adc.rs | 4 +--- examples/rt685s-evk/src/bin/benchmarking-gpio.rs | 4 +--- .../rt685s-evk/src/bin/benchmarking-system-peripheral.rs | 4 +--- examples/rt685s-evk/src/bin/button.rs | 4 +--- examples/rt685s-evk/src/bin/clocks-blinky.rs | 5 +---- examples/rt685s-evk/src/bin/crc.rs | 4 +--- examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs | 4 +--- examples/rt685s-evk/src/bin/dma-mem.rs | 4 +--- examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs | 3 +-- examples/rt685s-evk/src/bin/flexspi-nor-flash.rs | 3 +-- examples/rt685s-evk/src/bin/flexspi-storage-service.rs | 3 +-- examples/rt685s-evk/src/bin/gpio-async-input.rs | 4 +--- examples/rt685s-evk/src/bin/gpio-blinky.rs | 4 +--- examples/rt685s-evk/src/bin/gpio-flex.rs | 4 +--- examples/rt685s-evk/src/bin/gpio-input.rs | 4 +--- examples/rt685s-evk/src/bin/hello-world.rs | 4 +--- examples/rt685s-evk/src/bin/i2c-master-async.rs | 4 +--- examples/rt685s-evk/src/bin/i2c-master-drop.rs | 4 +--- examples/rt685s-evk/src/bin/i2c-master.rs | 4 +--- examples/rt685s-evk/src/bin/keyboard.rs | 4 +--- examples/rt685s-evk/src/bin/pwm.rs | 4 +--- examples/rt685s-evk/src/bin/rng.rs | 4 +--- examples/rt685s-evk/src/bin/rtc-alarm.rs | 4 +--- examples/rt685s-evk/src/bin/rtc-nvram.rs | 4 +--- examples/rt685s-evk/src/bin/rtc-time.rs | 4 +--- examples/rt685s-evk/src/bin/sha256-async.rs | 4 +--- examples/rt685s-evk/src/bin/sha256.rs | 4 +--- examples/rt685s-evk/src/bin/spi-async.rs | 4 +--- examples/rt685s-evk/src/bin/spi-loopback.rs | 4 +--- examples/rt685s-evk/src/bin/spi.rs | 4 +--- examples/rt685s-evk/src/bin/time-driver-blinky.rs | 5 +---- examples/rt685s-evk/src/bin/timer.rs | 4 +--- examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs | 4 +--- examples/rt685s-evk/src/bin/uart-async-with-buffer.rs | 4 +--- examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs | 4 +--- examples/rt685s-evk/src/bin/uart-async.rs | 4 +--- examples/rt685s-evk/src/bin/uart.rs | 4 +--- examples/rt685s-evk/src/bin/uuid.rs | 4 +--- examples/rt685s-evk/src/bin/wwdt.rs | 4 +--- 43 files changed, 43 insertions(+), 124 deletions(-) diff --git a/examples/rt633/src/bin/espi.rs b/examples/rt633/src/bin/espi.rs index ebd2556f..d8e5b4d7 100644 --- a/examples/rt633/src/bin/espi.rs +++ b/examples/rt633/src/bin/espi.rs @@ -4,7 +4,6 @@ use core::slice; use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::bind_interrupts; use embassy_imxrt::espi::{ @@ -12,8 +11,7 @@ use embassy_imxrt::espi::{ PortConfig, }; use embassy_imxrt::peripherals::ESPI; -use panic_probe as _; -use rt633_examples as _; +use {defmt_rtt as _, panic_probe as _, rt633_examples as _}; bind_interrupts!(struct Irqs { ESPI => InterruptHandler; diff --git a/examples/rt633/src/bin/flexspi-nor-flash.rs b/examples/rt633/src/bin/flexspi-nor-flash.rs index f1bd6fdb..86dd9c12 100644 --- a/examples/rt633/src/bin/flexspi-nor-flash.rs +++ b/examples/rt633/src/bin/flexspi-nor-flash.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::flexspi::nor_flash::FlexSpiNorFlash; use embassy_imxrt::gpio; use embassy_time::Timer; -use panic_probe as _; -use rt633_examples as _; +use {defmt_rtt as _, panic_probe as _, rt633_examples as _}; /* This example demonstrates usage of the FlexSPI NOR flash driver to read, erase, and program. This is for the lower level flash operations that is not safe. Application should use the diff --git a/examples/rt633/src/lib.rs b/examples/rt633/src/lib.rs index 6ca3877e..11091585 100644 --- a/examples/rt633/src/lib.rs +++ b/examples/rt633/src/lib.rs @@ -1,12 +1,11 @@ #![no_std] -use defmt_rtt as _; use mimxrt600_fcb::FlexSpiLutOpcode::{CMD_SDR, RADDR_SDR, READ_SDR, STOP, WRITE_SDR}; use mimxrt600_fcb::FlexSpiNumPads::Single; use mimxrt600_fcb::{ ControllerMiscOption, FlexSPIFlashConfigurationBlock, SFlashPadType, SerialClkFreq, SerialNORType, flexspi_lut_seq, }; -use panic_probe as _; +use {defmt_rtt as _, panic_probe as _}; // auto-generated version information from Cargo.toml include!(concat!(env!("OUT_DIR"), "/biv.rs")); diff --git a/examples/rt685s-evk-performance-tracing/src/bin/multiple-tasks.rs b/examples/rt685s-evk-performance-tracing/src/bin/multiple-tasks.rs index ed3f7e12..fe719d17 100644 --- a/examples/rt685s-evk-performance-tracing/src/bin/multiple-tasks.rs +++ b/examples/rt685s-evk-performance-tracing/src/bin/multiple-tasks.rs @@ -4,6 +4,7 @@ extern crate embassy_imxrt_perf_examples; use core::sync::atomic::{AtomicBool, Ordering}; + use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_time::Timer; diff --git a/examples/rt685s-evk/src/bin/adc.rs b/examples/rt685s-evk/src/bin/adc.rs index 392df364..0803e61a 100644 --- a/examples/rt685s-evk/src/bin/adc.rs +++ b/examples/rt685s-evk/src/bin/adc.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::adc::{Adc, Average, ChannelConfig, Config, InterruptHandler}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { ADC0 => InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/benchmarking-gpio.rs b/examples/rt685s-evk/src/bin/benchmarking-gpio.rs index 4ae0a9cb..b4c3e0b1 100644 --- a/examples/rt685s-evk/src/bin/benchmarking-gpio.rs +++ b/examples/rt685s-evk/src/bin/benchmarking-gpio.rs @@ -2,11 +2,9 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs b/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs index 2a81fba0..702b2dbc 100644 --- a/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs +++ b/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs @@ -2,11 +2,9 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/button.rs b/examples/rt685s-evk/src/bin/button.rs index e9ea8433..7cc39ca2 100644 --- a/examples/rt685s-evk/src/bin/button.rs +++ b/examples/rt685s-evk/src/bin/button.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_futures::select::{Either, select}; use embassy_imxrt::gpio; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/clocks-blinky.rs b/examples/rt685s-evk/src/bin/clocks-blinky.rs index d9a09c2e..13a49288 100644 --- a/examples/rt685s-evk/src/bin/clocks-blinky.rs +++ b/examples/rt685s-evk/src/bin/clocks-blinky.rs @@ -2,14 +2,11 @@ #![no_std] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_imxrt as _; use embassy_imxrt::iopctl::IopctlPin; use embassy_imxrt::{clocks, gpio}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/crc.rs b/examples/rt685s-evk/src/bin/crc.rs index b400af15..d8c7ac8c 100644 --- a/examples/rt685s-evk/src/bin/crc.rs +++ b/examples/rt685s-evk/src/bin/crc.rs @@ -1,11 +1,9 @@ #![no_std] #![no_main] -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::crc::{Config, Crc, Polynomial}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs b/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs index 431c8393..d08a0fa8 100644 --- a/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs +++ b/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs @@ -2,14 +2,12 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_imxrt::pwm::{CentiPercent, MicroSeconds, Pwm}; use embassy_imxrt::timer::{CTimerPwm, CTimerPwmPeriodChannel}; -use embassy_imxrt_examples as _; use embassy_time::{Duration, with_timeout}; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/dma-mem.rs b/examples/rt685s-evk/src/bin/dma-mem.rs index 1d6a1fc6..e0a8f6c6 100644 --- a/examples/rt685s-evk/src/bin/dma-mem.rs +++ b/examples/rt685s-evk/src/bin/dma-mem.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::Peri; use embassy_imxrt::dma::Dma; use embassy_imxrt::dma::transfer::{Priority, Transfer, TransferOptions, Width}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const TEST_LEN: usize = 16; diff --git a/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs b/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs index bd3dbef1..f31f40db 100644 --- a/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs +++ b/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs @@ -1,12 +1,11 @@ #![no_std] #![no_main] -use defmt_rtt as _; use embassy_imxrt::flexspi::nor_flash::FlexSpiNorFlash; use embassy_imxrt::gpio; use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use futures::FutureExt as _; -use panic_probe as _; +use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: embassy_executor::Spawner) { diff --git a/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs b/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs index 7fe758a8..4cf40493 100644 --- a/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs +++ b/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs @@ -1,11 +1,10 @@ #![no_std] #![no_main] -use defmt_rtt as _; use embassy_imxrt::flexspi::nor_flash::FlexSpiNorFlash; use embassy_imxrt::gpio; use futures::FutureExt as _; -use panic_probe as _; +use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: embassy_executor::Spawner) { diff --git a/examples/rt685s-evk/src/bin/flexspi-storage-service.rs b/examples/rt685s-evk/src/bin/flexspi-storage-service.rs index 791030c8..cb80e8f4 100644 --- a/examples/rt685s-evk/src/bin/flexspi-storage-service.rs +++ b/examples/rt685s-evk/src/bin/flexspi-storage-service.rs @@ -2,7 +2,6 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::flexspi::nor_storage_bus::{ AhbConfig, FlexSpiFlashPort, FlexSpiFlashPortDeviceInstance, FlexspiAhbBufferConfig, FlexspiConfig, @@ -18,11 +17,11 @@ use embassy_time::Timer; use embedded_storage::nor_flash::{ ErrorType, NorFlash as BlockingNorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash as BlockingReadNorFlash, }; -use panic_probe as _; use storage_bus::nor::{ BlockingNorStorageBusDriver, NorStorageBusWidth, NorStorageCmd, NorStorageCmdMode, NorStorageCmdType, NorStorageDummyCycles, }; +use {defmt_rtt as _, panic_probe as _}; static ADDR: u32 = 0x3FD0000; diff --git a/examples/rt685s-evk/src/bin/gpio-async-input.rs b/examples/rt685s-evk/src/bin/gpio-async-input.rs index c855516f..680b915e 100644 --- a/examples/rt685s-evk/src/bin/gpio-async-input.rs +++ b/examples/rt685s-evk/src/bin/gpio-async-input.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::debug; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; -use embassy_imxrt_examples as _; use embassy_time::{Duration, Ticker}; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::task] async fn monitor_task(mut monitor: gpio::Input<'static>) { diff --git a/examples/rt685s-evk/src/bin/gpio-blinky.rs b/examples/rt685s-evk/src/bin/gpio-blinky.rs index a98bb048..9f140b92 100644 --- a/examples/rt685s-evk/src/bin/gpio-blinky.rs +++ b/examples/rt685s-evk/src/bin/gpio-blinky.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/gpio-flex.rs b/examples/rt685s-evk/src/bin/gpio-flex.rs index 3a8d2983..b518b995 100644 --- a/examples/rt685s-evk/src/bin/gpio-flex.rs +++ b/examples/rt685s-evk/src/bin/gpio-flex.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::{assert, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_imxrt::gpio::SenseDisabled; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/gpio-input.rs b/examples/rt685s-evk/src/bin/gpio-input.rs index 10b82f9a..ef608d68 100644 --- a/examples/rt685s-evk/src/bin/gpio-input.rs +++ b/examples/rt685s-evk/src/bin/gpio-input.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/hello-world.rs b/examples/rt685s-evk/src/bin/hello-world.rs index 07c9dc77..8f3c8f89 100644 --- a/examples/rt685s-evk/src/bin/hello-world.rs +++ b/examples/rt685s-evk/src/bin/hello-world.rs @@ -2,11 +2,9 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/i2c-master-async.rs b/examples/rt685s-evk/src/bin/i2c-master-async.rs index bf869a3d..09bc217e 100644 --- a/examples/rt685s-evk/src/bin/i2c-master-async.rs +++ b/examples/rt685s-evk/src/bin/i2c-master-async.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::{bind_interrupts, i2c, peripherals}; -use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_hal_async::i2c::I2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const NACK_ADDR: u8 = 0x07; diff --git a/examples/rt685s-evk/src/bin/i2c-master-drop.rs b/examples/rt685s-evk/src/bin/i2c-master-drop.rs index e26a15f1..e4ded6c3 100644 --- a/examples/rt685s-evk/src/bin/i2c-master-drop.rs +++ b/examples/rt685s-evk/src/bin/i2c-master-drop.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::{bind_interrupts, i2c, peripherals}; -use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_hal_async::i2c::I2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const ACC_ADDR: u8 = 0x1E; const ACC_ID_REG: u8 = 0x0D; diff --git a/examples/rt685s-evk/src/bin/i2c-master.rs b/examples/rt685s-evk/src/bin/i2c-master.rs index d481203b..554b78bc 100644 --- a/examples/rt685s-evk/src/bin/i2c-master.rs +++ b/examples/rt685s-evk/src/bin/i2c-master.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::{error, info}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c; -use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_hal_1::i2c::I2c; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const ACC_ADDR: u8 = 0x1E; diff --git a/examples/rt685s-evk/src/bin/keyboard.rs b/examples/rt685s-evk/src/bin/keyboard.rs index 2558c7fe..a84162ff 100644 --- a/examples/rt685s-evk/src/bin/keyboard.rs +++ b/examples/rt685s-evk/src/bin/keyboard.rs @@ -2,19 +2,17 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::clocks::delay_loop_clocks; use embassy_imxrt::gpio; use embassy_imxrt::gpio::{Input, Level, Output}; use embassy_imxrt::iopctl::{DriveMode, DriveStrength, Inverter, Pull, SlewRate}; -use embassy_imxrt_examples as _; use embassy_time::{Duration, Timer}; use keyberon::debounce::Debouncer; use keyberon::key_code::KbHidReport; use keyberon::layout::Layout; use keyberon::matrix::Matrix; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const COLS: usize = 4; const ROWS: usize = 4; diff --git a/examples/rt685s-evk/src/bin/pwm.rs b/examples/rt685s-evk/src/bin/pwm.rs index 99e1a456..bf2bc262 100644 --- a/examples/rt685s-evk/src/bin/pwm.rs +++ b/examples/rt685s-evk/src/bin/pwm.rs @@ -2,14 +2,12 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::pac; use embassy_imxrt::pwm::{CentiPercent, Channel, MicroSeconds, SCTClockSource, SCTPwm}; use embassy_imxrt::timer::{CTimerPwm, CTimerPwmPeriodChannel}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; // TODO: connect with GPIO port when that is ready fn setup_gpio() { diff --git a/examples/rt685s-evk/src/bin/rng.rs b/examples/rt685s-evk/src/bin/rng.rs index 8d8c3ad7..b0f668d6 100644 --- a/examples/rt685s-evk/src/bin/rng.rs +++ b/examples/rt685s-evk/src/bin/rng.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::rng::Rng; use embassy_imxrt::{bind_interrupts, peripherals, rng}; -use embassy_imxrt_examples as _; -use panic_probe as _; use rand::TryRngCore; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/rtc-alarm.rs b/examples/rt685s-evk/src/bin/rtc-alarm.rs index 9455c0e2..613c0742 100644 --- a/examples/rt685s-evk/src/bin/rtc-alarm.rs +++ b/examples/rt685s-evk/src/bin/rtc-alarm.rs @@ -4,13 +4,11 @@ use core::task::Poll; use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::rtc::{Rtc, RtcDatetimeClock}; -use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError, DatetimeFields, Month}; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; /// RTC alarm struct to await the time alarm wakeup location /// This should be implemented by the user to handle RTC peripheral synchronization diff --git a/examples/rt685s-evk/src/bin/rtc-nvram.rs b/examples/rt685s-evk/src/bin/rtc-nvram.rs index 3daf7031..ced6f9df 100644 --- a/examples/rt685s-evk/src/bin/rtc-nvram.rs +++ b/examples/rt685s-evk/src/bin/rtc-nvram.rs @@ -4,15 +4,13 @@ use core::cell::RefCell; use defmt::info; -use defmt_rtt as _; use embassy_imxrt::rtc::RtcNvramStorage; -use embassy_imxrt_examples as _; use embassy_sync::blocking_mutex::{CriticalSectionMutex, Mutex}; use embassy_sync::once_lock::OnceLock; use embassy_time::{Duration, Timer}; use embedded_mcu_hal::nvram::{Nvram, NvramStorage}; -use panic_probe as _; use static_cell::StaticCell; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; // Tasks demonstrating a way to share an NVRAM storage cell between multiple tasks #[embassy_executor::task] diff --git a/examples/rt685s-evk/src/bin/rtc-time.rs b/examples/rt685s-evk/src/bin/rtc-time.rs index c2f6447c..b338b15c 100644 --- a/examples/rt685s-evk/src/bin/rtc-time.rs +++ b/examples/rt685s-evk/src/bin/rtc-time.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::rtc::Rtc; -use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeFields, Month}; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/sha256-async.rs b/examples/rt685s-evk/src/bin/sha256-async.rs index e41dade0..373ccb56 100644 --- a/examples/rt685s-evk/src/bin/sha256-async.rs +++ b/examples/rt685s-evk/src/bin/sha256-async.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::{info, trace}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::hashcrypt::{self, Hashcrypt, hasher}; use embassy_imxrt::{bind_interrupts, peripherals}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { HASHCRYPT => hashcrypt::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/sha256.rs b/examples/rt685s-evk/src/bin/sha256.rs index 1a7cd7d0..6e3a76ca 100644 --- a/examples/rt685s-evk/src/bin/sha256.rs +++ b/examples/rt685s-evk/src/bin/sha256.rs @@ -2,11 +2,9 @@ #![no_main] use defmt::{info, trace}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::hashcrypt::{Hashcrypt, hasher}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/spi-async.rs b/examples/rt685s-evk/src/bin/spi-async.rs index 8b800973..0ced7d13 100644 --- a/examples/rt685s-evk/src/bin/spi-async.rs +++ b/examples/rt685s-evk/src/bin/spi-async.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::bind_interrupts; use embassy_imxrt::peripherals::FLEXCOMM5; use embassy_imxrt::spi::{InterruptHandler, Spi}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { FLEXCOMM5 => InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/spi-loopback.rs b/examples/rt685s-evk/src/bin/spi-loopback.rs index 90305d4c..cb22aab1 100644 --- a/examples/rt685s-evk/src/bin/spi-loopback.rs +++ b/examples/rt685s-evk/src/bin/spi-loopback.rs @@ -2,11 +2,9 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::spi::Spi; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/spi.rs b/examples/rt685s-evk/src/bin/spi.rs index a89837cb..a58d65e8 100644 --- a/examples/rt685s-evk/src/bin/spi.rs +++ b/examples/rt685s-evk/src/bin/spi.rs @@ -2,15 +2,13 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_imxrt::spi::Spi; -use embassy_imxrt_examples as _; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use is31fl3743b_driver::{CSy, Is31fl3743b, SWx}; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/time-driver-blinky.rs b/examples/rt685s-evk/src/bin/time-driver-blinky.rs index d0902fda..bebde9a9 100644 --- a/examples/rt685s-evk/src/bin/time-driver-blinky.rs +++ b/examples/rt685s-evk/src/bin/time-driver-blinky.rs @@ -2,13 +2,10 @@ #![no_std] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; -use embassy_imxrt as _; use embassy_imxrt::gpio; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { diff --git a/examples/rt685s-evk/src/bin/timer.rs b/examples/rt685s-evk/src/bin/timer.rs index 9c4c192e..4688e2ad 100644 --- a/examples/rt685s-evk/src/bin/timer.rs +++ b/examples/rt685s-evk/src/bin/timer.rs @@ -2,14 +2,12 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::clocks::ClockConfig; use embassy_imxrt::timer::{CaptureChEdge, CaptureTimer, CountingTimer}; use embassy_imxrt::{bind_interrupts, peripherals, timer}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { CTIMER0 => timer::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs b/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs index 3436ade0..bf65efc3 100644 --- a/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs +++ b/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart}; use embassy_imxrt::{bind_interrupts, peripherals, uart}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { FLEXCOMM2 => uart::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs b/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs index b848db1f..64029751 100644 --- a/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs +++ b/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart, UartRx}; use embassy_imxrt::{bind_interrupts, pac, peripherals, uart}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; const BUFLEN: usize = 512; const POLLING_RATE_US: u64 = 1000; diff --git a/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs b/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs index e912f560..636b4290 100644 --- a/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs +++ b/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart}; use embassy_imxrt::{bind_interrupts, peripherals, uart}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { FLEXCOMM2 => uart::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart-async.rs b/examples/rt685s-evk/src/bin/uart-async.rs index 472ed473..3910825a 100644 --- a/examples/rt685s-evk/src/bin/uart-async.rs +++ b/examples/rt685s-evk/src/bin/uart-async.rs @@ -2,13 +2,11 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart}; use embassy_imxrt::{bind_interrupts, peripherals, uart}; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { FLEXCOMM2 => uart::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart.rs b/examples/rt685s-evk/src/bin/uart.rs index 9f0b3c94..44616d6a 100644 --- a/examples/rt685s-evk/src/bin/uart.rs +++ b/examples/rt685s-evk/src/bin/uart.rs @@ -2,11 +2,9 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Uart, UartTx}; -use embassy_imxrt_examples as _; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/uuid.rs b/examples/rt685s-evk/src/bin/uuid.rs index b2e226ff..f21e4900 100644 --- a/examples/rt685s-evk/src/bin/uuid.rs +++ b/examples/rt685s-evk/src/bin/uuid.rs @@ -2,12 +2,10 @@ #![no_main] use defmt::info; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uuid::Uuid; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/wwdt.rs b/examples/rt685s-evk/src/bin/wwdt.rs index 58173b56..6fbf7cc9 100644 --- a/examples/rt685s-evk/src/bin/wwdt.rs +++ b/examples/rt685s-evk/src/bin/wwdt.rs @@ -3,13 +3,11 @@ use cortex_m::peripheral::NVIC; use defmt::{info, warn}; -use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::pac::{Interrupt, interrupt}; use embassy_imxrt::wwdt::WindowedWatchdog; -use embassy_imxrt_examples as _; use embassy_time::Timer; -use panic_probe as _; +use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { From b4298226ab4f040748d60de741d996a5bedeb7e3 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 06:54:33 -0700 Subject: [PATCH 11/27] supply-chain: reformat imports.lock with cargo-vet 0.10.2 CI's cargo-vet bumped from 0.10.1 to 0.10.2, which has stricter TOML string-literal normalisation: single-quoted literal strings are preferred over double-quoted-with-escaping for notes that contain bare double quotes, and triple-quoted multi-line strings whose interior contains no special characters get unescaped. All twelve affected lines are inside notes the Google audit aggregator imports verbatim (proc-macro-error / proc-macro-error-attr / proc-macro2 review notes); no audit semantics or trust entries change. Generated by running 'cargo vet fmt' against the existing store. --- supply-chain/imports.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 4109d72a..af781765 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -836,7 +836,7 @@ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_p who = "Lukasz Anforowicz " criteria = "safe-to-deploy" delta = "1.14.0 -> 1.15.0" -notes = "The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = \"std\")]`." +notes = 'The delta in `lib.rs` only tweaks doc comments and `#[cfg(feature = "std")]`.' aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.lazy_static]] @@ -901,8 +901,8 @@ who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.78" notes = """ -Grepped for \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits -(except for a benign \"fs\" hit in a doc comment) +Grepped for "crypt", "cipher", "fs", "net" - there were no hits +(except for a benign "fs" hit in a doc comment) Notes from the `unsafe` review can be found in https://crrev.com/c/5385745. """ @@ -1014,8 +1014,8 @@ who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.35" notes = """ -Grepped for \"unsafe\", \"crypt\", \"cipher\", \"fs\", \"net\" - there were no hits -(except for benign \"net\" hit in tests and \"fs\" hit in README.md) +Grepped for "unsafe", "crypt", "cipher", "fs", "net" - there were no hits +(except for benign "net" hit in tests and "fs" hit in README.md) """ aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" @@ -1092,7 +1092,7 @@ and there were no hits except for: * Using `unsafe` in a string: ``` - src/constfn.rs: \"unsafe\" => Qualifiers::Unsafe, + src/constfn.rs: "unsafe" => Qualifiers::Unsafe, ``` * Using `std::fs` in `build/build.rs` to write `${OUT_DIR}/version.expr` From 59d8b1a868739bab33f01cc07c022859a999c1dc Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 06:54:57 -0700 Subject: [PATCH 12/27] examples: reformat with newer nightly rustfmt CI's nightly rustfmt (1.9.0-nightly, rustc 1.98.0-nightly 6bdf43094 2026-06-01) prefers one-import-per-line for the throwaway 'use _ as _;' bindings used to anchor defmt-rtt, panic-probe, and embassy-imxrt-examples into the binary. The older nightly that was pinned locally collapsed those into braced groups; the current CI nightly expands them again. Pure formatter output across 47 example binaries + examples/rt633/src/lib.rs. No behavioural changes. Generated by running 'cargo +nightly fmt' in examples/rt685s-evk and examples/rt633 with rustfmt 1.9.0-nightly (6bdf43094f 2026-06-01). --- examples/rt633/src/bin/espi.rs | 4 +++- examples/rt633/src/bin/flexspi-nor-flash.rs | 4 +++- examples/rt633/src/lib.rs | 3 ++- examples/rt685s-evk/src/bin/adc.rs | 4 +++- examples/rt685s-evk/src/bin/benchmarking-gpio.rs | 4 +++- .../rt685s-evk/src/bin/benchmarking-system-peripheral.rs | 4 +++- examples/rt685s-evk/src/bin/button.rs | 4 +++- examples/rt685s-evk/src/bin/clocks-blinky.rs | 5 ++++- examples/rt685s-evk/src/bin/crc.rs | 4 +++- examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs | 4 +++- examples/rt685s-evk/src/bin/dma-mem.rs | 4 +++- examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs | 3 ++- examples/rt685s-evk/src/bin/flexspi-nor-flash.rs | 3 ++- examples/rt685s-evk/src/bin/flexspi-storage-service.rs | 3 ++- examples/rt685s-evk/src/bin/gpio-async-input.rs | 4 +++- examples/rt685s-evk/src/bin/gpio-blinky.rs | 4 +++- examples/rt685s-evk/src/bin/gpio-flex.rs | 4 +++- examples/rt685s-evk/src/bin/gpio-input.rs | 4 +++- examples/rt685s-evk/src/bin/hello-world.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-loopback-async.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-master-async.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-master-drop.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-master.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-slave-async.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs | 4 +++- examples/rt685s-evk/src/bin/i2c-slave.rs | 4 +++- examples/rt685s-evk/src/bin/keyboard.rs | 4 +++- examples/rt685s-evk/src/bin/pwm.rs | 4 +++- examples/rt685s-evk/src/bin/rng.rs | 4 +++- examples/rt685s-evk/src/bin/rtc-alarm.rs | 4 +++- examples/rt685s-evk/src/bin/rtc-nvram.rs | 4 +++- examples/rt685s-evk/src/bin/rtc-time.rs | 4 +++- examples/rt685s-evk/src/bin/sha256-async.rs | 4 +++- examples/rt685s-evk/src/bin/sha256.rs | 4 +++- examples/rt685s-evk/src/bin/spi-async.rs | 4 +++- examples/rt685s-evk/src/bin/spi-loopback.rs | 4 +++- examples/rt685s-evk/src/bin/spi.rs | 4 +++- examples/rt685s-evk/src/bin/time-driver-blinky.rs | 5 ++++- examples/rt685s-evk/src/bin/timer.rs | 4 +++- examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs | 4 +++- examples/rt685s-evk/src/bin/uart-async-with-buffer.rs | 4 +++- examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs | 4 +++- examples/rt685s-evk/src/bin/uart-async.rs | 4 +++- examples/rt685s-evk/src/bin/uart.rs | 4 +++- examples/rt685s-evk/src/bin/uuid.rs | 4 +++- examples/rt685s-evk/src/bin/wwdt.rs | 4 +++- 48 files changed, 142 insertions(+), 48 deletions(-) diff --git a/examples/rt633/src/bin/espi.rs b/examples/rt633/src/bin/espi.rs index d8e5b4d7..ebd2556f 100644 --- a/examples/rt633/src/bin/espi.rs +++ b/examples/rt633/src/bin/espi.rs @@ -4,6 +4,7 @@ use core::slice; use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::bind_interrupts; use embassy_imxrt::espi::{ @@ -11,7 +12,8 @@ use embassy_imxrt::espi::{ PortConfig, }; use embassy_imxrt::peripherals::ESPI; -use {defmt_rtt as _, panic_probe as _, rt633_examples as _}; +use panic_probe as _; +use rt633_examples as _; bind_interrupts!(struct Irqs { ESPI => InterruptHandler; diff --git a/examples/rt633/src/bin/flexspi-nor-flash.rs b/examples/rt633/src/bin/flexspi-nor-flash.rs index 86dd9c12..f1bd6fdb 100644 --- a/examples/rt633/src/bin/flexspi-nor-flash.rs +++ b/examples/rt633/src/bin/flexspi-nor-flash.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::flexspi::nor_flash::FlexSpiNorFlash; use embassy_imxrt::gpio; use embassy_time::Timer; -use {defmt_rtt as _, panic_probe as _, rt633_examples as _}; +use panic_probe as _; +use rt633_examples as _; /* This example demonstrates usage of the FlexSPI NOR flash driver to read, erase, and program. This is for the lower level flash operations that is not safe. Application should use the diff --git a/examples/rt633/src/lib.rs b/examples/rt633/src/lib.rs index 11091585..6ca3877e 100644 --- a/examples/rt633/src/lib.rs +++ b/examples/rt633/src/lib.rs @@ -1,11 +1,12 @@ #![no_std] +use defmt_rtt as _; use mimxrt600_fcb::FlexSpiLutOpcode::{CMD_SDR, RADDR_SDR, READ_SDR, STOP, WRITE_SDR}; use mimxrt600_fcb::FlexSpiNumPads::Single; use mimxrt600_fcb::{ ControllerMiscOption, FlexSPIFlashConfigurationBlock, SFlashPadType, SerialClkFreq, SerialNORType, flexspi_lut_seq, }; -use {defmt_rtt as _, panic_probe as _}; +use panic_probe as _; // auto-generated version information from Cargo.toml include!(concat!(env!("OUT_DIR"), "/biv.rs")); diff --git a/examples/rt685s-evk/src/bin/adc.rs b/examples/rt685s-evk/src/bin/adc.rs index 0803e61a..392df364 100644 --- a/examples/rt685s-evk/src/bin/adc.rs +++ b/examples/rt685s-evk/src/bin/adc.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::adc::{Adc, Average, ChannelConfig, Config, InterruptHandler}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; bind_interrupts!(struct Irqs { ADC0 => InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/benchmarking-gpio.rs b/examples/rt685s-evk/src/bin/benchmarking-gpio.rs index b4c3e0b1..4ae0a9cb 100644 --- a/examples/rt685s-evk/src/bin/benchmarking-gpio.rs +++ b/examples/rt685s-evk/src/bin/benchmarking-gpio.rs @@ -2,9 +2,11 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs b/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs index 702b2dbc..2a81fba0 100644 --- a/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs +++ b/examples/rt685s-evk/src/bin/benchmarking-system-peripheral.rs @@ -2,9 +2,11 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/button.rs b/examples/rt685s-evk/src/bin/button.rs index 7cc39ca2..e9ea8433 100644 --- a/examples/rt685s-evk/src/bin/button.rs +++ b/examples/rt685s-evk/src/bin/button.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_futures::select::{Either, select}; use embassy_imxrt::gpio; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/clocks-blinky.rs b/examples/rt685s-evk/src/bin/clocks-blinky.rs index 13a49288..d9a09c2e 100644 --- a/examples/rt685s-evk/src/bin/clocks-blinky.rs +++ b/examples/rt685s-evk/src/bin/clocks-blinky.rs @@ -2,11 +2,14 @@ #![no_std] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; +use embassy_imxrt as _; use embassy_imxrt::iopctl::IopctlPin; use embassy_imxrt::{clocks, gpio}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/crc.rs b/examples/rt685s-evk/src/bin/crc.rs index d8c7ac8c..b400af15 100644 --- a/examples/rt685s-evk/src/bin/crc.rs +++ b/examples/rt685s-evk/src/bin/crc.rs @@ -1,9 +1,11 @@ #![no_std] #![no_main] +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::crc::{Config, Crc, Polynomial}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs b/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs index d08a0fa8..431c8393 100644 --- a/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs +++ b/examples/rt685s-evk/src/bin/ctimer-pwm-0-100.rs @@ -2,12 +2,14 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_imxrt::pwm::{CentiPercent, MicroSeconds, Pwm}; use embassy_imxrt::timer::{CTimerPwm, CTimerPwmPeriodChannel}; +use embassy_imxrt_examples as _; use embassy_time::{Duration, with_timeout}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/dma-mem.rs b/examples/rt685s-evk/src/bin/dma-mem.rs index e0a8f6c6..1d6a1fc6 100644 --- a/examples/rt685s-evk/src/bin/dma-mem.rs +++ b/examples/rt685s-evk/src/bin/dma-mem.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::Peri; use embassy_imxrt::dma::Dma; use embassy_imxrt::dma::transfer::{Priority, Transfer, TransferOptions, Width}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; const TEST_LEN: usize = 16; diff --git a/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs b/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs index f31f40db..bd3dbef1 100644 --- a/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs +++ b/examples/rt685s-evk/src/bin/flexspi-embedded-storage.rs @@ -1,11 +1,12 @@ #![no_std] #![no_main] +use defmt_rtt as _; use embassy_imxrt::flexspi::nor_flash::FlexSpiNorFlash; use embassy_imxrt::gpio; use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use futures::FutureExt as _; -use {defmt_rtt as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: embassy_executor::Spawner) { diff --git a/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs b/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs index 4cf40493..7fe758a8 100644 --- a/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs +++ b/examples/rt685s-evk/src/bin/flexspi-nor-flash.rs @@ -1,10 +1,11 @@ #![no_std] #![no_main] +use defmt_rtt as _; use embassy_imxrt::flexspi::nor_flash::FlexSpiNorFlash; use embassy_imxrt::gpio; use futures::FutureExt as _; -use {defmt_rtt as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: embassy_executor::Spawner) { diff --git a/examples/rt685s-evk/src/bin/flexspi-storage-service.rs b/examples/rt685s-evk/src/bin/flexspi-storage-service.rs index cb80e8f4..791030c8 100644 --- a/examples/rt685s-evk/src/bin/flexspi-storage-service.rs +++ b/examples/rt685s-evk/src/bin/flexspi-storage-service.rs @@ -2,6 +2,7 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::flexspi::nor_storage_bus::{ AhbConfig, FlexSpiFlashPort, FlexSpiFlashPortDeviceInstance, FlexspiAhbBufferConfig, FlexspiConfig, @@ -17,11 +18,11 @@ use embassy_time::Timer; use embedded_storage::nor_flash::{ ErrorType, NorFlash as BlockingNorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash as BlockingReadNorFlash, }; +use panic_probe as _; use storage_bus::nor::{ BlockingNorStorageBusDriver, NorStorageBusWidth, NorStorageCmd, NorStorageCmdMode, NorStorageCmdType, NorStorageDummyCycles, }; -use {defmt_rtt as _, panic_probe as _}; static ADDR: u32 = 0x3FD0000; diff --git a/examples/rt685s-evk/src/bin/gpio-async-input.rs b/examples/rt685s-evk/src/bin/gpio-async-input.rs index 680b915e..c855516f 100644 --- a/examples/rt685s-evk/src/bin/gpio-async-input.rs +++ b/examples/rt685s-evk/src/bin/gpio-async-input.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::debug; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; +use embassy_imxrt_examples as _; use embassy_time::{Duration, Ticker}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::task] async fn monitor_task(mut monitor: gpio::Input<'static>) { diff --git a/examples/rt685s-evk/src/bin/gpio-blinky.rs b/examples/rt685s-evk/src/bin/gpio-blinky.rs index 9f140b92..a98bb048 100644 --- a/examples/rt685s-evk/src/bin/gpio-blinky.rs +++ b/examples/rt685s-evk/src/bin/gpio-blinky.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/gpio-flex.rs b/examples/rt685s-evk/src/bin/gpio-flex.rs index b518b995..3a8d2983 100644 --- a/examples/rt685s-evk/src/bin/gpio-flex.rs +++ b/examples/rt685s-evk/src/bin/gpio-flex.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::{assert, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_imxrt::gpio::SenseDisabled; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/gpio-input.rs b/examples/rt685s-evk/src/bin/gpio-input.rs index ef608d68..10b82f9a 100644 --- a/examples/rt685s-evk/src/bin/gpio-input.rs +++ b/examples/rt685s-evk/src/bin/gpio-input.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/hello-world.rs b/examples/rt685s-evk/src/bin/hello-world.rs index 8f3c8f89..07c9dc77 100644 --- a/examples/rt685s-evk/src/bin/hello-world.rs +++ b/examples/rt685s-evk/src/bin/hello-world.rs @@ -2,9 +2,11 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs b/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs index caf3f207..fba1f65b 100644 --- a/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs +++ b/examples/rt685s-evk/src/bin/i2c-loopback-async-10bit.rs @@ -2,13 +2,15 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::master::I2cMaster; use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_imxrt_examples as _; use embedded_hal_async::i2c::I2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const ADDR: u16 = 0x0123; const MASTER_BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/i2c-loopback-async.rs b/examples/rt685s-evk/src/bin/i2c-loopback-async.rs index 29e00f12..fbdbf503 100644 --- a/examples/rt685s-evk/src/bin/i2c-loopback-async.rs +++ b/examples/rt685s-evk/src/bin/i2c-loopback-async.rs @@ -2,13 +2,15 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::master::{DutyCycle, I2cMaster}; use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_imxrt_examples as _; use embedded_hal_async::i2c::I2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const ADDR: u8 = 0x20; const MASTER_BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/i2c-master-async.rs b/examples/rt685s-evk/src/bin/i2c-master-async.rs index 09bc217e..bf869a3d 100644 --- a/examples/rt685s-evk/src/bin/i2c-master-async.rs +++ b/examples/rt685s-evk/src/bin/i2c-master-async.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::{bind_interrupts, i2c, peripherals}; +use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_hal_async::i2c::I2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const NACK_ADDR: u8 = 0x07; diff --git a/examples/rt685s-evk/src/bin/i2c-master-drop.rs b/examples/rt685s-evk/src/bin/i2c-master-drop.rs index e4ded6c3..e26a15f1 100644 --- a/examples/rt685s-evk/src/bin/i2c-master-drop.rs +++ b/examples/rt685s-evk/src/bin/i2c-master-drop.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::{bind_interrupts, i2c, peripherals}; +use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_hal_async::i2c::I2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const ACC_ADDR: u8 = 0x1E; const ACC_ID_REG: u8 = 0x0D; diff --git a/examples/rt685s-evk/src/bin/i2c-master.rs b/examples/rt685s-evk/src/bin/i2c-master.rs index 554b78bc..d481203b 100644 --- a/examples/rt685s-evk/src/bin/i2c-master.rs +++ b/examples/rt685s-evk/src/bin/i2c-master.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::{error, info}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c; +use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_hal_1::i2c::I2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const ACC_ADDR: u8 = 0x1E; diff --git a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs index 82ce0b52..090331cc 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs @@ -26,16 +26,18 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::slave::{Address, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_imxrt_examples as _; // Bring the target trait methods into scope so we go through the trait // instead of the inherent API. use embedded_mcu_hal::i2c::SevenBitAddress; use embedded_mcu_hal::i2c::target::Request; use embedded_mcu_hal::i2c::target::asynch::I2c as TargetI2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/i2c-slave-async.rs b/examples/rt685s-evk/src/bin/i2c-slave-async.rs index 6855bdcc..f5324160 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-async.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-async.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs index 1239396e..5bdb6e53 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs @@ -13,13 +13,15 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::Blocking; use embassy_imxrt::i2c::slave::{Address, I2cSlave}; +use embassy_imxrt_examples as _; use embedded_mcu_hal::i2c::SevenBitAddress; use embedded_mcu_hal::i2c::target::Request; use embedded_mcu_hal::i2c::target::blocking::I2c as TargetI2c; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/i2c-slave.rs b/examples/rt685s-evk/src/bin/i2c-slave.rs index e716c788..253d9289 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::Blocking; use embassy_imxrt::i2c::slave::{Address, Command, I2cSlave}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; const SLAVE_ADDR: Option
= Address::new(0x20); const BUFLEN: usize = 8; diff --git a/examples/rt685s-evk/src/bin/keyboard.rs b/examples/rt685s-evk/src/bin/keyboard.rs index a84162ff..2558c7fe 100644 --- a/examples/rt685s-evk/src/bin/keyboard.rs +++ b/examples/rt685s-evk/src/bin/keyboard.rs @@ -2,17 +2,19 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::clocks::delay_loop_clocks; use embassy_imxrt::gpio; use embassy_imxrt::gpio::{Input, Level, Output}; use embassy_imxrt::iopctl::{DriveMode, DriveStrength, Inverter, Pull, SlewRate}; +use embassy_imxrt_examples as _; use embassy_time::{Duration, Timer}; use keyberon::debounce::Debouncer; use keyberon::key_code::KbHidReport; use keyberon::layout::Layout; use keyberon::matrix::Matrix; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; const COLS: usize = 4; const ROWS: usize = 4; diff --git a/examples/rt685s-evk/src/bin/pwm.rs b/examples/rt685s-evk/src/bin/pwm.rs index bf2bc262..99e1a456 100644 --- a/examples/rt685s-evk/src/bin/pwm.rs +++ b/examples/rt685s-evk/src/bin/pwm.rs @@ -2,12 +2,14 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::pac; use embassy_imxrt::pwm::{CentiPercent, Channel, MicroSeconds, SCTClockSource, SCTPwm}; use embassy_imxrt::timer::{CTimerPwm, CTimerPwmPeriodChannel}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; // TODO: connect with GPIO port when that is ready fn setup_gpio() { diff --git a/examples/rt685s-evk/src/bin/rng.rs b/examples/rt685s-evk/src/bin/rng.rs index b0f668d6..8d8c3ad7 100644 --- a/examples/rt685s-evk/src/bin/rng.rs +++ b/examples/rt685s-evk/src/bin/rng.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::rng::Rng; use embassy_imxrt::{bind_interrupts, peripherals, rng}; +use embassy_imxrt_examples as _; +use panic_probe as _; use rand::TryRngCore; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; bind_interrupts!(struct Irqs { RNG => rng::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/rtc-alarm.rs b/examples/rt685s-evk/src/bin/rtc-alarm.rs index 613c0742..9455c0e2 100644 --- a/examples/rt685s-evk/src/bin/rtc-alarm.rs +++ b/examples/rt685s-evk/src/bin/rtc-alarm.rs @@ -4,11 +4,13 @@ use core::task::Poll; use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::rtc::{Rtc, RtcDatetimeClock}; +use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeClockError, DatetimeFields, Month}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; /// RTC alarm struct to await the time alarm wakeup location /// This should be implemented by the user to handle RTC peripheral synchronization diff --git a/examples/rt685s-evk/src/bin/rtc-nvram.rs b/examples/rt685s-evk/src/bin/rtc-nvram.rs index ced6f9df..3daf7031 100644 --- a/examples/rt685s-evk/src/bin/rtc-nvram.rs +++ b/examples/rt685s-evk/src/bin/rtc-nvram.rs @@ -4,13 +4,15 @@ use core::cell::RefCell; use defmt::info; +use defmt_rtt as _; use embassy_imxrt::rtc::RtcNvramStorage; +use embassy_imxrt_examples as _; use embassy_sync::blocking_mutex::{CriticalSectionMutex, Mutex}; use embassy_sync::once_lock::OnceLock; use embassy_time::{Duration, Timer}; use embedded_mcu_hal::nvram::{Nvram, NvramStorage}; +use panic_probe as _; use static_cell::StaticCell; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; // Tasks demonstrating a way to share an NVRAM storage cell between multiple tasks #[embassy_executor::task] diff --git a/examples/rt685s-evk/src/bin/rtc-time.rs b/examples/rt685s-evk/src/bin/rtc-time.rs index b338b15c..c2f6447c 100644 --- a/examples/rt685s-evk/src/bin/rtc-time.rs +++ b/examples/rt685s-evk/src/bin/rtc-time.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::rtc::Rtc; +use embassy_imxrt_examples as _; use embassy_time::Timer; use embedded_mcu_hal::time::{Datetime, DatetimeClock, DatetimeFields, Month}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/sha256-async.rs b/examples/rt685s-evk/src/bin/sha256-async.rs index 373ccb56..e41dade0 100644 --- a/examples/rt685s-evk/src/bin/sha256-async.rs +++ b/examples/rt685s-evk/src/bin/sha256-async.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::{info, trace}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::hashcrypt::{self, Hashcrypt, hasher}; use embassy_imxrt::{bind_interrupts, peripherals}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; bind_interrupts!(struct Irqs { HASHCRYPT => hashcrypt::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/sha256.rs b/examples/rt685s-evk/src/bin/sha256.rs index 6e3a76ca..1a7cd7d0 100644 --- a/examples/rt685s-evk/src/bin/sha256.rs +++ b/examples/rt685s-evk/src/bin/sha256.rs @@ -2,9 +2,11 @@ #![no_main] use defmt::{info, trace}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::hashcrypt::{Hashcrypt, hasher}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/spi-async.rs b/examples/rt685s-evk/src/bin/spi-async.rs index 0ced7d13..8b800973 100644 --- a/examples/rt685s-evk/src/bin/spi-async.rs +++ b/examples/rt685s-evk/src/bin/spi-async.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::bind_interrupts; use embassy_imxrt::peripherals::FLEXCOMM5; use embassy_imxrt::spi::{InterruptHandler, Spi}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; bind_interrupts!(struct Irqs { FLEXCOMM5 => InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/spi-loopback.rs b/examples/rt685s-evk/src/bin/spi-loopback.rs index cb22aab1..90305d4c 100644 --- a/examples/rt685s-evk/src/bin/spi-loopback.rs +++ b/examples/rt685s-evk/src/bin/spi-loopback.rs @@ -2,9 +2,11 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::spi::Spi; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/spi.rs b/examples/rt685s-evk/src/bin/spi.rs index a58d65e8..a89837cb 100644 --- a/examples/rt685s-evk/src/bin/spi.rs +++ b/examples/rt685s-evk/src/bin/spi.rs @@ -2,13 +2,15 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::gpio; use embassy_imxrt::spi::Spi; +use embassy_imxrt_examples as _; use embassy_time::{Delay, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; use is31fl3743b_driver::{CSy, Is31fl3743b, SWx}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/time-driver-blinky.rs b/examples/rt685s-evk/src/bin/time-driver-blinky.rs index bebde9a9..d0902fda 100644 --- a/examples/rt685s-evk/src/bin/time-driver-blinky.rs +++ b/examples/rt685s-evk/src/bin/time-driver-blinky.rs @@ -2,10 +2,13 @@ #![no_std] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; +use embassy_imxrt as _; use embassy_imxrt::gpio; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { diff --git a/examples/rt685s-evk/src/bin/timer.rs b/examples/rt685s-evk/src/bin/timer.rs index 4688e2ad..9c4c192e 100644 --- a/examples/rt685s-evk/src/bin/timer.rs +++ b/examples/rt685s-evk/src/bin/timer.rs @@ -2,12 +2,14 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::clocks::ClockConfig; use embassy_imxrt::timer::{CaptureChEdge, CaptureTimer, CountingTimer}; use embassy_imxrt::{bind_interrupts, peripherals, timer}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; bind_interrupts!(struct Irqs { CTIMER0 => timer::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs b/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs index bf65efc3..3436ade0 100644 --- a/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs +++ b/examples/rt685s-evk/src/bin/uart-async-hw-flow-control.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart}; use embassy_imxrt::{bind_interrupts, peripherals, uart}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; bind_interrupts!(struct Irqs { FLEXCOMM2 => uart::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs b/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs index 64029751..b848db1f 100644 --- a/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs +++ b/examples/rt685s-evk/src/bin/uart-async-with-buffer.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart, UartRx}; use embassy_imxrt::{bind_interrupts, pac, peripherals, uart}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; const BUFLEN: usize = 512; const POLLING_RATE_US: u64 = 1000; diff --git a/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs b/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs index 636b4290..e912f560 100644 --- a/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs +++ b/examples/rt685s-evk/src/bin/uart-async-with-rtscts-buffer.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart}; use embassy_imxrt::{bind_interrupts, peripherals, uart}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; bind_interrupts!(struct Irqs { FLEXCOMM2 => uart::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart-async.rs b/examples/rt685s-evk/src/bin/uart-async.rs index 3910825a..472ed473 100644 --- a/examples/rt685s-evk/src/bin/uart-async.rs +++ b/examples/rt685s-evk/src/bin/uart-async.rs @@ -2,11 +2,13 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Async, Uart}; use embassy_imxrt::{bind_interrupts, peripherals, uart}; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; bind_interrupts!(struct Irqs { FLEXCOMM2 => uart::InterruptHandler; diff --git a/examples/rt685s-evk/src/bin/uart.rs b/examples/rt685s-evk/src/bin/uart.rs index 44616d6a..9f0b3c94 100644 --- a/examples/rt685s-evk/src/bin/uart.rs +++ b/examples/rt685s-evk/src/bin/uart.rs @@ -2,9 +2,11 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uart::{Uart, UartTx}; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use embassy_imxrt_examples as _; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/uuid.rs b/examples/rt685s-evk/src/bin/uuid.rs index f21e4900..b2e226ff 100644 --- a/examples/rt685s-evk/src/bin/uuid.rs +++ b/examples/rt685s-evk/src/bin/uuid.rs @@ -2,10 +2,12 @@ #![no_main] use defmt::info; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::uuid::Uuid; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/examples/rt685s-evk/src/bin/wwdt.rs b/examples/rt685s-evk/src/bin/wwdt.rs index 6fbf7cc9..58173b56 100644 --- a/examples/rt685s-evk/src/bin/wwdt.rs +++ b/examples/rt685s-evk/src/bin/wwdt.rs @@ -3,11 +3,13 @@ use cortex_m::peripheral::NVIC; use defmt::{info, warn}; +use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::pac::{Interrupt, interrupt}; use embassy_imxrt::wwdt::WindowedWatchdog; +use embassy_imxrt_examples as _; use embassy_time::Timer; -use {defmt_rtt as _, embassy_imxrt_examples as _, panic_probe as _}; +use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { From 04cfe79a5085a13f3276eaea297ca03ea10b3861 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 07:25:35 -0700 Subject: [PATCH 13/27] i2c::slave: enforce address-mode guard on every trait respond_* MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The embedded_mcu_hal::i2c::target impls already check the configured address mode in listen(), but respond_to_read() and respond_to_write() trusted the caller to go through listen() first. A misbehaving caller that drives the wrong trait impl (e.g. I2c on a 10-bit-configured slave) directly into respond_to_* would have skipped the guard and driven the peripheral with mismatched semantics. Add self.require_address_kind(want_ten_bit)? as the first statement of every respond_to_read() and respond_to_write() across all four trait impls (Blocking SevenBit/TenBit, Async SevenBit/TenBit). The guard is O(1) — one Option::is_some + one equality compare — and returns Error::UnsupportedConfiguration on mismatch, which maps to ErrorKind::Other at the trait surface (per the existing Error::kind() implementation tested in error_kind_mapping_covers_every_transfer_variant). Of the eight respond_to_* methods, one (Blocking SevenBitAddress::respond_to_read) was already guarded by the auto-merged Copilot autofix in 273ec2e; this commit adds the remaining seven. The result is uniform address-mode rejection across the full trait API surface, regardless of call ordering. --- src/i2c/slave.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index 57411b6e..b6deda03 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -1221,10 +1221,12 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { } fn respond_to_read(&mut self, buf: &[u8]) -> Result { + self.require_address_kind(false)?; Ok(resp_to_read_status(I2cSlave::::respond_to_read(self, buf)?)) } fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + self.require_address_kind(false)?; Ok(resp_to_write_status(I2cSlave::::respond_to_write(self, buf)?)) } } @@ -1247,10 +1249,12 @@ impl mcu_target::blocking::I2c for I2cSlave<'_, Blocking> { } fn respond_to_read(&mut self, buf: &[u8]) -> Result { + self.require_address_kind(true)?; Ok(resp_to_read_status(I2cSlave::::respond_to_read(self, buf)?)) } fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + self.require_address_kind(true)?; Ok(resp_to_write_status(I2cSlave::::respond_to_write(self, buf)?)) } } @@ -1273,12 +1277,14 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { } async fn respond_to_read(&mut self, buf: &[u8]) -> Result { + self.require_address_kind(false)?; Ok(resp_to_read_status( I2cSlave::::respond_to_read(self, buf).await?, )) } async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + self.require_address_kind(false)?; Ok(resp_to_write_status( I2cSlave::::respond_to_write(self, buf).await?, )) @@ -1303,12 +1309,14 @@ impl mcu_target::asynch::I2c for I2cSlave<'_, Async> { } async fn respond_to_read(&mut self, buf: &[u8]) -> Result { + self.require_address_kind(true)?; Ok(resp_to_read_status( I2cSlave::::respond_to_read(self, buf).await?, )) } async fn respond_to_write(&mut self, buf: &mut [u8]) -> Result { + self.require_address_kind(true)?; Ok(resp_to_write_status( I2cSlave::::respond_to_write(self, buf).await?, )) From dc345fdfa61b4e80b798be594825d7a71729310c Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 07:38:50 -0700 Subject: [PATCH 14/27] examples: add race-watching telemetry to i2c target-trait demos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The async/blocking i2c-slave-*-target-trait examples now emit two `warn!` lines when the slave observes event shapes associated with the `slv_state -> addressed` mid-DMA HW race tracked on PR #565: 1. `WriteStatus::Restarted(0)` — a zero-byte restart should not occur on a healthy bus; a real repeated START is preceded by at least one ACKed payload byte. Seeing it strongly suggests the slave mis-classified an in-progress receive as a restart. 2. `Request::RepeatedStart(_)` arriving when the prior `respond_to_*` did not report `Restarted(_)`. The queued edge that surfaces as `RepeatedStart` should always have a matching upstream `Restarted`. Both signatures are tracked via a single local `expect_repeated_start: bool` flipped on `Restarted` and consumed on the next `listen()`. `info!` `(consumed expected RepeatedStart edge before Read/Write)` is also logged on the healthy combined-format path so the RTT capture has a clear positive marker, not just the negative warning. Drives the race-watch surface complementary to the host-side `write-read-soak` harness: the host call times out and prints the iteration that wedged; the on-target RTT log identifies which mis-classification the slave made when it stopped clocking. Also drops the stale module-rustdoc bullet on the async example that claimed `recover()` was called from the `Restarted` branch as a no-op demonstration — the actual code (rightly) does not call it, because a `Restarted` is a healthy mid-transaction continuation. Replaced with explicit `recover()` is not called on the happy path language, plus a new `Race-watching telemetry` section in the rustdoc that documents the two warnings and points at the soak tool. Addresses Copilot review thread PRRT_kwDOMUqVSs6Gc48a on PR #565. No behavioural change to the trait API or the inherent `I2cSlave` driver. Verified locally: cargo +stable build --release --bin i2c-slave-async-target-trait --bin i2c-slave-target-trait (clean), cargo +stable clippy --locked -- -Dwarnings -D clippy::suspicious -D clippy::correctness -D clippy::perf -D clippy::style on examples/rt685s-evk (clean), cargo +nightly fmt --check (clean). --- .../src/bin/i2c-slave-async-target-trait.rs | 72 +++++++++++++++++-- .../src/bin/i2c-slave-target-trait.rs | 51 ++++++++++++- 2 files changed, 118 insertions(+), 5 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs index 090331cc..1d2f5571 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-async-target-trait.rs @@ -14,9 +14,29 @@ //! (for reads). //! * `RepeatedStart(prev_addr)` is surfaced as a separate `listen()` event //! between a write and a read on the same controller transaction. -//! * `recover()` is called from the `Restarted` branch as a no-op -//! demonstration; in production it would only be needed after a -//! cancelled `respond_*` future. +//! * `recover()` is **not** called on the happy path. The `Restarted` +//! branch deliberately leaves the in-flight transaction alone so the +//! queued `RepeatedStart` edge surfaces on the next `listen()`. Reserve +//! `recover()` for wedged / cancelled transfers — e.g. after dropping a +//! `respond_to_*` future mid-transaction. +//! +//! ## Race-watching telemetry +//! +//! Two `warn!` emissions in this example flag known-suspicious shapes +//! associated with the `slv_state -> addressed` mid-DMA HW race tracked +//! on PR #565: +//! +//! 1. `WriteStatus::Restarted(0)` — a zero-byte restart should not occur +//! on a healthy bus: a real repeated START is preceded by at least one +//! ACKed payload byte. Seeing this strongly suggests the slave +//! mis-classified an in-progress receive as a restart. +//! 2. `Request::RepeatedStart(_)` arriving when the prior `respond_to_*` +//! did **not** report `Restarted(_)`. The queued edge that produces +//! `RepeatedStart` should always have a matching upstream `Restarted`. +//! +//! Pair this binary with `tools/i2c-target-test write-read-soak` for the +//! reproducer; `defmt-print` the RTT channel to capture the on-target +//! event history when the soak fails fast. //! //! Tested against the same Raspberry Pi 5 master rig as the existing //! `i2c-slave-async.rs` example @@ -25,7 +45,7 @@ #![no_std] #![no_main] -use defmt::info; +use defmt::{info, warn}; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::slave::{Address, I2cSlave}; @@ -48,6 +68,13 @@ bind_interrupts!(struct Irqs { #[embassy_executor::task] async fn slave_service(mut i2c: I2cSlave<'static, Async>) { + // Tracks whether the most recent respond_to_* terminator was + // `Restarted(_)`. The very next `listen()` is expected to return + // `Request::RepeatedStart(_)`; any other shape indicates an event + // mismatch worth investigating. See the module-level "Race-watching + // telemetry" docs. + let mut expect_repeated_start = false; + loop { let mut buf: [u8; BUFLEN] = [0xAA; BUFLEN]; @@ -63,23 +90,48 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { Ok(r) => r, Err(e) => { info!("listen error: {:?}", defmt::Debug2Format(&e)); + expect_repeated_start = false; continue; } }; + let was_expecting_restart = expect_repeated_start; + expect_repeated_start = false; + match req { Request::Stop(addr) => { // A probe (address-only transaction terminated by STOP) // surfaces here. The inherent API reports the same event // as `Command::Probe { addr }`. info!("Stop @ 0x{:02X} (probe)", addr); + if was_expecting_restart { + warn!( + "RACE WATCH: prior respond_to_* reported Restarted but listen() \ + returned Stop(0x{:02X}); expected RepeatedStart", + addr + ); + } } Request::RepeatedStart(prev_addr) => { // Surfaced when a previous respond_to_* observed a Sr. info!("RepeatedStart from prev @ 0x{:02X}", prev_addr); + if !was_expecting_restart { + warn!( + "RACE WATCH: RepeatedStart(0x{:02X}) surfaced without a prior \ + Restarted(_) — likely a spurious edge synthesised from a \ + mid-DMA SlaveAddress mis-classification", + prev_addr + ); + } } Request::Read(addr) => { info!("Read @ 0x{:02X}", addr); + if was_expecting_restart { + // A Read after a Restarted is a normal combined-format + // transaction; the RepeatedStart event was consumed + // implicitly by the trait impl. + info!("(consumed expected RepeatedStart edge before Read)"); + } loop { use embedded_mcu_hal::i2c::target::ReadStatus; match TargetI2c::::respond_to_read(&mut i2c, &buf).await { @@ -112,6 +164,9 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { } Request::Write(addr) => { info!("Write @ 0x{:02X}", addr); + if was_expecting_restart { + info!("(consumed expected RepeatedStart edge before Write)"); + } loop { use embedded_mcu_hal::i2c::target::WriteStatus; match TargetI2c::::respond_to_write(&mut i2c, &mut buf).await { @@ -124,6 +179,14 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { "Write restarted after {} bytes — next listen will surface RepeatedStart", n ); + if n == 0 { + warn!( + "RACE WATCH: WriteStatus::Restarted(0) — zero-byte restart \ + should not occur on a healthy bus (Sr is preceded by at \ + least one ACKed payload byte). Likely the slv_state -> \ + addressed mid-DMA HW race noted in PR #565." + ); + } // Do NOT call recover() here: a Restarted is a // healthy continuation of an in-flight master // transaction (Sr + ADDR+R/W is queued on the @@ -133,6 +196,7 @@ async fn slave_service(mut i2c: I2cSlave<'static, Async>) { // Reserve recover() for wedged/cancelled // transfers — e.g. after dropping a // respond_to_* future mid-transaction. + expect_repeated_start = true; break; } Ok(WriteStatus::BufferFull(n)) => { diff --git a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs index 5bdb6e53..2504fc08 100644 --- a/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs +++ b/examples/rt685s-evk/src/bin/i2c-slave-target-trait.rs @@ -5,6 +5,20 @@ //! goes through the `embedded_mcu_hal::i2c::target::blocking::I2c` trait //! instead of the inherent `I2cSlave` methods. //! +//! ## Race-watching telemetry +//! +//! Two `warn!` emissions in this example flag known-suspicious shapes +//! associated with the `slv_state -> addressed` mid-DMA HW race tracked +//! on PR #565: +//! +//! 1. `WriteStatus::Restarted(0)` — a zero-byte restart should not occur +//! on a healthy bus. +//! 2. `Request::RepeatedStart(_)` arriving when the prior `respond_to_*` +//! did **not** report `Restarted(_)`. +//! +//! See the async counterpart (`i2c-slave-async-target-trait.rs`) for more +//! context. +//! //! Tested against the same Raspberry Pi 5 master rig as the existing //! `i2c-slave.rs` example //! (https://github.com/jerrysxie/pi5-i2c-test). @@ -12,7 +26,7 @@ #![no_std] #![no_main] -use defmt::info; +use defmt::{info, warn}; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::Blocking; @@ -28,6 +42,10 @@ const BUFLEN: usize = 8; #[embassy_executor::task] async fn slave_service(mut i2c: I2cSlave<'static, Blocking>) { + // See `i2c-slave-async-target-trait.rs` for an explanation of this + // expect_repeated_start flag and the corresponding `warn!` emissions. + let mut expect_repeated_start = false; + loop { let mut buf: [u8; BUFLEN] = [0xAA; BUFLEN]; @@ -39,19 +57,40 @@ async fn slave_service(mut i2c: I2cSlave<'static, Blocking>) { Ok(r) => r, Err(e) => { info!("listen error: {:?}", defmt::Debug2Format(&e)); + expect_repeated_start = false; continue; } }; + let was_expecting_restart = expect_repeated_start; + expect_repeated_start = false; + match req { Request::Stop(addr) => { info!("Stop @ 0x{:02X} (probe)", addr); + if was_expecting_restart { + warn!( + "RACE WATCH: prior respond_to_* reported Restarted but listen() \ + returned Stop(0x{:02X}); expected RepeatedStart", + addr + ); + } } Request::RepeatedStart(prev_addr) => { info!("RepeatedStart from prev @ 0x{:02X}", prev_addr); + if !was_expecting_restart { + warn!( + "RACE WATCH: RepeatedStart(0x{:02X}) without prior Restarted(_) — \ + likely a spurious edge from a mid-DMA SlaveAddress mis-classification", + prev_addr + ); + } } Request::Read(addr) => { info!("Read @ 0x{:02X}", addr); + if was_expecting_restart { + info!("(consumed expected RepeatedStart edge before Read)"); + } loop { use embedded_mcu_hal::i2c::target::ReadStatus; match TargetI2c::::respond_to_read(&mut i2c, &buf) { @@ -79,6 +118,9 @@ async fn slave_service(mut i2c: I2cSlave<'static, Blocking>) { } Request::Write(addr) => { info!("Write @ 0x{:02X}", addr); + if was_expecting_restart { + info!("(consumed expected RepeatedStart edge before Write)"); + } loop { use embedded_mcu_hal::i2c::target::WriteStatus; match TargetI2c::::respond_to_write(&mut i2c, &mut buf) { @@ -88,6 +130,12 @@ async fn slave_service(mut i2c: I2cSlave<'static, Blocking>) { } Ok(WriteStatus::Restarted(n)) => { info!("Write restarted after {} bytes", n); + if n == 0 { + warn!( + "RACE WATCH: WriteStatus::Restarted(0) — zero-byte restart \ + should not occur on a healthy bus." + ); + } // Do NOT call recover() here: a Restarted is a // healthy continuation of an in-flight master // transaction (Sr + ADDR+R/W is queued on the @@ -97,6 +145,7 @@ async fn slave_service(mut i2c: I2cSlave<'static, Blocking>) { // Reserve recover() for wedged/cancelled // transfers — e.g. after dropping a // respond_to_* future mid-transaction. + expect_repeated_start = true; break; } Ok(WriteStatus::BufferFull(n)) => { From edf3ec4f0fdcd7fffbbc77997dd7a76f25505a85 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 2 Jun 2026 07:42:34 -0700 Subject: [PATCH 15/27] i2c::slave: split overloaded slvpending/slave_address branch in async respond_to_read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The async `respond_to_read` termination handler treated `slvpending().is_pending() || slvstate().is_slave_address()` as a single `Restarted` case, queueing a `RepeatedStart` edge for any trait caller. `slvpending` is overloaded though — it asserts for *any* SW-intervention condition the FC peripheral cannot resolve on its own, not just a repeated START. The common case on the read path is a controller-side NACK ending the transaction: the FC raises `slvpending` first, then propagates `slvdesel` one peripheral cycle later. If the poll loop wakes on the `slvpending` arm before `slvdesel` lands, we synthesise a phantom `Restarted` plus a queued `RepeatedStart` edge — and the next `listen()` reports a `RepeatedStart` event that has no matching Sr on the wire. Split the branch to discriminate on the actual state, matching what the blocking flavour already does at line 665: * `slvstate:().is_slave_address()` — a genuine new address phase has been latched, queue `EdgeKind::RepeatedStart` and report `Response::Restarted(xfer_count)`. * `slvpending:().is_pending()` (bare, without `slvdesel` or new address) — treat as a normal read termination: NAK to settle the peripheral, then report `ReadComplete(xfer_count)` or `ReadEarlyStop(xfer_count)` based on whether the buffer was fully drained. No edge queued. This also hardens against the `slv_state -> addressed` mid-DMA HW race jerrysxie has been chasing on the read path: if the FC transiently mis-classifies during a stress soak, `slvpending` may fire without a real address transition. After this change the worst-case observable becomes a `ReadEarlyStop` instead of a fabricated trait-side `RepeatedStart` followed by a wedge on the next `listen()` waiting for a phantom Sr that never came. Addresses Copilot review thread PRRT_kwDOMUqVSs6GcUOx on PR on-target race-watch `warn!` lines added in 62937dd — the soak now has a fix to prove, not just a probe to fire. --- src/i2c/slave.rs | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index b6deda03..b95472ff 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -963,12 +963,34 @@ impl I2cSlave<'_, Async> { } else { return Ok(Response::ReadEarlyStop(xfer_count)); } - } else if stat.slvpending().is_pending() || stat.slvstate().is_slave_address() { - // Restart (or NACK-then-next-address) — queue the edge for - // any trait caller and report the restart to the inherent - // caller. + } else if stat.slvstate().is_slave_address() { + // The peripheral has latched a new address phase — that is a + // genuine repeated START (Sr + ADDR+R/W queued on the wire). + // Queue the edge for any trait caller and report the restart + // to the inherent caller. Mirrors the blocking flavour. self.queue_edge(EdgeKind::RepeatedStart); return Ok(Response::Restarted(xfer_count)); + } else if stat.slvpending().is_pending() { + // `slvpending` is asserted but neither `slvdesel` nor a new + // address phase fired — this is the "SW intervention needed" + // signal the FC peripheral raises when the controller NACKs + // our last transmitted byte to end the transaction (the + // deselect latch lands one peripheral cycle later) and when + // the FC state machine briefly mis-classifies mid-DMA under + // stress (the `slv_state -> addressed` race documented on + // PR #565 follow-up discussions). + // + // Treat it as a normal read termination: NAK to settle the + // peripheral and report based on `xfer_count`. Do NOT + // synthesise a `RepeatedStart` edge here — that would + // fabricate a phantom trait-side event with no matching + // upstream Sr on the wire. + i2c.slvctl().write(|w| w.slvnack().nack()); + if xfer_count == buf_len { + return Ok(Response::ReadComplete(xfer_count)); + } else { + return Ok(Response::ReadEarlyStop(xfer_count)); + } } else if stat.slvstate().is_slave_transmit() { // DMA drained while the master is still clocking — caller // should supply another buffer to continue. From 49e9d10e8398a75c148fada1c01235ff4fd18b9b Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 3 Jun 2026 06:19:28 -0700 Subject: [PATCH 16/27] Update Cargo.lock post-merge --- Cargo.lock | 36 ++----- examples/rt633/Cargo.lock | 88 ++++++++--------- .../rt685s-evk-performance-tracing/Cargo.lock | 94 ++++++++++--------- examples/rt685s-evk/Cargo.lock | 26 ++--- 4 files changed, 116 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e0ae39f..a5db2b8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -181,15 +181,6 @@ name = "defmt" version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" -dependencies = [ - "defmt 1.0.1", -] - -[[package]] -name = "defmt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" dependencies = [ "defmt 1.1.0", ] @@ -233,7 +224,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" dependencies = [ "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] @@ -314,7 +305,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "num-traits", ] @@ -336,7 +327,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-embedded-hal", "embassy-executor", @@ -378,7 +369,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -501,7 +492,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ - "defmt 1.0.1", + "defmt 1.1.0", "embedded-hal 1.0.0", "embedded-hal-async", "num_enum", @@ -639,9 +630,9 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "log" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" [[package]] name = "loom" @@ -689,7 +680,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -702,7 +693,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -913,15 +904,6 @@ dependencies = [ "embedded-crc-macros", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0763a680cd5d72b28f7bfc8a054c117d8841380a6ad4f72f05bd2a34217d3e" -dependencies = [ - "embedded-crc-macros", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" diff --git a/examples/rt633/Cargo.lock b/examples/rt633/Cargo.lock index e1ca4176..3616fab6 100644 --- a/examples/rt633/Cargo.lock +++ b/examples/rt633/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "az" @@ -64,9 +64,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "shlex", @@ -174,14 +174,14 @@ version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" dependencies = [ - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] name = "defmt" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ "bitflags", "defmt-macros", @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" dependencies = [ "defmt-parser", "proc-macro-error2", @@ -211,12 +211,12 @@ dependencies = [ [[package]] name = "defmt-rtt" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" dependencies = [ "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] @@ -230,9 +230,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embassy-embedded-hal" @@ -261,7 +261,7 @@ dependencies = [ "cordyceps", "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -299,7 +299,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "num-traits", ] @@ -320,7 +320,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -358,7 +358,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -373,7 +373,7 @@ checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -482,7 +482,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ - "defmt 1.0.1", + "defmt 1.1.0", "embedded-hal 1.0.0", "embedded-hal-async", "num_enum", @@ -602,9 +602,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ "cc", "cfg-if", @@ -637,9 +637,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" dependencies = [ "hash32", "stable_deref_trait", @@ -668,9 +668,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "litrs" @@ -680,9 +680,9 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "log" -version = "0.4.29" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" [[package]] name = "loom" @@ -708,9 +708,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mimxrt600-fcb" @@ -730,7 +730,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -743,7 +743,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -814,7 +814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] @@ -871,9 +871,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "rand_core 0.6.4", ] @@ -913,7 +913,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", - "defmt 1.0.1", + "defmt 1.1.0", "defmt-rtt", "embassy-executor", "embassy-futures", @@ -975,9 +975,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "smallvec" @@ -1114,9 +1114,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1177,18 +1177,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/examples/rt685s-evk-performance-tracing/Cargo.lock b/examples/rt685s-evk-performance-tracing/Cargo.lock index 2d40880a..e113eaa6 100644 --- a/examples/rt685s-evk-performance-tracing/Cargo.lock +++ b/examples/rt685s-evk-performance-tracing/Cargo.lock @@ -13,9 +13,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "az" @@ -48,7 +48,7 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 1.3.0", ] [[package]] @@ -83,12 +83,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.59" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", - "shlex", + "shlex 2.0.1", ] [[package]] @@ -219,14 +219,14 @@ version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" dependencies = [ - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] name = "defmt" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ "bitflags", "defmt-macros", @@ -234,9 +234,9 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" dependencies = [ "defmt-parser", "proc-macro-error2", @@ -256,12 +256,12 @@ dependencies = [ [[package]] name = "defmt-rtt" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" dependencies = [ "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] @@ -275,9 +275,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "embassy-embedded-hal" @@ -306,7 +306,7 @@ dependencies = [ "cordyceps", "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -346,7 +346,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "num-traits", ] @@ -367,7 +367,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -404,7 +404,7 @@ version = "0.1.0" dependencies = [ "cortex-m", "cortex-m-rt", - "defmt 1.0.1", + "defmt 1.1.0", "defmt-rtt", "embassy-executor", "embassy-futures", @@ -431,7 +431,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -446,7 +446,7 @@ checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -555,7 +555,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ - "defmt 1.0.1", + "defmt 1.1.0", "embedded-hal 1.0.0", "embedded-hal-async", "num_enum", @@ -675,9 +675,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" dependencies = [ "cc", "cfg-if", @@ -716,9 +716,9 @@ dependencies = [ [[package]] name = "heapless" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" dependencies = [ "hash32", "stable_deref_trait", @@ -753,9 +753,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" @@ -775,9 +775,9 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "log" -version = "0.4.29" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" [[package]] name = "loom" @@ -803,9 +803,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mimxrt600-fcb" @@ -825,7 +825,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -838,7 +838,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -925,7 +925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] @@ -988,9 +988,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "rand_core 0.6.4", ] @@ -1105,6 +1105,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "smallvec" version = "1.15.1" @@ -1263,9 +1269,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1326,18 +1332,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/examples/rt685s-evk/Cargo.lock b/examples/rt685s-evk/Cargo.lock index 4d5dcd51..ef7ec048 100644 --- a/examples/rt685s-evk/Cargo.lock +++ b/examples/rt685s-evk/Cargo.lock @@ -260,7 +260,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e" dependencies = [ "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] @@ -305,7 +305,7 @@ dependencies = [ "cordyceps", "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-executor-macros", "embassy-executor-timer-queue", @@ -343,7 +343,7 @@ checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" dependencies = [ "cortex-m", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "num-traits", ] @@ -364,7 +364,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-embedded-hal", "embassy-futures", @@ -401,7 +401,7 @@ dependencies = [ "chrono", "cortex-m", "cortex-m-rt", - "defmt 1.0.1", + "defmt 1.1.0", "defmt-rtt", "embassy-executor", "embassy-futures", @@ -432,7 +432,7 @@ checksum = "7bbd85cf5a5ae56bdf26f618364af642d1d0a4e245cdd75cd9aabda382f65a81" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "embedded-io-async 0.7.0", "futures-core", "futures-sink", @@ -447,7 +447,7 @@ checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" dependencies = [ "cfg-if", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "document-features", "embassy-time-driver", "embedded-hal 0.2.7", @@ -567,7 +567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0563f27b8ea59eb57e2c5926db6ee311d0a24ab9912adf82b9f418cc9799b0ff" dependencies = [ "chrono", - "defmt 1.0.1", + "defmt 1.1.0", "embedded-hal 1.0.0", "embedded-hal-async", "num_enum", @@ -810,9 +810,9 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "log" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" [[package]] name = "loom" @@ -871,7 +871,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -884,7 +884,7 @@ dependencies = [ "cortex-m", "cortex-m-rt", "critical-section", - "defmt 1.0.1", + "defmt 1.1.0", "vcell", ] @@ -955,7 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" dependencies = [ "cortex-m", - "defmt 1.0.1", + "defmt 1.1.0", ] [[package]] From 9123cd55419139f9f267985e473e0aa1e488e37e Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Wed, 3 Jun 2026 07:07:42 -0700 Subject: [PATCH 17/27] update cargo vet --- supply-chain/imports.lock | 155 ++++++++++++++++++++++++++++++++++---- 1 file changed, 140 insertions(+), 15 deletions(-) diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 243f1423..3a6125b3 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -22,8 +22,8 @@ user-id = 55123 user-login = "rust-lang-owner" [[publisher.log]] -version = "0.4.30" -when = "2026-05-25" +version = "0.4.31" +when = "2026-06-02" user-id = 3204 user-login = "KodrAus" user-name = "Ashley Mannix" @@ -204,13 +204,6 @@ version = "0.3.100" notes = "Compatibility shim: no_std crate that re-exports defmt 1.x items for 0.3 API compatibility. No unsafe code, no build script, no powerful imports, no logic - pure pub-use re-exports. Assisted-by: copilot-cli:claude-opus-4.6" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" -[[audits.OpenDevicePartnership.audits.defmt]] -who = "Jerry Xie " -criteria = "safe-to-deploy" -version = "0.3.100" -notes = "Compatibility shim: no_std crate that re-exports defmt 1.x items for 0.3 API compatibility. No unsafe code, no build script, no powerful imports, no logic - pure pub-use re-exports. Assisted-by: copilot-cli:claude-opus-4.6" -aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" - [[audits.OpenDevicePartnership.audits.defmt]] who = "Felipe Balbi " criteria = "safe-to-deploy" @@ -291,6 +284,13 @@ criteria = "safe-to-deploy" version = "0.5.0" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/tps6699x/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.embassy-time-driver]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.2.1" +notes = "no_std driver trait for embassy-time. Minimal unsafe for extern Rust FFI calls (sound via links key). Empty build.rs. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.embedded-crc-macros]] who = "Matteo Tullo " criteria = "safe-to-deploy" @@ -303,11 +303,123 @@ criteria = "safe-to-deploy" version = "0.2.7" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/mcxa-pac/refs/heads/main/supply-chain/audits.toml" -[[audits.OpenDevicePartnership.audits.embedded-mcu-hal]] -who = "Felipe Balbi " +[[audits.OpenDevicePartnership.audits.embedded-hal]] +who = "Jerry Xie " criteria = "safe-to-deploy" -delta = "0.2.0 -> 0.3.0" -aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-mcu/refs/heads/main/supply-chain/audits.toml" +delta = "0.2.7 -> 1.0.0" +notes = "Pure no_std trait crate. Complete API redesign for 1.0: removed nb-based traits, CAN module, all unsafe code. Only defines traits/enums/types for digital, I2C, SPI, PWM, delay. No build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-hal-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "no_std async HAL trait definitions. No unsafe in library. Build script only runs rustc --version. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-hal-nb]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.0.0" +notes = "no_std trait-only crate. No unsafe, no build script, no proc macros, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-io-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.6.1" +notes = "No unsafe. Build script only detects nightly via rustc --version. Pure async trait definitions for embedded I/O. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-storage]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "Pure no_std storage abstraction traits. deny(unsafe_code), no build script, no dependencies, no powerful imports. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.embedded-storage-async]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.4.1" +notes = "Pure no_std async trait definitions for NOR flash storage. No unsafe code, no build script, no powerful imports. Only dependency is embedded-storage. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.find-msvc-tools]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.1.9" +notes = "Full audit of find-msvc-tools 0.1.9, extracted from cc-rs. Zero dependencies. Unsafe code is COM/FFI wrappers for Windows registry, VS Setup Configuration COM interfaces, and kernel32 dynamic loading - all sound with proper Drop impls and error checking. Compile-time type check guards transmute_copy for dynamic GetMachineTypeAttributes. No build script, no proc macros, no network access. Only accesses filesystem/registry/COM to locate MSVC tooling as advertised. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.fixed]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "1.29.0" +notes = "no_std fixed-point number library. Unsafe limited to: bytemuck Pod/Zeroable impls on repr(transparent) types, NonZero::new_unchecked after proven-nonzero guards, unreachable_unchecked in exhaustive remainder logic. Build script probes compiler features in OUT_DIR. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.fixed]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.29.0 -> 1.31.0" +notes = "Delta audit 1.29.0->1.31.0. Unwrapped renamed to Strict; new unchecked_neg/shl/shr thin wrappers over std integer unchecked ops (sound); defmt feature delegates to Display; compat-readonly-static disables atomic caching. No new powerful imports, no build.rs logic changes. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.futures-core]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.3.31 -> 0.3.32" +notes = "Delta audit: version bump, Cargo.toml cosmetic reorder, AtomicWaker correctness fix (fetch_and to swap for cleaner state transition), ready! macro doc update. No new unsafe code, no build script, no proc macros, no powerful imports. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.futures-sink]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.3.31 -> 0.3.32" +notes = "No source code changes, only Cargo.toml metadata reordering. Assisted-by: GitHub Copilot:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.half]] +who = "Robert Zieba " +criteria = "safe-to-deploy" +delta = "2.5.0 -> 2.6.0" +notes = "Big change seems to be a change in the repo URL, but both the old and new URL resolve to the same place so it looks like the author is still in control." +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.half]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "2.6.0 -> 2.7.1" +notes = "Delta audit: migrated unsafe transmutes to zerocopy::transmute! (safety improvement), added loongarch64 LSX SIMD (behind nightly feature), added Signed/Weight trait impls, added zerocopy derives on f16/bf16. New loongarch64 unsafe follows same sound MaybeUninit+intrinsics pattern. Net reduction in unsafe code. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.hash32]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.3.1" +notes = "no_std 32-bit hashing (FNV, MurmurHash3). ~10 unsafe blocks in murmur3.rs for MaybeUninit buffer handling - all sound. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heapless]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +version = "0.9.2" +notes = "no_std fixed-capacity data structures. Extensive unsafe for MaybeUninit buffers, lock-free queues (Vyukov MPMC, SPSC), Treiber stack pools with ABA prevention (CAS tagged pointers + ARM LLSC). All Send/Sync bounds verified correct. Build script probes for ARM LLSC. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.heapless]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "0.9.2 -> 0.9.3" +notes = "Delta 0.9.2->0.9.3: Fixes unsoundness in Deque/HistoryBuf/IndexMap::clear when Drop panics (Guard pattern). Adds CString conversions (into_string, into_bytes, from_bytes_truncating_at_nul), Deque::pop_front_if/pop_back_if, const Vec::from_array. New transmute_copy in as_len_type is sound (closed type enum). Pointer-cast cleanups. Assisted-by: copilot-cli:claude-opus-4.6" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + +[[audits.OpenDevicePartnership.audits.ident_case]] +who = "matteotullo " +criteria = "safe-to-deploy" +version = "1.0.1" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" [[audits.OpenDevicePartnership.audits.itertools]] who = "Felipe Balbi " @@ -424,6 +536,19 @@ criteria = "safe-to-deploy" version = "1.0.1" aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" +[[audits.OpenDevicePartnership.audits.stable_deref_trait]] +who = "Jerry Xie " +criteria = "safe-to-deploy" +delta = "1.2.0 -> 1.2.1" +notes = """ +Delta audit: metadata-only Cargo.toml changes (license format, explicit lib section). +src/lib.rs adds new unsafe impl StableDeref for Cow types - all sound since Cow::deref() +returns a stable heap or borrowed address across moves. Arc import and impls now correctly +gated behind target_has_atomic=ptr. No build script, no proc macros, no powerful imports. +Assisted-by: copilot-chat:claude-opus-4.6 +""" +aggregated-from = "https://raw.githubusercontent.com/OpenDevicePartnership/embedded-services/refs/heads/main/supply-chain/audits.toml" + [[audits.OpenDevicePartnership.audits.static_cell]] who = "Robert Zieba " criteria = "safe-to-deploy" @@ -953,7 +1078,7 @@ aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust who = "Lukasz Anforowicz " criteria = "safe-to-deploy" version = "1.0.14" -notes = ''' +notes = """ Grepped for `-i cipher`, `-i crypto`, `'\bfs\b'``, `'\bnet\b'``, `'\bunsafe\b'`` and there were no hits except for: @@ -975,7 +1100,7 @@ and there were no hits except for: Version `1.0.6` of this crate has been added to Chromium in https://source.chromium.org/chromium/chromium/src/+/28841c33c77833cc30b286f9ae24c97e7a8f4057 -''' +""" aggregated-from = "https://chromium.googlesource.com/chromium/src/+/main/third_party/rust/chromium_crates_io/supply-chain/audits.toml?format=TEXT" [[audits.google.audits.rustversion]] From 94dd08bff17b4644db309265f0b212e48bb8f5da Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 8 Jun 2026 10:03:42 -0700 Subject: [PATCH 18/27] examples: add i2c write_read boundary soak Long-running soak example targeting the specific master pattern that surfaces `WriteStatus::Restarted(0)` on the slave trait API: master.write_read(addr, &w[..k*S], &mut r[..m]) where `S` is the slave receive-buffer length and the master write payload is always an exact `k*S` multiple. The slave's first `respond_to_write` returns `BufferFull(S)`; the caller loops and the second call returns `Restarted(0)` because the controller has issued Sr on the wire in between. The existing `i2c-slave-async-target-trait` example flags that shape as `RACE WATCH` on the suspicion of a HW race. This soak tests the alternative hypothesis: `Restarted(0)` is a buffer-boundary surfacing artifact, not a hardware race. The example: * Uses the `embedded_mcu_hal::i2c::target::asynch::I2c` trait so the reproducer matches the harness that produced the original log. * Sweeps `k in 1..=4` and `m in 1..=S-1` round-robin (12 unique pairs per cycle), back-to-back with no master pacing. * Wraps every awaited transfer on both tasks in `with_timeout(250 ms)`; any timeout panics with a distinct message identifying the call site and the offending iteration/parameters so RTT freezes with the wedge state captured. * Refills the slave TX buffer with `t_buf[j] = (j + iter) mod 256` each iteration; the master verifies the increment in the read buffer and panics on mismatch (catches cross-iteration buffer bleed without requiring shared state). * Counts every terminator branch on the slave (BufferFull, Stopped(0)/n, Restarted(0)/n, ReadComplete/NeedMore/EarlyStop, RepeatedStart, Probe) and emits a one-line summary every 10 000 iterations. Pass signal for the hypothesis: many millions of iterations with no `defmt::panic!` and no read mismatch -> the `Restarted(0)` shape is benign API surfacing, not a HW race. Any timeout or mismatch promotes the artifact to a real bug with the failing iteration captured in RTT. Wiring matches the existing i2c-loopback-async example (FC2 slave on PIO0_18/PIO0_17 + DMA0_CH4, FC4 master on PIO0_29/PIO0_30 + DMA0_CH9, Fast mode at 50 percent duty). --- .../src/bin/i2c-write-read-boundary-soak.rs | 690 ++++++++++++++++++ src/i2c/mod.rs | 19 +- 2 files changed, 705 insertions(+), 4 deletions(-) create mode 100644 examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs diff --git a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs new file mode 100644 index 00000000..f9930bde --- /dev/null +++ b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs @@ -0,0 +1,690 @@ +//! I2C `write_read` boundary soak. +//! +//! Exercises the specific master pattern that surfaces +//! `WriteStatus::Restarted(0)` on the slave's `embedded_mcu_hal::i2c::target` +//! trait API: +//! +//! master.write_read(addr, &w[..k*S], &mut r[..m]) +//! +//! where: +//! +//! * `S` is the slave receive-buffer length (see `SLAVE_BUFLEN`). +//! * `k` cycles round-robin over `BOUNDARY_K_MIN ..= BOUNDARY_K_MAX`. +//! * `m` cycles round-robin over `READ_LEN_MIN ..= READ_LEN_MAX`, with +//! `READ_LEN_MAX < S` so the read leg is always a simple +//! NACK+STOP-terminated short read (never another `k*S` boundary). +//! +//! ## Why this matters +//! +//! When the master writes exactly `k*S` bytes and follows with a +//! repeated-START into the read phase, the slave's first +//! `respond_to_write` call drains a full buffer and returns +//! `WriteStatus::BufferFull(S)`. The caller loops with a fresh buffer; by +//! then the controller has issued Sr on the wire and the second +//! `respond_to_write` returns `WriteStatus::Restarted(0)` — a zero-byte +//! restart. The `i2c-slave-async-target-trait` example flags this as a +//! `RACE WATCH` line because, on a healthy bus, a real Sr is preceded by +//! at least one ACKed payload byte on the call that observes it. +//! +//! Hypothesis under test: the `Restarted(0)` shape is a *buffer-boundary +//! surfacing artifact*, not a hardware race. If the soak runs for +//! millions of iterations without: +//! +//! * a transfer timing out (master or slave wedge), +//! * an error return from any trait/master call, +//! * a mismatch in the read-back payload, +//! +//! then the artifact is benign and the surfacing API is the place to +//! address it (if at all). Any timeout or mismatch panics immediately so +//! RTT freezes with the offending iteration's context. +//! +//! ## Wiring +//! +//! Same FC2 ↔ FC4 loopback as `i2c-loopback-async.rs` on the RT685S-EVK: +//! +//! * FC2 slave: SCL = PIO0_18, SDA = PIO0_17, DMA = DMA0_CH4 +//! * FC4 master: SCL = PIO0_29, SDA = PIO0_30, DMA = DMA0_CH9 +//! * Speed: Fast (400 kHz), 50% duty. +//! +//! ### Pull-ups +//! +//! I2C requires pull-up resistors on SDA and SCL to drive the lines +//! high when neither end is asserting them low (per UM10204). The +//! FC2 ↔ FC4 loopback on the EVK wires two GPIO pins directly with no +//! external resistors. Without pull-ups, every "release" of the line +//! leaves it floating and both peripheral state machines stall +//! waiting for SCL/SDA edges that never come — reproducible as the +//! master/slave mutual wedge captured by an early breadcrumb dump on +//! this soak. +//! +//! The driver's `as_scl()`/`as_sda()` enables the SoC's internal +//! pull-ups (~40 kΩ-100 kΩ) so this loopback configuration works +//! out-of-the-box. On a real I2C bus with external pull-ups the +//! internal pull-up parallels them with negligible effect on rise +//! time. +//! +//! ## What to look for in RTT +//! +//! Per master iteration (one of 4×3 = 12 unique `(k, m)` pairs in +//! round-robin): +//! +//! * 1× `Write @ 0x20` on the slave (from `Request::Write`). +//! * `k`× `Write BufferFull after S bytes — supplying more buffer space`. +//! * 1× `Write restarted after 0 bytes …` + `RACE WATCH: +//! WriteStatus::Restarted(0)` warning. +//! * 1× `RepeatedStart from prev @ 0x20`. +//! * 1× `Read @ 0x20`. +//! * 1× `Read terminated by controller after m bytes` (always EarlyStop +//! because `m < S`). +//! +//! Counter summaries are emitted by each task every `REPORT_EVERY` +//! iterations. Absence of any `defmt::panic!` over a long run is the +//! pass signal. +//! +//! ## Forensic breadcrumbs +//! +//! Adding per-step `info!` lines slows the loop enough to mask the +//! release-mode HardFault we are chasing. Instead each step in both +//! tasks stores a packed `(site_id, iter)` u32 into a 32-entry static +//! ring buffer (a single relaxed atomic store, no formatting, no +//! critical section). Every `wedge_panic!` call flushes the ring via +//! `defmt::error!` *before* delegating to `defmt::panic!`, so the RTT +//! log carries the last 32 (task, site, iter) transitions immediately +//! preceding any wedge/error/mismatch panic. See `breadcrumb()` and the +//! site-ID table near the top of this file. +//! +//! ## Reading the breadcrumb ring with probe-rs +//! +//! When the failure is our own `wedge_panic!`, the ring contents are +//! already on RTT — no extra step is required. When the failure is a +//! true CPU HardFault (no panic message reached RTT, just +//! `Firmware exited unexpectedly: Exception` from probe-rs), the ring +//! still lives in RAM because `panic-probe`'s `udf` halt loop does not +//! touch user data. Read it directly: +//! +//! ### 1. Discover the symbol addresses +//! +//! ```text +//! $ rust-nm target/thumbv8m.main-none-eabihf/release/i2c-write-read-boundary-soak \ +//! | rg 'BREADCRUMB_(INDEX|RING)' +//! 20081200 D BREADCRUMB_INDEX +//! 20081204 D BREADCRUMB_RING +//! ``` +//! +//! Address values will differ per build; always rediscover after a +//! rebuild. The `D` flag confirms the symbols live in the data +//! (`.bss` / `.data`) region, which `panic-probe`'s `udf` halt does +//! not perturb. +//! +//! ### 2. Read the ring index and the ring +//! +//! `probe-rs read` opens its own debug session on each invocation, so +//! no separate `attach` is required — and a long-lived `attach` in +//! another shell would actually block the read. probe-rs also rejects +//! `_` digit separators in numeric arguments; use plain hex. +//! +//! ```text +//! # 32-bit index (1 word): +//! $ probe-rs read --chip MIMXRT685SFVKB b32 0x20081200 1 +//! 0x00000087 +//! +//! # 32-entry ring (32 words = 128 bytes): +//! $ probe-rs read --chip MIMXRT685SFVKB b32 0x20081204 32 +//! 0x14000086 0x15000086 0x19000086 0x13000086 0x14000087 ... +//! ``` +//! +//! ### 3. Decode entries +//! +//! Each `u32` is packed `(site << 24) | (iter & 0x00FFFFFF)`: +//! +//! * **site** = `(packed >> 24) & 0xFF` — cross-reference with the +//! site-ID table in the source comments above (`0x10`..`0x19` = +//! slave_service, `0x20`..`0x24` = master_service). +//! * **iter** = `packed & 0x00FFFFFF` — the task-local iteration +//! counter at the time the breadcrumb was dropped. Saturates at +//! 24 bits; not unique across long soaks but the local ordering +//! within the last 32 entries is always meaningful. +//! +//! ### 4. Reconstruct chronological order +//! +//! Entries are written round-robin. Let `next = BREADCRUMB_INDEX`, +//! `count = min(next, 32)`, `start = next.wrapping_sub(count)`. Read +//! in order: slots `(start + 0) & 31`, `(start + 1) & 31`, …, +//! `(start + count - 1) & 31`. The on-target `dump_breadcrumbs()` +//! function does the same reconstruction; emulating it off-target +//! recovers the same oldest-to-newest sequence. +//! +//! ### 5. Optional: Python decoder +//! +//! Paste the `probe-rs read` outputs into this short script to +//! reproduce the RTT dump format off-target. Python accepts `_` in +//! numeric literals; the example values below drop them so they match +//! `probe-rs read` output verbatim. +//! +//! ```python +//! #!/usr/bin/env python3 +//! # usage: paste the index value and 32 ring words below, then run. +//! index = 0x00000087 +//! ring = [ +//! 0x14000086, 0x15000086, 0x19000086, 0x13000086, +//! # ... 28 more u32 words from `probe-rs read` ... +//! ] +//! count = min(index, len(ring)) +//! start = (index - count) % len(ring) +//! for i in range(count): +//! packed = ring[(start + i) % len(ring)] +//! site = (packed >> 24) & 0xFF +//! iter = packed & 0x00FFFFFF +//! print(f"bc[{i:2}]: site=0x{site:02X} iter={iter}") +//! ``` +//! +//! ### 6. Optional: GDB stub +//! +//! If you prefer GDB: +//! +//! ```text +//! $ probe-rs gdb --chip MIMXRT685SFVKB & +//! $ arm-none-eabi-gdb \ +//! target/thumbv8m.main-none-eabihf/release/i2c-write-read-boundary-soak +//! (gdb) target remote :1337 +//! (gdb) print/x BREADCRUMB_INDEX +//! (gdb) print/x BREADCRUMB_RING +//! ``` +//! +//! GDB prints the array nicely formatted; apply the same decoding +//! from step 3 to each element. + +#![no_std] +#![no_main] + +use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; + +use defmt::{error, info, warn}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_imxrt::i2c::master::{DutyCycle, I2cMaster}; +use embassy_imxrt::i2c::slave::{Address, I2cSlave}; +use embassy_imxrt::i2c::{self, Async}; +use embassy_imxrt::{bind_interrupts, peripherals}; +use embassy_imxrt_examples as _; +use embassy_time::{Duration, with_timeout}; +use embedded_hal_async::i2c::I2c; +use embedded_mcu_hal::i2c::SevenBitAddress; +use embedded_mcu_hal::i2c::target::asynch::I2c as TargetI2c; +use embedded_mcu_hal::i2c::target::{ReadStatus, Request, WriteStatus}; +use panic_probe as _; + +// --------------------------------------------------------------------------- +// Forensic breadcrumb ring +// --------------------------------------------------------------------------- +// +// In release mode this soak has been observed to HardFault between +// `Read @ 0x20` and the subsequent `respond_to_read` await, with no +// `WEDGE:` panic message reaching RTT. Adding any per-iteration `info!` +// slows the loop enough to mask the fault. The breadcrumb ring is the +// cheapest possible forensic trail: each checkpoint does one atomic +// store of a packed `(site << 24) | (iter & 0xFFFFFF)` u32 into a +// 32-entry static ring, no formatting and no critical section. +// +// On a wedge `defmt::panic!`, `dump_breadcrumbs!()` flushes the last 32 +// entries via `defmt::error!` *before* delegating to the panic macro. +// That gives us the exact sequence of slave + master steps leading up +// to the failure, even when the per-iteration `info!` lines that would +// usually narrate the state cannot be afforded. +// +// Site IDs are packed into the upper byte: +// +// 0x1_ — slave_service lane +// 0x10 enter loop / about to call listen() +// 0x11 listen() returned +// 0x12 Probe arm +// 0x13 RepeatedStart arm +// 0x14 Read arm entered +// 0x15 about to await respond_to_read +// 0x16 respond_to_read returned, classifying status +// 0x17 Write arm entered +// 0x18 about to await respond_to_write +// 0x19 respond_to_write returned, classifying status +// +// 0x2_ — master_service lane +// 0x20 chose k, m; filled buffers +// 0x21 about to await master.write_read +// 0x22 master.write_read returned Ok +// 0x23 verifying read payload increment +// 0x24 verification passed + +const BREADCRUMB_RING_LEN: usize = 32; +const BREADCRUMB_RING_MASK: usize = BREADCRUMB_RING_LEN - 1; +const _: () = assert!(BREADCRUMB_RING_LEN.is_power_of_two()); + +static BREADCRUMB_INDEX: AtomicUsize = AtomicUsize::new(0); +static BREADCRUMB_RING: [AtomicU32; BREADCRUMB_RING_LEN] = { + const Z: AtomicU32 = AtomicU32::new(0); + [Z; BREADCRUMB_RING_LEN] +}; + +#[inline(always)] +fn breadcrumb(site: u8, iter: u32) { + let packed = ((site as u32) << 24) | (iter & 0x00FF_FFFF); + let idx = BREADCRUMB_INDEX.fetch_add(1, Ordering::Relaxed) & BREADCRUMB_RING_MASK; + BREADCRUMB_RING[idx].store(packed, Ordering::Relaxed); +} + +/// Dump the breadcrumb ring oldest-to-newest via `defmt::error!`. +/// Intended to be called immediately before `defmt::panic!` at any +/// wedge site so the panic-loop halt does not race the panic message +/// to RTT. +fn dump_breadcrumbs() { + let next = BREADCRUMB_INDEX.load(Ordering::Relaxed); + let count = next.min(BREADCRUMB_RING_LEN); + let start = next.wrapping_sub(count); + error!("---- breadcrumb dump ({} entries, newest last) ----", count); + for i in 0..count { + let slot = (start.wrapping_add(i)) & BREADCRUMB_RING_MASK; + let packed = BREADCRUMB_RING[slot].load(Ordering::Relaxed); + let site = (packed >> 24) as u8; + let iter = packed & 0x00FF_FFFF; + error!(" bc[{}]: site=0x{:02X} iter={}", i, site, iter); + } + error!("---- end breadcrumb dump ----"); +} + +/// Panic with full breadcrumb dump. Replaces `defmt::panic!` at every +/// wedge / error site so the RTT log carries the last 32 (site, iter) +/// transitions before the chip halts. +macro_rules! wedge_panic { + ($($arg:tt)*) => {{ + $crate::dump_breadcrumbs(); + defmt::panic!($($arg)*); + }}; +} + +// --------------------------------------------------------------------------- +// Tunables +// --------------------------------------------------------------------------- + +const ADDR: u8 = 0x20; +const SLAVE_ADDR: Option
= Address::new(ADDR); + +/// Slave receive-buffer length `S`. Small on purpose — keeps the `k*S` +/// boundary firing every iteration and minimises stack use. +const SLAVE_BUFLEN: usize = 4; + +/// Smallest `k` in `master.write_read(addr, &w[..k*S], …)`. +const BOUNDARY_K_MIN: usize = 1; +/// Largest `k`. The master write payload length sweeps round-robin over +/// `[k*S for k in K_MIN..=K_MAX]`, i.e. always an exact multiple of `S`. +const BOUNDARY_K_MAX: usize = 4; + +/// Smallest read-leg length `m`. +const READ_LEN_MIN: usize = 1; +/// Largest read-leg length. Keep `< SLAVE_BUFLEN` so the read leg always +/// terminates with a NACK+STOP on the first `respond_to_read` call. +const READ_LEN_MAX: usize = SLAVE_BUFLEN - 1; + +/// How often each task emits a counter-summary line. +const REPORT_EVERY: u32 = 10_000; + +/// Per-await timeout for every transfer. Generous to absorb defmt-rtt +/// flush jitter during periodic summary lines; anything longer than this +/// is a wedge worth investigating. +const PER_TRANSFER_TIMEOUT: Duration = Duration::from_millis(250); + +/// Maximum master write payload size — `k_max * S`. +const MASTER_MAX_WRITE: usize = BOUNDARY_K_MAX * SLAVE_BUFLEN; +/// Maximum master read payload size — `m_max`. +const MASTER_MAX_READ: usize = READ_LEN_MAX; + +bind_interrupts!(struct Irqs { + FLEXCOMM2 => i2c::InterruptHandler; + FLEXCOMM4 => i2c::InterruptHandler; +}); + +// --------------------------------------------------------------------------- +// Slave service +// --------------------------------------------------------------------------- + +#[embassy_executor::task] +async fn slave_service(mut slave: I2cSlave<'static, Async>) { + // Slave-local iteration counter. Advances once per `listen()` event. + // The TX pattern is keyed on its low byte; the master verifies the + // monotonic increment in the read buffer relative to the first byte + // it actually received, so the two counters do not need to be in + // lock-step. + let mut slv_iter: u32 = 0; + + // Tracks whether the most recent `respond_to_*` ended with + // `Restarted(_)`. The next `listen()` should return + // `Request::RepeatedStart(_)`; any other shape is suspicious. + let mut expect_repeated_start = false; + + // Counters. + let mut write_continued: u32 = 0; + let mut write_restarted_zero: u32 = 0; + let mut write_restarted_nonzero: u32 = 0; + let mut write_stopped_zero: u32 = 0; + let mut write_stopped_nonzero: u32 = 0; + let mut read_complete: u32 = 0; + let mut read_need_more: u32 = 0; + let mut read_early_stop: u32 = 0; + let mut repeated_start_count: u32 = 0; + let mut probe_count: u32 = 0; + + loop { + let mut r_buf = [0xAAu8; SLAVE_BUFLEN]; + let mut t_buf = [0xAAu8; SLAVE_BUFLEN]; + + // Per-iteration keyed TX pattern: t_buf[j] = (j + key) mod 256. + // The master checks the increment r_buf[j] - r_buf[0] == j (mod + // 256). This catches cross-iteration buffer bleed without + // requiring shared state between tasks. + let key = slv_iter as u8; + for (j, e) in t_buf.iter_mut().enumerate() { + *e = (j as u8).wrapping_add(key); + } + + // listen() with timeout — wedge on the listen side panics. + breadcrumb(0x10, slv_iter); + let req = match with_timeout( + PER_TRANSFER_TIMEOUT, + TargetI2c::::listen(&mut slave), + ) + .await + { + Err(_) => wedge_panic!("WEDGE: slave.listen() timed out at slv_iter={}", slv_iter), + Ok(Err(e)) => wedge_panic!( + "slave.listen() err at slv_iter={}: {:?}", + slv_iter, + defmt::Debug2Format(&e) + ), + Ok(Ok(r)) => r, + }; + breadcrumb(0x11, slv_iter); + + let was_expecting_restart = expect_repeated_start; + expect_repeated_start = false; + + match req { + Request::Stop(addr) => { + breadcrumb(0x12, slv_iter); + probe_count += 1; + info!("Stop @ 0x{:02X} (probe)", addr); + if was_expecting_restart { + warn!( + "RACE WATCH: prior respond_to_* reported Restarted but listen() returned Stop(0x{:02X}); expected RepeatedStart", + addr + ); + } + } + Request::RepeatedStart(prev_addr) => { + breadcrumb(0x13, slv_iter); + repeated_start_count += 1; + info!("RepeatedStart from prev @ 0x{:02X}", prev_addr); + if !was_expecting_restart { + warn!( + "RACE WATCH: RepeatedStart(0x{:02X}) surfaced without a prior Restarted(_)", + prev_addr + ); + } + } + Request::Read(addr) => { + breadcrumb(0x14, slv_iter); + info!("Read @ 0x{:02X}", addr); + if was_expecting_restart { + info!("(consumed expected RepeatedStart edge before Read)"); + } + loop { + breadcrumb(0x15, slv_iter); + let r = match with_timeout( + PER_TRANSFER_TIMEOUT, + TargetI2c::::respond_to_read(&mut slave, &t_buf), + ) + .await + { + Err(_) => wedge_panic!("WEDGE: respond_to_read at slv_iter={}", slv_iter), + Ok(Err(e)) => wedge_panic!( + "respond_to_read err at slv_iter={}: {:?}", + slv_iter, + defmt::Debug2Format(&e) + ), + Ok(Ok(r)) => r, + }; + breadcrumb(0x16, slv_iter); + match r { + ReadStatus::Complete(n) => { + read_complete += 1; + info!("Read complete with {} bytes", n); + break; + } + ReadStatus::EarlyStop(n) => { + read_early_stop += 1; + info!("Read terminated by controller after {} bytes", n); + break; + } + ReadStatus::NeedMore(n) => { + read_need_more += 1; + info!("Read NeedMore: sent {} bytes so far, more requested", n); + // Loop and supply `t_buf` again. + } + _ => { + // ReadStatus is #[non_exhaustive]; ignore. + info!("Read: unknown status variant"); + break; + } + } + } + } + Request::Write(addr) => { + breadcrumb(0x17, slv_iter); + info!("Write @ 0x{:02X}", addr); + if was_expecting_restart { + info!("(consumed expected RepeatedStart edge before Write)"); + } + loop { + breadcrumb(0x18, slv_iter); + let r = match with_timeout( + PER_TRANSFER_TIMEOUT, + TargetI2c::::respond_to_write(&mut slave, &mut r_buf), + ) + .await + { + Err(_) => wedge_panic!("WEDGE: respond_to_write at slv_iter={}", slv_iter), + Ok(Err(e)) => wedge_panic!( + "respond_to_write err at slv_iter={}: {:?}", + slv_iter, + defmt::Debug2Format(&e) + ), + Ok(Ok(r)) => r, + }; + breadcrumb(0x19, slv_iter); + match r { + WriteStatus::BufferFull(n) => { + write_continued += 1; + info!("Write BufferFull after {} bytes — supplying more buffer space", n); + // Loop and continue draining. + } + WriteStatus::Stopped(n) => { + if n == 0 { + write_stopped_zero += 1; + } else { + write_stopped_nonzero += 1; + } + info!("Write stopped after {} bytes", n); + break; + } + WriteStatus::Restarted(n) => { + if n == 0 { + write_restarted_zero += 1; + info!("Write restarted after 0 bytes — next listen will surface RepeatedStart"); + warn!( + "RACE WATCH: WriteStatus::Restarted(0) — buffer-boundary surfacing artifact under test" + ); + } else { + write_restarted_nonzero += 1; + info!("Write restarted after {} bytes", n); + } + // Do NOT call recover() — the Sr+ADDR is queued + // on the wire and the next listen() must surface + // it as RepeatedStart. + expect_repeated_start = true; + break; + } + _ => { + // WriteStatus is #[non_exhaustive]; ignore. + info!("Write: unknown status variant"); + break; + } + } + } + } + _ => { + info!("unhandled request variant"); + } + } + + slv_iter = slv_iter.wrapping_add(1); + + if slv_iter % REPORT_EVERY == 0 { + info!( + "[slave] iter={} cont={} restZ={} restN={} stopZ={} stopN={} readC={} readNM={} readES={} rstart={} probe={}", + slv_iter, + write_continued, + write_restarted_zero, + write_restarted_nonzero, + write_stopped_zero, + write_stopped_nonzero, + read_complete, + read_need_more, + read_early_stop, + repeated_start_count, + probe_count + ); + } + } +} + +// --------------------------------------------------------------------------- +// Master service +// --------------------------------------------------------------------------- + +#[embassy_executor::task] +async fn master_service(mut master: I2cMaster<'static, Async>) { + let mut w_buf = [0u8; MASTER_MAX_WRITE]; + let mut r_buf = [0u8; MASTER_MAX_READ]; + + let mut iter: u32 = 0; + + loop { + // Round-robin sweep over (k, m). k_window = 4 entries, m_window = 3 + // entries; together that's 12 unique pairs in 12 iterations. + let k = BOUNDARY_K_MIN + ((iter as usize) % (BOUNDARY_K_MAX - BOUNDARY_K_MIN + 1)); + let read_len = READ_LEN_MIN + ((iter as usize) % (READ_LEN_MAX - READ_LEN_MIN + 1)); + let write_len = k * SLAVE_BUFLEN; + + // Tag the write payload with the iter low byte for forensics on + // the slave side if a future test wants to inspect r_buf there. + // Content is otherwise irrelevant to the soak's verification. + for (j, e) in w_buf[..write_len].iter_mut().enumerate() { + *e = (j as u8).wrapping_add(iter as u8); + } + for e in r_buf[..read_len].iter_mut() { + *e = 0; + } + breadcrumb(0x20, iter); + + breadcrumb(0x21, iter); + match with_timeout( + PER_TRANSFER_TIMEOUT, + master.write_read(ADDR, &w_buf[..write_len], &mut r_buf[..read_len]), + ) + .await + { + Err(_) => wedge_panic!( + "WEDGE: master.write_read timed out at iter={}, k={}, m={}", + iter, + k, + read_len + ), + Ok(Err(e)) => wedge_panic!( + "master.write_read err at iter={}, k={}, m={}: {:?}", + iter, + k, + read_len, + defmt::Debug2Format(&e) + ), + Ok(Ok(())) => { + breadcrumb(0x22, iter); + // Verify the read buffer is a monotonic increment starting + // from `r_buf[0]`. Slave fills `t_buf[j] = (j + key) mod + // 256` so the difference `r_buf[j] - r_buf[0]` must equal + // `j` (mod 256) regardless of which slave iteration + // produced the data. + let base = r_buf[0]; + breadcrumb(0x23, iter); + for j in 1..read_len { + let expect = base.wrapping_add(j as u8); + if r_buf[j] != expect { + wedge_panic!( + "READ MISMATCH at iter={}, k={}, m={}, j={}: got 0x{:02X}, expected 0x{:02X} (base=0x{:02X})", + iter, + k, + read_len, + j, + r_buf[j], + expect, + base + ); + } + } + breadcrumb(0x24, iter); + } + } + + if iter % REPORT_EVERY == 0 { + info!( + "[master] iter={} k={} m={} write_len={}", + iter, k, read_len, write_len + ); + } + iter = iter.wrapping_add(1); + } +} + +// --------------------------------------------------------------------------- +// Entry point +// --------------------------------------------------------------------------- + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_imxrt::init(Default::default()); + + info!( + "i2c write_read boundary soak (S={}, k=[{}..={}], m=[{}..={}], timeout={}ms)", + SLAVE_BUFLEN, + BOUNDARY_K_MIN, + BOUNDARY_K_MAX, + READ_LEN_MIN, + READ_LEN_MAX, + PER_TRANSFER_TIMEOUT.as_millis() + ); + + let slave = I2cSlave::new_async( + p.FLEXCOMM2, + p.PIO0_18, + p.PIO0_17, + Irqs, + SLAVE_ADDR.unwrap(), + p.DMA0_CH4, + ) + .unwrap(); + + let cfg = i2c::master::Config { + speed: i2c::master::Speed::Fast, + duty_cycle: DutyCycle::new(50).unwrap(), + ..Default::default() + }; + let master = I2cMaster::new_async(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Irqs, cfg, p.DMA0_CH9).unwrap(); + + spawner.spawn(slave_service(slave).unwrap()); + spawner.spawn(master_service(master).unwrap()); +} diff --git a/src/i2c/mod.rs b/src/i2c/mod.rs index eac4c1f7..99396ce0 100644 --- a/src/i2c/mod.rs +++ b/src/i2c/mod.rs @@ -236,9 +236,18 @@ macro_rules! impl_scl { ($piom_n:ident, $fn:ident, $fcn:ident) => { impl SclPin for crate::peripherals::$piom_n { fn as_scl(&self) { - // UM11147 table 556 pg 550 + // UM11147 table 556 pg 550. + // + // Pull-up enabled so internal pull-ups can drive the + // line high when neither end is asserting it low. This + // is required for board configurations without external + // pull-up resistors (e.g. the FC ↔ FC loopback on the + // RT685S-EVK, which wire-ands two pins directly). On a + // real I2C bus with external pull-ups, the internal + // ~40 kΩ-100 kΩ pull-up simply parallels them — a + // negligible effect on rise time. self.set_function(crate::iopctl::Function::$fn) - .set_pull(crate::iopctl::Pull::None) + .set_pull(crate::iopctl::Pull::Up) .enable_input_buffer() .set_slew_rate(crate::gpio::SlewRate::Slow) .set_drive_strength(crate::gpio::DriveStrength::Normal) @@ -253,9 +262,11 @@ macro_rules! impl_sda { ($piom_n:ident, $fn:ident, $fcn:ident) => { impl SdaPin for crate::peripherals::$piom_n { fn as_sda(&self) { - // UM11147 table 556 pg 550 + // UM11147 table 556 pg 550. + // + // See `impl_scl!` for the pull-up rationale. self.set_function(crate::iopctl::Function::$fn) - .set_pull(crate::iopctl::Pull::None) + .set_pull(crate::iopctl::Pull::Up) .enable_input_buffer() .set_slew_rate(crate::gpio::SlewRate::Slow) .set_drive_strength(crate::gpio::DriveStrength::Normal) From 286df1f23b07ce6c6bf66e59f59ec9ee2de2d095 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 9 Jun 2026 07:37:01 -0700 Subject: [PATCH 19/27] i2c::slave: classify bus state on async respond_* re-entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The async `respond_to_write` and `respond_to_read` entry guards both required `slvstate` to be `slave_receive` / `slave_transmit` and treated everything else as `TransferError::ReadFail` / `TransferError::WriteFail` (the only exception was an entry-time `slvdesel`, which `respond_to_write` mapped to `Stopped(0)`). That is correct for the straight-through case where the caller has just returned from `listen()`, but wrong for the continuation case where the caller is looping back with a fresh buffer after a prior call returned `Response::WriteContinued` / `Response::ReadContinued`. Between the previous call returning and the re-entry being polled the executor still runs `info!` lines, sets up `with_timeout`, polls other ready futures, etc. At 400 kHz the controller can finish its remaining bytes, ACK its last byte, and issue Sr+ADDR+R/W in that gap. When the re-entry then samples `stat`, `slvstate` is `slave_address` (with `slvpending` asserted waiting for SW to ACK/NACK the new address phase) rather than `slave_receive` / `slave_transmit`. The old guard fabricated a `ReadFail` / `WriteFail`, which the i2c-write-read- boundary-soak example surfaced as a `defmt::panic!` from its `Ok(Err(e))` arm — a HardFault visible to probe-rs as `Firmware exited unexpectedly: Exception` because the defmt panic message and breadcrumb dump were dropped by defmt-rtt's saturated host-side buffer before the `udf #0` halt landed. The blocking flavour was unaffected because it polls inside the for-loop and naturally `break`s out of the loop when `slave_address` fires, then the post-loop arm queues the restart edge and returns `Response::Restarted(xfer_count)`. Replace both async entry guards with re-entry classification that mirrors what the post-poll path already emits, so the continuation case sees uniform `Response` shapes regardless of when the controller terminated: * `slvdesel` set — clear the latch and return `Response::Stopped(0)` / `Response::ReadEarlyStop(0)`. * `slvstate == slave_address` — queue `EdgeKind::RepeatedStart` and return `Response::Restarted(0)`. The next `listen()` then continues the address phase and surfaces the new direction normally. * other non-receive / non-transmit state — keep returning `TransferError::ReadFail` / `TransferError::WriteFail`, the caller must `recover()`. `respond_to_read` got the symmetric treatment for completeness; the current i2c-write-read-boundary-soak example keeps `READ_LEN_MAX < SLAVE_BUFLEN` so the read leg never triggers `ReadContinued` and the race is not directly exercised on the read path, but the same window exists. Validated on rt685s-evk with the i2c-write-read-boundary-soak example (FC2 slave ↔ FC4 master loopback, Fast/400 kHz, S=4, k∈[1..=4], m∈[1..=3]). Pre-fix: HardFault after ~28 minutes of MCU uptime at slv_iter ≈ 150 000. Post-fix: ran cleanly past 76 minutes of MCU uptime, slv_iter ≈ 11.2 million, no panic / wedge / mismatch / fault anywhere in the RTT log. --- src/i2c/slave.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 81 insertions(+), 6 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index b95472ff..61e844df 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -811,13 +811,53 @@ impl I2cSlave<'_, Async> { let i2c = self.info.regs; let buf_len = buf.len(); - // Verify that we are ready for write + // Re-entry classification. + // + // Two shapes reach this entry point: the inherent caller has just + // returned from `listen()` with `Command::Write` (the + // straight-through case), or it is looping back with a fresh + // buffer after a prior call returned `Response::WriteContinued` + // (the continuation case). In the continuation case the bus state + // may have advanced between the previous call returning and this + // call being polled — the controller can issue a `STOP` (slvdesel + // latches) or a repeated `START` followed by ADDR+R/W (the + // peripheral transitions out of `slave_receive` into + // `slave_address`, with `slvpending` asserted waiting for SW to + // ACK/NACK the new address). + // + // Both terminators are legitimate end-of-transaction events from + // the slave's perspective. Surface them as the same `Response` + // variants the post-poll path emits so the caller's outer loop + // sees a uniform shape regardless of when the controller + // terminated. The blocking flavour already handles this via its + // polling loop; the async flavour must check explicitly because + // it arms DMA before awaiting and needs the bus to actually be in + // `slave_receive` for the DMA to make forward progress. let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + // Controller issued STOP — either between calls (continuation + // case) or this is a zero-byte write that the caller + // dispatched after a `Probe`-shaped match. Clear the latch + // before returning so the next `listen()` does not see a + // stale deselect. + i2c.stat().write(|w| w.slvdesel().deselected()); + return Ok(Response::Stopped(0)); + } + if stat.slvstate().is_slave_address() { + // Controller issued Sr+ADDR between BufferFull continuations. + // The new address phase is latched and waiting for SW + // ACK/NACK; the next `listen()` will continue_ it and pick + // up the new direction. Queue the RepeatedStart edge for any + // trait caller and report Restarted so the outer loop loops + // back to `listen()` instead of arming a DMA transfer for a + // transaction that no longer exists. + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(0)); + } if !stat.slvstate().is_slave_receive() { - // 0 byte write - if stat.slvdesel().is_deselected() { - return Ok(Response::Stopped(0)); - } + // The peripheral is in some other transient state we do not + // know how to drain from here. Surface as a read failure so + // the caller can recover(). return Err(TransferError::ReadFail.into()); } @@ -897,8 +937,43 @@ impl I2cSlave<'_, Async> { let i2c = self.info.regs; let buf_len = buf.len(); + // Re-entry classification. + // + // Two shapes reach this entry point: the inherent caller has just + // returned from `listen()` with `Command::Read` (the + // straight-through case), or it is looping back with a fresh + // buffer after a prior call returned `Response::ReadContinued` + // (the continuation case). In the continuation case the bus state + // may have advanced between the previous call returning and this + // call being polled — the controller can NACK+STOP (slvdesel + // latches) or issue a repeated `START` followed by ADDR+R/W (the + // peripheral transitions out of `slave_transmit` into + // `slave_address`, with `slvpending` asserted waiting for SW to + // ACK/NACK the new address). + // + // Surface both terminators with the same `Response` variants the + // post-poll path emits so the caller's outer loop sees a uniform + // shape regardless of when the controller terminated. Mirrors the + // re-entry classification in `respond_to_write`. + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + // Controller NACKed + STOPped. Clear the latch and report as + // `ReadEarlyStop(0)` — no bytes from this call's buffer + // reached the wire (the buffer is untouched). The caller can + // distinguish "buffer fully consumed at termination" from + // "buffer not consumed at termination" via the variant. + i2c.stat().write(|w| w.slvdesel().deselected()); + return Ok(Response::ReadEarlyStop(0)); + } + if stat.slvstate().is_slave_address() { + // Controller issued Sr+ADDR between ReadContinued + // continuations. Queue the RepeatedStart edge and report + // Restarted so the outer loop loops back to `listen()`. + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(0)); + } // Verify that we are ready for transmit - if !i2c.stat().read().slvstate().is_slave_transmit() { + if !stat.slvstate().is_slave_transmit() { return Err(TransferError::WriteFail.into()); } From 3a7b6e7ccda27af6190d69d1b79b7c165bf3cd23 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 11 Jun 2026 06:06:33 -0700 Subject: [PATCH 20/27] i2c::slave: re-check bus state after arming SLVDMA Yesterday's classify-bus-state fix (286df1f) reduced the mean time between failures on the i2c-write-read-boundary-soak from ~28 minutes to ~3 hours, but did not eliminate the wedge. The remaining failure mode was a master-side `WEDGE: master.write_read timed out` that landed with the slave stuck inside `respond_to_write`'s `poll_fn` await, between bc[0x18] ("about to await respond_to_write") and the 0x19 ("returned") that never came. Root cause is a window-of-vulnerability between the entry guard's `stat.read()` and the `i2c.slvctl().write(|w| w.slvdma().enabled())` that arms DMA. The flow inside the async `respond_to_write` is: 1. entry guard reads `stat` -> sees `slvstate == slave_receive` 2. enable SLVDMA 3. install NakGuard / dma_guard 4. construct the DMA Transfer 5. await poll_fn If the controller finishes its current data phase and issues Sr+ADDR+R between steps 1 and 5, the SLV peripheral transitions out of `slave_receive` (to `slave_address`, and -- in DMA mode -- auto-acknowledges the address and lands in `slave_transmit`). Per the mimxrt633s-pac STAT documentation (matching UM11147 25.7.2.2.2), `SLVPENDING is not set when the DMA is handling an event`. With SLVDMA now set, this suppression covers the auto-handled address phase, so `slvpending` does not raise on the bus state change. The DMA channel also never completes (it is armed for receive into a buffer that will never receive bytes, the bus having moved past the receive phase). Neither of the future's wake sources fires, and the await wedges until its outer `with_timeout` cancels it. Yesterday's fix only checked the entry guard. The window between that read and DMA arm -- a few-dozen-clock instruction sequence under release builds -- is just long enough at 400 kHz to lose the race against a tight write_read pattern on a fast controller. This commit adds a second `stat` read AFTER all the guards are installed but BEFORE awaiting. If the bus has advanced past the expected direction, drop into the same classification the post-poll path uses: * slvdesel set -> clear the latch, return Stopped(0) / ReadEarlyStop(0) * slvstate latched in slave_address -> queue the RepeatedStart edge for any trait caller, return Restarted(0) The dropped guards (`_transfer`, `_dma_guard`) handle the SLVDMA teardown and DMA channel abort on the early return; `nak_guard` is defused explicitly on the write path because we never moved any bytes and there is no in-flight transmission to NAK. Validated on rt685s-evk with the i2c-write-read-boundary-soak example (FC2 slave <-> FC4 master loopback, Fast/400 kHz, S=4, k in [1..=4], m in [1..=3]). Pre-fix MTBF was a few hours. Post-fix run achieved 19h 57min of continuous clean MCU uptime with zero panic / wedge / mismatch / fault markers across the full rotated log set (~226 MB of RTT output lefover from the 3x 100MiB rotated logs) and zero entries in the forensic Err-path capture ring before the runner was stopped for cleanup. --- src/i2c/slave.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index 61e844df..71e17e29 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -883,6 +883,35 @@ impl I2cSlave<'_, Async> { i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); }); + // Re-check bus state after arming DMA and installing guards. + // + // There is a tiny window between the entry guard's `stat` read and + // the DMA arm during which the controller can complete the data + // phase and issue Sr+ADDR+R/W. Once DMA is armed (SLVDMA bit set), + // the FlexComm peripheral suppresses `slvpending` for events it + // handles — including the auto-handled new address phase. If we + // entered with `slvstate == slave_receive` but the bus then moved + // to `slave_address` (or `slave_transmit` after DMA-mode address + // auto-ack) before we got here, no further wake source will fire: + // DMA stays armed waiting for bytes that never come, slvpending + // is suppressed by the active DMA. The future then wedges until + // its `with_timeout` outer guard cancels it. + // + // The fix: re-read stat AFTER arming. If the bus has already + // advanced, drop the DMA (handled by `_dma_guard` / `_transfer` + // drop) and classify the same way the post-poll path would. + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + nak_guard.defuse(); + return Ok(Response::Stopped(0)); + } + if stat.slvstate().is_slave_address() { + self.queue_edge(EdgeKind::RepeatedStart); + nak_guard.defuse(); + return Ok(Response::Restarted(0)); + } + poll_fn(|cx| { let i2c = self.info.regs; @@ -995,6 +1024,21 @@ impl I2cSlave<'_, Async> { i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); }); + // Re-check bus state after arming DMA. See the equivalent block in + // `respond_to_write` for the rationale: the window between entry + // guard and DMA arm is just long enough for the controller to + // issue Sr+ADDR+R/W or STOP, after which SLVDMA suppression of + // `slvpending` would wedge the await. + let stat = i2c.stat().read(); + if stat.slvdesel().is_deselected() { + i2c.stat().write(|w| w.slvdesel().deselected()); + return Ok(Response::ReadEarlyStop(0)); + } + if stat.slvstate().is_slave_address() { + self.queue_edge(EdgeKind::RepeatedStart); + return Ok(Response::Restarted(0)); + } + poll_fn(|cx| { let i2c = self.info.regs; From ddfb0d4997c1bf9ef031e84225b667b79fa9a287 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 11 Jun 2026 06:54:07 -0700 Subject: [PATCH 21/27] cargo +nightly fmt --- .../src/bin/i2c-write-read-boundary-soak.rs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs index f9930bde..6f42b06f 100644 --- a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs +++ b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs @@ -385,12 +385,7 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { // listen() with timeout — wedge on the listen side panics. breadcrumb(0x10, slv_iter); - let req = match with_timeout( - PER_TRANSFER_TIMEOUT, - TargetI2c::::listen(&mut slave), - ) - .await - { + let req = match with_timeout(PER_TRANSFER_TIMEOUT, TargetI2c::::listen(&mut slave)).await { Err(_) => wedge_panic!("WEDGE: slave.listen() timed out at slv_iter={}", slv_iter), Ok(Err(e)) => wedge_panic!( "slave.listen() err at slv_iter={}: {:?}", @@ -641,10 +636,7 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { } if iter % REPORT_EVERY == 0 { - info!( - "[master] iter={} k={} m={} write_len={}", - iter, k, read_len, write_len - ); + info!("[master] iter={} k={} m={} write_len={}", iter, k, read_len, write_len); } iter = iter.wrapping_add(1); } @@ -668,15 +660,7 @@ async fn main(spawner: Spawner) { PER_TRANSFER_TIMEOUT.as_millis() ); - let slave = I2cSlave::new_async( - p.FLEXCOMM2, - p.PIO0_18, - p.PIO0_17, - Irqs, - SLAVE_ADDR.unwrap(), - p.DMA0_CH4, - ) - .unwrap(); + let slave = I2cSlave::new_async(p.FLEXCOMM2, p.PIO0_18, p.PIO0_17, Irqs, SLAVE_ADDR.unwrap(), p.DMA0_CH4).unwrap(); let cfg = i2c::master::Config { speed: i2c::master::Speed::Fast, From 33f1cd331500ea02088ad488d5006a7d13d8ec5d Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 15 Jun 2026 09:12:09 -0700 Subject: [PATCH 22/27] i2c::slave: configure DMA channel before enabling SLVDMA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UM11147 §24.7.7.1 and §24.7.7.2 describe the slave DMA arm sequence as: program the DMA channel first, then set the peripheral's SLVDMA bit. Enabling SLVDMA first allows the SLV peripheral to assert a DMA request on a not-yet-armed channel — the request is missed entirely. The resulting failure mode is the slave stretching SCL indefinitely while the master times out: no slvpending fires (the peripheral is in DMA mode), no DMA completion fires (the channel never received the request), and the inherent poll_fn waits forever until the outer with_timeout cancels it. The master path in i2c::master already follows this ordering with an explicit comment citing the same UM sections. Mirror the same sequence in both espond_to_write (line 880 area) and espond_to_read (line 1025 area). Captured during repeated wedges in the i2c-write-read-boundary-soak example on the rt685s-evk. The fix is verified by an in-tree hardware snapshot tool (separate commit) which confirms that after the reorder the slave correctly drives SCL and waits in slave_receive with DMA armed and xfercount > 0, instead of wedging on a missed request. --- src/i2c/slave.rs | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/i2c/slave.rs b/src/i2c/slave.rs index 71e17e29..f114ca42 100644 --- a/src/i2c/slave.rs +++ b/src/i2c/slave.rs @@ -863,17 +863,32 @@ impl I2cSlave<'_, Async> { let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; - // Enable DMA + let options = dma::transfer::TransferOptions::default(); + // Configure + enable + trigger the DMA channel BEFORE enabling + // SLVDMA on the peripheral. + // + // Per UM11147 §24.7.7.1 / §24.7.7.2 (matching the master path's + // sequencing in `i2c::master`): the DMA channel must be programmed + // first and only then may the peripheral's SLVDMA bit be set. + // Enabling SLVDMA first lets the SLV peripheral assert a DMA + // request line to a not-yet-configured channel — the request can + // be missed entirely. In practice this manifests as the slave + // arming DMA on a pending data-byte event, never servicing it, + // and stretching SCL indefinitely while the master times out. + // + // Keep a reference to Transfer so it does not get dropped (drop + // would abort the DMA transfer). + let _transfer = dma_channel.read_from_peripheral(i2c.slvdat().as_ptr() as *mut u8, buf, options); + + // Enable DMA on the SLV peripheral. The channel is now armed and + // listening, so any pending or fresh slvpending event will be + // serviced by DMA. i2c.slvctl().write(|w| w.slvdma().enabled()); // Enable interrupt i2c.intenset() .write(|w| w.slvpendingen().enabled().slvdeselen().enabled()); - let options = dma::transfer::TransferOptions::default(); - // Keep a reference to Transfer so it does not get dropped and aborted the DMA transfer - let _transfer = dma_channel.read_from_peripheral(i2c.slvdat().as_ptr() as *mut u8, buf, options); - // Hold guard to make sure that we send a NAK on cancellation // Since drop order is reverse, this comes BEFORE the dma guard, // so the DMA guard will be dropped FIRST, then the NAK guard. @@ -1006,19 +1021,22 @@ impl I2cSlave<'_, Async> { return Err(TransferError::WriteFail.into()); } - // Enable DMA + let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; + + let options = dma::transfer::TransferOptions::default(); + // Configure + enable + trigger the DMA channel BEFORE enabling + // SLVDMA on the peripheral. See the equivalent block in + // `respond_to_write` for the UM11147 §24.7.7.1 / §24.7.7.2 + // rationale. + let _transfer = dma_channel.write_to_peripheral(buf, i2c.slvdat().as_ptr() as *mut u8, options); + + // Enable DMA on the SLV peripheral. i2c.slvctl().write(|w| w.slvdma().enabled()); // Enable interrupts i2c.intenset() .write(|w| w.slvpendingen().enabled().slvdeselen().enabled()); - let options = dma::transfer::TransferOptions::default(); - let dma_channel = self.dma_ch.as_ref().ok_or(Error::UnsupportedConfiguration)?; - - // Keep a reference to Transfer so it does not get dropped and aborted the DMA transfer - let _transfer = dma_channel.write_to_peripheral(buf, i2c.slvdat().as_ptr() as *mut u8, options); - // Hold guard to disable on cancellation or completion let _dma_guard = OnDrop::new(|| { i2c.slvctl().modify(|_r, w| w.slvdma().disabled()); From 8a95cb301290b27adae62b0fc37c133958014382 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Mon, 15 Jun 2026 09:12:31 -0700 Subject: [PATCH 23/27] examples: add hardware-state snapshot to i2c boundary soak Add a wedge_watch task that periodically (every 25 ms) samples FC2 and DMA channel 4 registers and stores them in a static HW_SNAPSHOT. wedge_panic! now dumps the most-recent snapshot BEFORE the breadcrumb dump and panic, so the RTT log captures the in-wedge state before drop guards in espond_to_* clear SLVDMA and abort the channel. This was critical in distinguishing a real slave-side wedge (DMA not arming, SCL stretched) from a master-side stall (chip healthy, waiting for bytes that never come). The snapshot decodes: - FC2 STAT.slvpending / slvstate / slvnotstr / slvsel / slvdesel - FC2 SLVCTL.slvdma - FC2 INTENSET.slvpendingen / slvdeselen - DMA ACTIVE0 / BUSY0 bits for channel 4 - DMA CH4 XFERCFG xfercount / xfer_en Also adjust the example for Mole bring-up: comment out master_service spawn (FC4 master is replaced by an external Mole running imxrt-boundary-repro.moleasm), and remove the outer listen() timeout since Mole can sit idle for long stretches between probes. Per-await respond_to_* timeouts are retained. The snapshot infrastructure uses absolute MMIO addresses with volatile reads to avoid borrow conflicts with the I2cSlave that owns the peripheral. Tear-detection via gen_start/gen_end atomic counters. --- .../src/bin/i2c-write-read-boundary-soak.rs | 226 ++++++++++++++++-- 1 file changed, 212 insertions(+), 14 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs index 6f42b06f..3feb8114 100644 --- a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs +++ b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs @@ -207,7 +207,7 @@ use embassy_imxrt::i2c::slave::{Address, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_imxrt_examples as _; -use embassy_time::{Duration, with_timeout}; +use embassy_time::{Duration, Instant, Timer, with_timeout}; use embedded_hal_async::i2c::I2c; use embedded_mcu_hal::i2c::SevenBitAddress; use embedded_mcu_hal::i2c::target::asynch::I2c as TargetI2c; @@ -263,6 +263,184 @@ static BREADCRUMB_RING: [AtomicU32; BREADCRUMB_RING_LEN] = { [Z; BREADCRUMB_RING_LEN] }; +// --------------------------------------------------------------------------- +// Hardware-state watchdog +// --------------------------------------------------------------------------- +// +// The slave `respond_to_write` future installs `_dma_guard` / +// `nak_guard` drop guards that, on `with_timeout` cancellation, clear +// SLVDMA and NAK the pending byte. By the time we observe `Err(_)` from +// `with_timeout` in `slave_service`, those guards have already run and +// the I2C peripheral state no longer reflects the wedge. +// +// Instead, this task runs independently and samples FC2 + DMA channel 4 +// state every WEDGE_WATCH_PERIOD into `HW_SNAPSHOT`. On a wedge_panic, +// `dump_breadcrumbs()` prints the most recent snapshot, which captures +// the in-wedge state ~50 ms before the timeout fired. +// +// Raw absolute addresses are used to avoid PAC borrow conflicts with the +// I2cSlave that owns the peripheral. The addresses are stable per the +// MIMXRT633S memory map: +// FC2 (I2C2) base = 0x4010_8000 +// STAT @ 0x4010_8804 +// INTENSET @ 0x4010_8808 +// SLVCTL @ 0x4010_8840 +// DMA0 base = 0x4010_4000 +// ACTIVE0 @ 0x4010_4030 (bit N = ch N active) +// BUSY0 @ 0x4010_4038 +// Channel 4 base @ 0x4010_4440 (CFG/CTLSTAT/XFERCFG at +0/+4/+8) +// XFERCFG ch4 @ 0x4010_4448 + +const HW_FC2_STAT: usize = 0x4010_8804; +const HW_FC2_INTENSET: usize = 0x4010_8808; +const HW_FC2_SLVCTL: usize = 0x4010_8840; +const HW_DMA_ACTIVE0: usize = 0x4010_4030; +const HW_DMA_BUSY0: usize = 0x4010_4038; +const HW_DMA_CH4_XFERCFG: usize = 0x4010_4448; + +const WEDGE_WATCH_PERIOD: Duration = Duration::from_millis(25); + +/// Snapshot layout — keep in sync with `dump_hw_snapshot()`. +/// +/// Generation counter brackets the writes for tear-detection. Writers +/// bump `gen_start`, write words, then `gen_end := gen_start`. Reader +/// retries until `gen_start == gen_end`. +#[repr(C)] +struct HwSnapshot { + gen_start: AtomicU32, + gen_end: AtomicU32, + /// Monotonic micros at the time of the sample. 0 = never sampled. + ts_us: AtomicU32, + fc2_stat: AtomicU32, + fc2_intenset: AtomicU32, + fc2_slvctl: AtomicU32, + dma_active0: AtomicU32, + dma_busy0: AtomicU32, + dma_ch4_xfercfg: AtomicU32, +} + +static HW_SNAPSHOT: HwSnapshot = HwSnapshot { + gen_start: AtomicU32::new(0), + gen_end: AtomicU32::new(0), + ts_us: AtomicU32::new(0), + fc2_stat: AtomicU32::new(0), + fc2_intenset: AtomicU32::new(0), + fc2_slvctl: AtomicU32::new(0), + dma_active0: AtomicU32::new(0), + dma_busy0: AtomicU32::new(0), + dma_ch4_xfercfg: AtomicU32::new(0), +}; + +/// Sample the hardware once and publish to `HW_SNAPSHOT`. Volatile +/// reads on raw addresses — safe because we never write through these +/// pointers, the addresses are guaranteed valid for the lifetime of the +/// program, and 32-bit aligned reads of MMIO registers are atomic on +/// Cortex-M33. +#[inline(always)] +fn sample_hw() { + // SAFETY: see function doc. + let stat = unsafe { core::ptr::read_volatile(HW_FC2_STAT as *const u32) }; + let intenset = unsafe { core::ptr::read_volatile(HW_FC2_INTENSET as *const u32) }; + let slvctl = unsafe { core::ptr::read_volatile(HW_FC2_SLVCTL as *const u32) }; + let active0 = unsafe { core::ptr::read_volatile(HW_DMA_ACTIVE0 as *const u32) }; + let busy0 = unsafe { core::ptr::read_volatile(HW_DMA_BUSY0 as *const u32) }; + let xfercfg = unsafe { core::ptr::read_volatile(HW_DMA_CH4_XFERCFG as *const u32) }; + + let ts = (Instant::now().as_micros() & 0xFFFF_FFFF) as u32; + let g = HW_SNAPSHOT.gen_start.fetch_add(1, Ordering::AcqRel).wrapping_add(1); + HW_SNAPSHOT.ts_us.store(ts, Ordering::Relaxed); + HW_SNAPSHOT.fc2_stat.store(stat, Ordering::Relaxed); + HW_SNAPSHOT.fc2_intenset.store(intenset, Ordering::Relaxed); + HW_SNAPSHOT.fc2_slvctl.store(slvctl, Ordering::Relaxed); + HW_SNAPSHOT.dma_active0.store(active0, Ordering::Relaxed); + HW_SNAPSHOT.dma_busy0.store(busy0, Ordering::Relaxed); + HW_SNAPSHOT.dma_ch4_xfercfg.store(xfercfg, Ordering::Relaxed); + HW_SNAPSHOT.gen_end.store(g, Ordering::Release); +} + +/// Dump the latest hardware snapshot. Tear-protected via the +/// gen_start/gen_end counters; falls back to a "torn" message if the +/// reader caught a snapshot mid-update (unlikely outside of races). +fn dump_hw_snapshot() { + for _ in 0..4 { + let g_end = HW_SNAPSHOT.gen_end.load(Ordering::Acquire); + let ts = HW_SNAPSHOT.ts_us.load(Ordering::Relaxed); + let stat = HW_SNAPSHOT.fc2_stat.load(Ordering::Relaxed); + let intenset = HW_SNAPSHOT.fc2_intenset.load(Ordering::Relaxed); + let slvctl = HW_SNAPSHOT.fc2_slvctl.load(Ordering::Relaxed); + let active0 = HW_SNAPSHOT.dma_active0.load(Ordering::Relaxed); + let busy0 = HW_SNAPSHOT.dma_busy0.load(Ordering::Relaxed); + let xfercfg = HW_SNAPSHOT.dma_ch4_xfercfg.load(Ordering::Relaxed); + let g_start = HW_SNAPSHOT.gen_start.load(Ordering::Acquire); + if g_start == g_end { + if ts == 0 { + error!("---- HW snapshot: never sampled ----"); + } else { + // Decode the bits we care about for STAT. + let stat_slvpending = (stat >> 8) & 1; + let stat_slvstate = (stat >> 9) & 3; + let stat_slvnotstr = (stat >> 11) & 1; + let stat_slvsel = (stat >> 14) & 1; + let stat_slvdesel = (stat >> 15) & 1; + // SLVCTL bit 3 = SLVDMA. + let slvctl_slvdma = (slvctl >> 3) & 1; + // INTENSET bit 8 = slvpendingen, bit 15 = slvdeselen. + let int_slvpend = (intenset >> 8) & 1; + let int_slvdes = (intenset >> 15) & 1; + // DMA ch4 active/busy. + let ch4_active = (active0 >> 4) & 1; + let ch4_busy = (busy0 >> 4) & 1; + // XFERCFG.XFERCOUNT = bits 16..26. + let xfer_count = (xfercfg >> 16) & 0x3FF; + let xfer_en = xfercfg & 1; + + error!( + "---- HW snapshot (ts_us={}, gen={}) ----", + ts, g_end + ); + error!( + " FC2.STAT=0x{:08X} slvpending={} slvstate={} slvnotstr={} slvsel={} slvdesel={}", + stat, stat_slvpending, stat_slvstate, stat_slvnotstr, stat_slvsel, stat_slvdesel + ); + error!( + " FC2.SLVCTL=0x{:08X} slvdma={}", + slvctl, slvctl_slvdma + ); + error!( + " FC2.INTENSET=0x{:08X} slvpendingen={} slvdeselen={}", + intenset, int_slvpend, int_slvdes + ); + error!( + " DMA.ACTIVE0=0x{:08X} ch4_active={}", + active0, ch4_active + ); + error!( + " DMA.BUSY0=0x{:08X} ch4_busy={}", + busy0, ch4_busy + ); + error!( + " DMA.CH4.XFERCFG=0x{:08X} xfercount={} xfer_en={}", + xfercfg, xfer_count, xfer_en + ); + error!("---- end HW snapshot ----"); + } + return; + } + // Torn read; retry. + } + error!("---- HW snapshot: torn after 4 attempts ----"); +} + +/// Watchdog task: sample HW every `WEDGE_WATCH_PERIOD` so the snapshot +/// is at most one period behind reality at any wedge_panic site. +#[embassy_executor::task] +async fn wedge_watch() { + loop { + sample_hw(); + Timer::after(WEDGE_WATCH_PERIOD).await; + } +} + #[inline(always)] fn breadcrumb(site: u8, iter: u32) { let packed = ((site as u32) << 24) | (iter & 0x00FF_FFFF); @@ -294,6 +472,7 @@ fn dump_breadcrumbs() { /// transitions before the chip halts. macro_rules! wedge_panic { ($($arg:tt)*) => {{ + $crate::dump_hw_snapshot(); $crate::dump_breadcrumbs(); defmt::panic!($($arg)*); }}; @@ -383,16 +562,19 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { *e = (j as u8).wrapping_add(key); } - // listen() with timeout — wedge on the listen side panics. + // listen() WITHOUT timeout for the mole bring-up: Mole is the + // external controller and may sit idle for long stretches + // between probes. The intra-transaction respond_to_* awaits + // below still carry PER_TRANSFER_TIMEOUT so a mid-transaction + // wedge is still caught. breadcrumb(0x10, slv_iter); - let req = match with_timeout(PER_TRANSFER_TIMEOUT, TargetI2c::::listen(&mut slave)).await { - Err(_) => wedge_panic!("WEDGE: slave.listen() timed out at slv_iter={}", slv_iter), - Ok(Err(e)) => wedge_panic!( + let req = match TargetI2c::::listen(&mut slave).await { + Err(e) => wedge_panic!( "slave.listen() err at slv_iter={}: {:?}", slv_iter, defmt::Debug2Format(&e) ), - Ok(Ok(r)) => r, + Ok(r) => r, }; breadcrumb(0x11, slv_iter); @@ -559,9 +741,17 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { } // --------------------------------------------------------------------------- -// Master service +// Master service (DISABLED for mole bring-up) +// +// The master leg is replaced by the external Mole running +// `imxrt-boundary-repro.moleasm`. Leaving the task body in source as +// reference only; main no longer spawns it and FC4 is no longer +// initialised. The file will emit dead-code warnings for the master- +// only imports (I2cMaster, DutyCycle, embedded_hal_async::i2c::I2c) +// and the unused MASTER_MAX_* constants; that is expected. // --------------------------------------------------------------------------- +/* #[embassy_executor::task] async fn master_service(mut master: I2cMaster<'static, Async>) { let mut w_buf = [0u8; MASTER_MAX_WRITE]; @@ -641,6 +831,7 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { iter = iter.wrapping_add(1); } } +*/ // --------------------------------------------------------------------------- // Entry point @@ -662,13 +853,20 @@ async fn main(spawner: Spawner) { let slave = I2cSlave::new_async(p.FLEXCOMM2, p.PIO0_18, p.PIO0_17, Irqs, SLAVE_ADDR.unwrap(), p.DMA0_CH4).unwrap(); - let cfg = i2c::master::Config { - speed: i2c::master::Speed::Fast, - duty_cycle: DutyCycle::new(50).unwrap(), - ..Default::default() - }; - let master = I2cMaster::new_async(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Irqs, cfg, p.DMA0_CH9).unwrap(); + // FC4 master is replaced by an external Mole running + // `imxrt-boundary-repro.moleasm`. The cfg / I2cMaster::new_async / + // spawn lines stay in source as commented-out reference; their pins + // (PIO0_29 / PIO0_30) and DMA channel (DMA0_CH9) are now free for + // the loopback wires that go to the Mole instead of FC4. + // + // let cfg = i2c::master::Config { + // speed: i2c::master::Speed::Fast, + // duty_cycle: DutyCycle::new(50).unwrap(), + // ..Default::default() + // }; + // let master = I2cMaster::new_async(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Irqs, cfg, p.DMA0_CH9).unwrap(); spawner.spawn(slave_service(slave).unwrap()); - spawner.spawn(master_service(master).unwrap()); + spawner.spawn(wedge_watch().unwrap()); + // spawner.spawn(master_service(master).unwrap()); } From 24f8b8fbaaec1c04072235c9bff30855846191b2 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 16 Jun 2026 06:06:54 -0700 Subject: [PATCH 24/27] examples: simplify i2c boundary soak to self-contained loopback Strip the debugging instrumentation added while chasing the Mole-driven slave wedge. That investigation concluded the wedge required Mole-side bus interaction (Mole's per-bit stretch timeout, ~43.69 ms) and is not reproducible in on-chip loopback: an FC4->FC2 soak ran 574M slave iterations clean. Removed: * the HW-snapshot watchdog task and its raw FC2/DMA register sampling (wedge_watch, HW_SNAPSHOT, sample_hw, dump_hw_snapshot, STRETCH_CONFIRM_COUNT, the absolute-address register constants), all tuned to Mole's stretch timeout; * the forensic breadcrumb ring and its probe-rs/Python/GDB read-back documentation; * the Mole bring-up scaffolding and stale comments. Restored the FC4->FC2 on-chip loopback master so the soak is self-contained, and kept per-event info logging silenced (the periodic counter summaries are the right granularity for a soak running thousands of transactions per second). Behaviour of the soak itself is unchanged. --- .../src/bin/i2c-write-read-boundary-soak.rs | 555 +++--------------- 1 file changed, 67 insertions(+), 488 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs index 3feb8114..9aea886a 100644 --- a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs +++ b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs @@ -53,9 +53,7 @@ //! FC2 ↔ FC4 loopback on the EVK wires two GPIO pins directly with no //! external resistors. Without pull-ups, every "release" of the line //! leaves it floating and both peripheral state machines stall -//! waiting for SCL/SDA edges that never come — reproducible as the -//! master/slave mutual wedge captured by an early breadcrumb dump on -//! this soak. +//! waiting for SCL/SDA edges that never come. //! //! The driver's `as_scl()`/`as_sda()` enables the SoC's internal //! pull-ups (~40 kΩ-100 kΩ) so this loopback configuration works @@ -65,141 +63,25 @@ //! //! ## What to look for in RTT //! -//! Per master iteration (one of 4×3 = 12 unique `(k, m)` pairs in -//! round-robin): +//! Per-event `info!` logging is intentionally silenced: at thousands of +//! transactions per second it saturates the RTT channel and slows the +//! loop. Instead each task emits a counter summary every `REPORT_EVERY` +//! iterations: //! -//! * 1× `Write @ 0x20` on the slave (from `Request::Write`). -//! * `k`× `Write BufferFull after S bytes — supplying more buffer space`. -//! * 1× `Write restarted after 0 bytes …` + `RACE WATCH: -//! WriteStatus::Restarted(0)` warning. -//! * 1× `RepeatedStart from prev @ 0x20`. -//! * 1× `Read @ 0x20`. -//! * 1× `Read terminated by controller after m bytes` (always EarlyStop -//! because `m < S`). +//! * `[slave] iter=… cont=… restZ=… …` — slave-side event tallies. +//! * `[master] iter=… k=… m=… write_len=…` — master-side progress. //! -//! Counter summaries are emitted by each task every `REPORT_EVERY` -//! iterations. Absence of any `defmt::panic!` over a long run is the -//! pass signal. -//! -//! ## Forensic breadcrumbs -//! -//! Adding per-step `info!` lines slows the loop enough to mask the -//! release-mode HardFault we are chasing. Instead each step in both -//! tasks stores a packed `(site_id, iter)` u32 into a 32-entry static -//! ring buffer (a single relaxed atomic store, no formatting, no -//! critical section). Every `wedge_panic!` call flushes the ring via -//! `defmt::error!` *before* delegating to `defmt::panic!`, so the RTT -//! log carries the last 32 (task, site, iter) transitions immediately -//! preceding any wedge/error/mismatch panic. See `breadcrumb()` and the -//! site-ID table near the top of this file. -//! -//! ## Reading the breadcrumb ring with probe-rs -//! -//! When the failure is our own `wedge_panic!`, the ring contents are -//! already on RTT — no extra step is required. When the failure is a -//! true CPU HardFault (no panic message reached RTT, just -//! `Firmware exited unexpectedly: Exception` from probe-rs), the ring -//! still lives in RAM because `panic-probe`'s `udf` halt loop does not -//! touch user data. Read it directly: -//! -//! ### 1. Discover the symbol addresses -//! -//! ```text -//! $ rust-nm target/thumbv8m.main-none-eabihf/release/i2c-write-read-boundary-soak \ -//! | rg 'BREADCRUMB_(INDEX|RING)' -//! 20081200 D BREADCRUMB_INDEX -//! 20081204 D BREADCRUMB_RING -//! ``` -//! -//! Address values will differ per build; always rediscover after a -//! rebuild. The `D` flag confirms the symbols live in the data -//! (`.bss` / `.data`) region, which `panic-probe`'s `udf` halt does -//! not perturb. -//! -//! ### 2. Read the ring index and the ring -//! -//! `probe-rs read` opens its own debug session on each invocation, so -//! no separate `attach` is required — and a long-lived `attach` in -//! another shell would actually block the read. probe-rs also rejects -//! `_` digit separators in numeric arguments; use plain hex. -//! -//! ```text -//! # 32-bit index (1 word): -//! $ probe-rs read --chip MIMXRT685SFVKB b32 0x20081200 1 -//! 0x00000087 -//! -//! # 32-entry ring (32 words = 128 bytes): -//! $ probe-rs read --chip MIMXRT685SFVKB b32 0x20081204 32 -//! 0x14000086 0x15000086 0x19000086 0x13000086 0x14000087 ... -//! ``` -//! -//! ### 3. Decode entries -//! -//! Each `u32` is packed `(site << 24) | (iter & 0x00FFFFFF)`: -//! -//! * **site** = `(packed >> 24) & 0xFF` — cross-reference with the -//! site-ID table in the source comments above (`0x10`..`0x19` = -//! slave_service, `0x20`..`0x24` = master_service). -//! * **iter** = `packed & 0x00FFFFFF` — the task-local iteration -//! counter at the time the breadcrumb was dropped. Saturates at -//! 24 bits; not unique across long soaks but the local ordering -//! within the last 32 entries is always meaningful. -//! -//! ### 4. Reconstruct chronological order -//! -//! Entries are written round-robin. Let `next = BREADCRUMB_INDEX`, -//! `count = min(next, 32)`, `start = next.wrapping_sub(count)`. Read -//! in order: slots `(start + 0) & 31`, `(start + 1) & 31`, …, -//! `(start + count - 1) & 31`. The on-target `dump_breadcrumbs()` -//! function does the same reconstruction; emulating it off-target -//! recovers the same oldest-to-newest sequence. -//! -//! ### 5. Optional: Python decoder -//! -//! Paste the `probe-rs read` outputs into this short script to -//! reproduce the RTT dump format off-target. Python accepts `_` in -//! numeric literals; the example values below drop them so they match -//! `probe-rs read` output verbatim. -//! -//! ```python -//! #!/usr/bin/env python3 -//! # usage: paste the index value and 32 ring words below, then run. -//! index = 0x00000087 -//! ring = [ -//! 0x14000086, 0x15000086, 0x19000086, 0x13000086, -//! # ... 28 more u32 words from `probe-rs read` ... -//! ] -//! count = min(index, len(ring)) -//! start = (index - count) % len(ring) -//! for i in range(count): -//! packed = ring[(start + i) % len(ring)] -//! site = (packed >> 24) & 0xFF -//! iter = packed & 0x00FFFFFF -//! print(f"bc[{i:2}]: site=0x{site:02X} iter={iter}") -//! ``` -//! -//! ### 6. Optional: GDB stub -//! -//! If you prefer GDB: -//! -//! ```text -//! $ probe-rs gdb --chip MIMXRT685SFVKB & -//! $ arm-none-eabi-gdb \ -//! target/thumbv8m.main-none-eabihf/release/i2c-write-read-boundary-soak -//! (gdb) target remote :1337 -//! (gdb) print/x BREADCRUMB_INDEX -//! (gdb) print/x BREADCRUMB_RING -//! ``` -//! -//! GDB prints the array nicely formatted; apply the same decoding -//! from step 3 to each element. +//! A healthy run shows both counters climbing steadily with `restZ` +//! (zero-byte restarts) and `cont` (buffer-full continuations) growing +//! every iteration and no `RACE WATCH` warnings. Absence of any +//! `defmt::panic!` over a long run is the pass signal; a wedge, error, +//! or read-back mismatch panics immediately with the offending +//! iteration's context. #![no_std] #![no_main] -use core::sync::atomic::{AtomicU32, AtomicUsize, Ordering}; - -use defmt::{error, info, warn}; +use defmt::{info, warn}; use defmt_rtt as _; use embassy_executor::Spawner; use embassy_imxrt::i2c::master::{DutyCycle, I2cMaster}; @@ -207,277 +89,13 @@ use embassy_imxrt::i2c::slave::{Address, I2cSlave}; use embassy_imxrt::i2c::{self, Async}; use embassy_imxrt::{bind_interrupts, peripherals}; use embassy_imxrt_examples as _; -use embassy_time::{Duration, Instant, Timer, with_timeout}; +use embassy_time::{Duration, with_timeout}; use embedded_hal_async::i2c::I2c; use embedded_mcu_hal::i2c::SevenBitAddress; use embedded_mcu_hal::i2c::target::asynch::I2c as TargetI2c; use embedded_mcu_hal::i2c::target::{ReadStatus, Request, WriteStatus}; use panic_probe as _; -// --------------------------------------------------------------------------- -// Forensic breadcrumb ring -// --------------------------------------------------------------------------- -// -// In release mode this soak has been observed to HardFault between -// `Read @ 0x20` and the subsequent `respond_to_read` await, with no -// `WEDGE:` panic message reaching RTT. Adding any per-iteration `info!` -// slows the loop enough to mask the fault. The breadcrumb ring is the -// cheapest possible forensic trail: each checkpoint does one atomic -// store of a packed `(site << 24) | (iter & 0xFFFFFF)` u32 into a -// 32-entry static ring, no formatting and no critical section. -// -// On a wedge `defmt::panic!`, `dump_breadcrumbs!()` flushes the last 32 -// entries via `defmt::error!` *before* delegating to the panic macro. -// That gives us the exact sequence of slave + master steps leading up -// to the failure, even when the per-iteration `info!` lines that would -// usually narrate the state cannot be afforded. -// -// Site IDs are packed into the upper byte: -// -// 0x1_ — slave_service lane -// 0x10 enter loop / about to call listen() -// 0x11 listen() returned -// 0x12 Probe arm -// 0x13 RepeatedStart arm -// 0x14 Read arm entered -// 0x15 about to await respond_to_read -// 0x16 respond_to_read returned, classifying status -// 0x17 Write arm entered -// 0x18 about to await respond_to_write -// 0x19 respond_to_write returned, classifying status -// -// 0x2_ — master_service lane -// 0x20 chose k, m; filled buffers -// 0x21 about to await master.write_read -// 0x22 master.write_read returned Ok -// 0x23 verifying read payload increment -// 0x24 verification passed - -const BREADCRUMB_RING_LEN: usize = 32; -const BREADCRUMB_RING_MASK: usize = BREADCRUMB_RING_LEN - 1; -const _: () = assert!(BREADCRUMB_RING_LEN.is_power_of_two()); - -static BREADCRUMB_INDEX: AtomicUsize = AtomicUsize::new(0); -static BREADCRUMB_RING: [AtomicU32; BREADCRUMB_RING_LEN] = { - const Z: AtomicU32 = AtomicU32::new(0); - [Z; BREADCRUMB_RING_LEN] -}; - -// --------------------------------------------------------------------------- -// Hardware-state watchdog -// --------------------------------------------------------------------------- -// -// The slave `respond_to_write` future installs `_dma_guard` / -// `nak_guard` drop guards that, on `with_timeout` cancellation, clear -// SLVDMA and NAK the pending byte. By the time we observe `Err(_)` from -// `with_timeout` in `slave_service`, those guards have already run and -// the I2C peripheral state no longer reflects the wedge. -// -// Instead, this task runs independently and samples FC2 + DMA channel 4 -// state every WEDGE_WATCH_PERIOD into `HW_SNAPSHOT`. On a wedge_panic, -// `dump_breadcrumbs()` prints the most recent snapshot, which captures -// the in-wedge state ~50 ms before the timeout fired. -// -// Raw absolute addresses are used to avoid PAC borrow conflicts with the -// I2cSlave that owns the peripheral. The addresses are stable per the -// MIMXRT633S memory map: -// FC2 (I2C2) base = 0x4010_8000 -// STAT @ 0x4010_8804 -// INTENSET @ 0x4010_8808 -// SLVCTL @ 0x4010_8840 -// DMA0 base = 0x4010_4000 -// ACTIVE0 @ 0x4010_4030 (bit N = ch N active) -// BUSY0 @ 0x4010_4038 -// Channel 4 base @ 0x4010_4440 (CFG/CTLSTAT/XFERCFG at +0/+4/+8) -// XFERCFG ch4 @ 0x4010_4448 - -const HW_FC2_STAT: usize = 0x4010_8804; -const HW_FC2_INTENSET: usize = 0x4010_8808; -const HW_FC2_SLVCTL: usize = 0x4010_8840; -const HW_DMA_ACTIVE0: usize = 0x4010_4030; -const HW_DMA_BUSY0: usize = 0x4010_4038; -const HW_DMA_CH4_XFERCFG: usize = 0x4010_4448; - -const WEDGE_WATCH_PERIOD: Duration = Duration::from_millis(25); - -/// Snapshot layout — keep in sync with `dump_hw_snapshot()`. -/// -/// Generation counter brackets the writes for tear-detection. Writers -/// bump `gen_start`, write words, then `gen_end := gen_start`. Reader -/// retries until `gen_start == gen_end`. -#[repr(C)] -struct HwSnapshot { - gen_start: AtomicU32, - gen_end: AtomicU32, - /// Monotonic micros at the time of the sample. 0 = never sampled. - ts_us: AtomicU32, - fc2_stat: AtomicU32, - fc2_intenset: AtomicU32, - fc2_slvctl: AtomicU32, - dma_active0: AtomicU32, - dma_busy0: AtomicU32, - dma_ch4_xfercfg: AtomicU32, -} - -static HW_SNAPSHOT: HwSnapshot = HwSnapshot { - gen_start: AtomicU32::new(0), - gen_end: AtomicU32::new(0), - ts_us: AtomicU32::new(0), - fc2_stat: AtomicU32::new(0), - fc2_intenset: AtomicU32::new(0), - fc2_slvctl: AtomicU32::new(0), - dma_active0: AtomicU32::new(0), - dma_busy0: AtomicU32::new(0), - dma_ch4_xfercfg: AtomicU32::new(0), -}; - -/// Sample the hardware once and publish to `HW_SNAPSHOT`. Volatile -/// reads on raw addresses — safe because we never write through these -/// pointers, the addresses are guaranteed valid for the lifetime of the -/// program, and 32-bit aligned reads of MMIO registers are atomic on -/// Cortex-M33. -#[inline(always)] -fn sample_hw() { - // SAFETY: see function doc. - let stat = unsafe { core::ptr::read_volatile(HW_FC2_STAT as *const u32) }; - let intenset = unsafe { core::ptr::read_volatile(HW_FC2_INTENSET as *const u32) }; - let slvctl = unsafe { core::ptr::read_volatile(HW_FC2_SLVCTL as *const u32) }; - let active0 = unsafe { core::ptr::read_volatile(HW_DMA_ACTIVE0 as *const u32) }; - let busy0 = unsafe { core::ptr::read_volatile(HW_DMA_BUSY0 as *const u32) }; - let xfercfg = unsafe { core::ptr::read_volatile(HW_DMA_CH4_XFERCFG as *const u32) }; - - let ts = (Instant::now().as_micros() & 0xFFFF_FFFF) as u32; - let g = HW_SNAPSHOT.gen_start.fetch_add(1, Ordering::AcqRel).wrapping_add(1); - HW_SNAPSHOT.ts_us.store(ts, Ordering::Relaxed); - HW_SNAPSHOT.fc2_stat.store(stat, Ordering::Relaxed); - HW_SNAPSHOT.fc2_intenset.store(intenset, Ordering::Relaxed); - HW_SNAPSHOT.fc2_slvctl.store(slvctl, Ordering::Relaxed); - HW_SNAPSHOT.dma_active0.store(active0, Ordering::Relaxed); - HW_SNAPSHOT.dma_busy0.store(busy0, Ordering::Relaxed); - HW_SNAPSHOT.dma_ch4_xfercfg.store(xfercfg, Ordering::Relaxed); - HW_SNAPSHOT.gen_end.store(g, Ordering::Release); -} - -/// Dump the latest hardware snapshot. Tear-protected via the -/// gen_start/gen_end counters; falls back to a "torn" message if the -/// reader caught a snapshot mid-update (unlikely outside of races). -fn dump_hw_snapshot() { - for _ in 0..4 { - let g_end = HW_SNAPSHOT.gen_end.load(Ordering::Acquire); - let ts = HW_SNAPSHOT.ts_us.load(Ordering::Relaxed); - let stat = HW_SNAPSHOT.fc2_stat.load(Ordering::Relaxed); - let intenset = HW_SNAPSHOT.fc2_intenset.load(Ordering::Relaxed); - let slvctl = HW_SNAPSHOT.fc2_slvctl.load(Ordering::Relaxed); - let active0 = HW_SNAPSHOT.dma_active0.load(Ordering::Relaxed); - let busy0 = HW_SNAPSHOT.dma_busy0.load(Ordering::Relaxed); - let xfercfg = HW_SNAPSHOT.dma_ch4_xfercfg.load(Ordering::Relaxed); - let g_start = HW_SNAPSHOT.gen_start.load(Ordering::Acquire); - if g_start == g_end { - if ts == 0 { - error!("---- HW snapshot: never sampled ----"); - } else { - // Decode the bits we care about for STAT. - let stat_slvpending = (stat >> 8) & 1; - let stat_slvstate = (stat >> 9) & 3; - let stat_slvnotstr = (stat >> 11) & 1; - let stat_slvsel = (stat >> 14) & 1; - let stat_slvdesel = (stat >> 15) & 1; - // SLVCTL bit 3 = SLVDMA. - let slvctl_slvdma = (slvctl >> 3) & 1; - // INTENSET bit 8 = slvpendingen, bit 15 = slvdeselen. - let int_slvpend = (intenset >> 8) & 1; - let int_slvdes = (intenset >> 15) & 1; - // DMA ch4 active/busy. - let ch4_active = (active0 >> 4) & 1; - let ch4_busy = (busy0 >> 4) & 1; - // XFERCFG.XFERCOUNT = bits 16..26. - let xfer_count = (xfercfg >> 16) & 0x3FF; - let xfer_en = xfercfg & 1; - - error!( - "---- HW snapshot (ts_us={}, gen={}) ----", - ts, g_end - ); - error!( - " FC2.STAT=0x{:08X} slvpending={} slvstate={} slvnotstr={} slvsel={} slvdesel={}", - stat, stat_slvpending, stat_slvstate, stat_slvnotstr, stat_slvsel, stat_slvdesel - ); - error!( - " FC2.SLVCTL=0x{:08X} slvdma={}", - slvctl, slvctl_slvdma - ); - error!( - " FC2.INTENSET=0x{:08X} slvpendingen={} slvdeselen={}", - intenset, int_slvpend, int_slvdes - ); - error!( - " DMA.ACTIVE0=0x{:08X} ch4_active={}", - active0, ch4_active - ); - error!( - " DMA.BUSY0=0x{:08X} ch4_busy={}", - busy0, ch4_busy - ); - error!( - " DMA.CH4.XFERCFG=0x{:08X} xfercount={} xfer_en={}", - xfercfg, xfer_count, xfer_en - ); - error!("---- end HW snapshot ----"); - } - return; - } - // Torn read; retry. - } - error!("---- HW snapshot: torn after 4 attempts ----"); -} - -/// Watchdog task: sample HW every `WEDGE_WATCH_PERIOD` so the snapshot -/// is at most one period behind reality at any wedge_panic site. -#[embassy_executor::task] -async fn wedge_watch() { - loop { - sample_hw(); - Timer::after(WEDGE_WATCH_PERIOD).await; - } -} - -#[inline(always)] -fn breadcrumb(site: u8, iter: u32) { - let packed = ((site as u32) << 24) | (iter & 0x00FF_FFFF); - let idx = BREADCRUMB_INDEX.fetch_add(1, Ordering::Relaxed) & BREADCRUMB_RING_MASK; - BREADCRUMB_RING[idx].store(packed, Ordering::Relaxed); -} - -/// Dump the breadcrumb ring oldest-to-newest via `defmt::error!`. -/// Intended to be called immediately before `defmt::panic!` at any -/// wedge site so the panic-loop halt does not race the panic message -/// to RTT. -fn dump_breadcrumbs() { - let next = BREADCRUMB_INDEX.load(Ordering::Relaxed); - let count = next.min(BREADCRUMB_RING_LEN); - let start = next.wrapping_sub(count); - error!("---- breadcrumb dump ({} entries, newest last) ----", count); - for i in 0..count { - let slot = (start.wrapping_add(i)) & BREADCRUMB_RING_MASK; - let packed = BREADCRUMB_RING[slot].load(Ordering::Relaxed); - let site = (packed >> 24) as u8; - let iter = packed & 0x00FF_FFFF; - error!(" bc[{}]: site=0x{:02X} iter={}", i, site, iter); - } - error!("---- end breadcrumb dump ----"); -} - -/// Panic with full breadcrumb dump. Replaces `defmt::panic!` at every -/// wedge / error site so the RTT log carries the last 32 (site, iter) -/// transitions before the chip halts. -macro_rules! wedge_panic { - ($($arg:tt)*) => {{ - $crate::dump_hw_snapshot(); - $crate::dump_breadcrumbs(); - defmt::panic!($($arg)*); - }}; -} - // --------------------------------------------------------------------------- // Tunables // --------------------------------------------------------------------------- @@ -509,6 +127,12 @@ const REPORT_EVERY: u32 = 10_000; /// is a wedge worth investigating. const PER_TRANSFER_TIMEOUT: Duration = Duration::from_millis(250); +/// Outer timeout on `listen()`. Generous compared to the per-transfer +/// timeout because between transactions the bus can sit idle briefly +/// while the master service formats its next buffer, but a 2 s wait +/// with both ends running on the same chip means something is wrong. +const PER_LISTEN_TIMEOUT: Duration = Duration::from_millis(2000); + /// Maximum master write payload size — `k_max * S`. const MASTER_MAX_WRITE: usize = BOUNDARY_K_MAX * SLAVE_BUFLEN; /// Maximum master read payload size — `m_max`. @@ -562,122 +186,97 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { *e = (j as u8).wrapping_add(key); } - // listen() WITHOUT timeout for the mole bring-up: Mole is the - // external controller and may sit idle for long stretches - // between probes. The intra-transaction respond_to_* awaits - // below still carry PER_TRANSFER_TIMEOUT so a mid-transaction - // wedge is still caught. - breadcrumb(0x10, slv_iter); - let req = match TargetI2c::::listen(&mut slave).await { - Err(e) => wedge_panic!( + // listen() guarded by PER_LISTEN_TIMEOUT. The chip is both + // master and slave; under healthy operation the master is + // hammering the slave at thousands of transactions/sec, so a + // listen() that does not return within the timeout window + // indicates a real wedge worth investigating. + let req = match with_timeout( + PER_LISTEN_TIMEOUT, + TargetI2c::::listen(&mut slave), + ) + .await + { + Err(_) => defmt::panic!("WEDGE: slave.listen() at slv_iter={}", slv_iter), + Ok(Err(e)) => defmt::panic!( "slave.listen() err at slv_iter={}: {:?}", slv_iter, defmt::Debug2Format(&e) ), - Ok(r) => r, + Ok(Ok(r)) => r, }; - breadcrumb(0x11, slv_iter); let was_expecting_restart = expect_repeated_start; expect_repeated_start = false; match req { - Request::Stop(addr) => { - breadcrumb(0x12, slv_iter); + Request::Stop(_) => { probe_count += 1; - info!("Stop @ 0x{:02X} (probe)", addr); if was_expecting_restart { - warn!( - "RACE WATCH: prior respond_to_* reported Restarted but listen() returned Stop(0x{:02X}); expected RepeatedStart", - addr - ); + warn!("RACE WATCH: prior respond_to_* reported Stop but expected RepeatedStart"); } } - Request::RepeatedStart(prev_addr) => { - breadcrumb(0x13, slv_iter); + Request::RepeatedStart(_) => { repeated_start_count += 1; - info!("RepeatedStart from prev @ 0x{:02X}", prev_addr); if !was_expecting_restart { - warn!( - "RACE WATCH: RepeatedStart(0x{:02X}) surfaced without a prior Restarted(_)", - prev_addr - ); + warn!("RACE WATCH: RepeatedStart surfaced without a prior Restarted(_)"); } } - Request::Read(addr) => { - breadcrumb(0x14, slv_iter); - info!("Read @ 0x{:02X}", addr); - if was_expecting_restart { - info!("(consumed expected RepeatedStart edge before Read)"); - } + Request::Read(_) => { loop { - breadcrumb(0x15, slv_iter); let r = match with_timeout( PER_TRANSFER_TIMEOUT, TargetI2c::::respond_to_read(&mut slave, &t_buf), ) .await { - Err(_) => wedge_panic!("WEDGE: respond_to_read at slv_iter={}", slv_iter), - Ok(Err(e)) => wedge_panic!( + Err(_) => defmt::panic!("WEDGE: respond_to_read at slv_iter={}", slv_iter), + Ok(Err(e)) => defmt::panic!( "respond_to_read err at slv_iter={}: {:?}", slv_iter, defmt::Debug2Format(&e) ), Ok(Ok(r)) => r, }; - breadcrumb(0x16, slv_iter); match r { - ReadStatus::Complete(n) => { + ReadStatus::Complete(_) => { read_complete += 1; - info!("Read complete with {} bytes", n); break; } - ReadStatus::EarlyStop(n) => { + ReadStatus::EarlyStop(_) => { read_early_stop += 1; - info!("Read terminated by controller after {} bytes", n); break; } - ReadStatus::NeedMore(n) => { + ReadStatus::NeedMore(_) => { read_need_more += 1; - info!("Read NeedMore: sent {} bytes so far, more requested", n); // Loop and supply `t_buf` again. } _ => { // ReadStatus is #[non_exhaustive]; ignore. - info!("Read: unknown status variant"); break; } } } } - Request::Write(addr) => { - breadcrumb(0x17, slv_iter); - info!("Write @ 0x{:02X}", addr); - if was_expecting_restart { - info!("(consumed expected RepeatedStart edge before Write)"); - } + Request::Write(_) => { loop { - breadcrumb(0x18, slv_iter); let r = match with_timeout( PER_TRANSFER_TIMEOUT, TargetI2c::::respond_to_write(&mut slave, &mut r_buf), ) .await { - Err(_) => wedge_panic!("WEDGE: respond_to_write at slv_iter={}", slv_iter), - Ok(Err(e)) => wedge_panic!( + Err(_) => defmt::panic!("WEDGE: respond_to_write at slv_iter={}", slv_iter), + Ok(Err(e)) => defmt::panic!( "respond_to_write err at slv_iter={}: {:?}", slv_iter, defmt::Debug2Format(&e) ), Ok(Ok(r)) => r, }; - breadcrumb(0x19, slv_iter); match r { - WriteStatus::BufferFull(n) => { + WriteStatus::BufferFull(_) => { write_continued += 1; - info!("Write BufferFull after {} bytes — supplying more buffer space", n); // Loop and continue draining. } WriteStatus::Stopped(n) => { @@ -686,19 +285,13 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { } else { write_stopped_nonzero += 1; } - info!("Write stopped after {} bytes", n); break; } WriteStatus::Restarted(n) => { if n == 0 { write_restarted_zero += 1; - info!("Write restarted after 0 bytes — next listen will surface RepeatedStart"); - warn!( - "RACE WATCH: WriteStatus::Restarted(0) — buffer-boundary surfacing artifact under test" - ); } else { write_restarted_nonzero += 1; - info!("Write restarted after {} bytes", n); } // Do NOT call recover() — the Sr+ADDR is queued // on the wire and the next listen() must surface @@ -708,7 +301,6 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { } _ => { // WriteStatus is #[non_exhaustive]; ignore. - info!("Write: unknown status variant"); break; } } @@ -741,17 +333,13 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { } // --------------------------------------------------------------------------- -// Master service (DISABLED for mole bring-up) +// Master service // -// The master leg is replaced by the external Mole running -// `imxrt-boundary-repro.moleasm`. Leaving the task body in source as -// reference only; main no longer spawns it and FC4 is no longer -// initialised. The file will emit dead-code warnings for the master- -// only imports (I2cMaster, DutyCycle, embedded_hal_async::i2c::I2c) -// and the unused MASTER_MAX_* constants; that is expected. +// FC4 master driving FC2 slave via PIO0_29/PIO0_30 ↔ PIO0_18/PIO0_17 +// loopback wires. No external controller — the rt685 talks to itself, +// so the soak is self-contained on a single board. // --------------------------------------------------------------------------- -/* #[embassy_executor::task] async fn master_service(mut master: I2cMaster<'static, Async>) { let mut w_buf = [0u8; MASTER_MAX_WRITE]; @@ -775,22 +363,20 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { for e in r_buf[..read_len].iter_mut() { *e = 0; } - breadcrumb(0x20, iter); - breadcrumb(0x21, iter); match with_timeout( PER_TRANSFER_TIMEOUT, master.write_read(ADDR, &w_buf[..write_len], &mut r_buf[..read_len]), ) .await { - Err(_) => wedge_panic!( + Err(_) => defmt::panic!( "WEDGE: master.write_read timed out at iter={}, k={}, m={}", iter, k, read_len ), - Ok(Err(e)) => wedge_panic!( + Ok(Err(e)) => defmt::panic!( "master.write_read err at iter={}, k={}, m={}: {:?}", iter, k, @@ -798,18 +384,16 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { defmt::Debug2Format(&e) ), Ok(Ok(())) => { - breadcrumb(0x22, iter); // Verify the read buffer is a monotonic increment starting // from `r_buf[0]`. Slave fills `t_buf[j] = (j + key) mod // 256` so the difference `r_buf[j] - r_buf[0]` must equal // `j` (mod 256) regardless of which slave iteration // produced the data. let base = r_buf[0]; - breadcrumb(0x23, iter); for j in 1..read_len { let expect = base.wrapping_add(j as u8); if r_buf[j] != expect { - wedge_panic!( + defmt::panic!( "READ MISMATCH at iter={}, k={}, m={}, j={}: got 0x{:02X}, expected 0x{:02X} (base=0x{:02X})", iter, k, @@ -821,7 +405,6 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { ); } } - breadcrumb(0x24, iter); } } @@ -831,7 +414,6 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { iter = iter.wrapping_add(1); } } -*/ // --------------------------------------------------------------------------- // Entry point @@ -853,20 +435,17 @@ async fn main(spawner: Spawner) { let slave = I2cSlave::new_async(p.FLEXCOMM2, p.PIO0_18, p.PIO0_17, Irqs, SLAVE_ADDR.unwrap(), p.DMA0_CH4).unwrap(); - // FC4 master is replaced by an external Mole running - // `imxrt-boundary-repro.moleasm`. The cfg / I2cMaster::new_async / - // spawn lines stay in source as commented-out reference; their pins - // (PIO0_29 / PIO0_30) and DMA channel (DMA0_CH9) are now free for - // the loopback wires that go to the Mole instead of FC4. - // - // let cfg = i2c::master::Config { - // speed: i2c::master::Speed::Fast, - // duty_cycle: DutyCycle::new(50).unwrap(), - // ..Default::default() - // }; - // let master = I2cMaster::new_async(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Irqs, cfg, p.DMA0_CH9).unwrap(); + // FC4 master on PIO0_29 (SCL) / PIO0_30 (SDA) driving FC2 slave on + // PIO0_18 (SCL) / PIO0_17 (SDA) via direct loopback wires on the + // rt685s-evk. No external controller — the chip drives both sides + // of the bus. + let cfg = i2c::master::Config { + speed: i2c::master::Speed::Fast, + duty_cycle: DutyCycle::new(50).unwrap(), + ..Default::default() + }; + let master = I2cMaster::new_async(p.FLEXCOMM4, p.PIO0_29, p.PIO0_30, Irqs, cfg, p.DMA0_CH9).unwrap(); spawner.spawn(slave_service(slave).unwrap()); - spawner.spawn(wedge_watch().unwrap()); - // spawner.spawn(master_service(master).unwrap()); + spawner.spawn(master_service(master).unwrap()); } From 73b4c080d7c6ee0949a701433ef9001a4bb05716 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 16 Jun 2026 06:09:13 -0700 Subject: [PATCH 25/27] cargo +nightly fmt --- .../rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs index 9aea886a..fa251651 100644 --- a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs +++ b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs @@ -191,12 +191,7 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { // hammering the slave at thousands of transactions/sec, so a // listen() that does not return within the timeout window // indicates a real wedge worth investigating. - let req = match with_timeout( - PER_LISTEN_TIMEOUT, - TargetI2c::::listen(&mut slave), - ) - .await - { + let req = match with_timeout(PER_LISTEN_TIMEOUT, TargetI2c::::listen(&mut slave)).await { Err(_) => defmt::panic!("WEDGE: slave.listen() at slv_iter={}", slv_iter), Ok(Err(e)) => defmt::panic!( "slave.listen() err at slv_iter={}: {:?}", From c12904c8ea1efdda8e2198dbecddf9ef971940d5 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 16 Jun 2026 06:23:38 -0700 Subject: [PATCH 26/27] deny.toml: add exception for proc-macro-error2 (RUSTSEC-2026-0173) --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index 7ffbbf10..59ab6ec2 100644 --- a/deny.toml +++ b/deny.toml @@ -75,6 +75,7 @@ ignore = [ #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, { id = "RUSTSEC-2024-0370", reason = "proc-macro-error is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, + { id = "RUSTSEC-2026-0173", reason = "proc-macro-error2 is unmaintained (pulled in via defmt-macros), no safe upgrade available, need upstream dependencies to migrate away from it." }, { id = "RUSTSEC-2026-0110", reason = "bare-metal is unmaintained, no safe upgrade available, need upstream dependencies to migrate away from it." }, { id = "RUSTSEC-2024-0436", reason = "there are no suitable replacements for paste right now; paste has been archived as read-only. It only affects compile time concatenation in macros. We will allow it for now" }, { id = "RUSTSEC-2023-0089", reason = "this is a deprecation warning for a dependency of a dependency. https://github.com/jamesmunns/postcard/issues/223 tracks fixing the dependency; until that's resolved, we can accept the deprecated code as it has no known vulnerabilities."} From 743b11501f06ceaf7b528d4531048cd5b73050d9 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Tue, 16 Jun 2026 06:23:38 -0700 Subject: [PATCH 27/27] examples: fix clippy lints in i2c boundary soak --- .../rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs index fa251651..f4f3bb85 100644 --- a/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs +++ b/examples/rt685s-evk/src/bin/i2c-write-read-boundary-soak.rs @@ -308,7 +308,7 @@ async fn slave_service(mut slave: I2cSlave<'static, Async>) { slv_iter = slv_iter.wrapping_add(1); - if slv_iter % REPORT_EVERY == 0 { + if slv_iter.is_multiple_of(REPORT_EVERY) { info!( "[slave] iter={} cont={} restZ={} restN={} stopZ={} stopN={} readC={} readNM={} readES={} rstart={} probe={}", slv_iter, @@ -385,16 +385,16 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { // `j` (mod 256) regardless of which slave iteration // produced the data. let base = r_buf[0]; - for j in 1..read_len { + for (j, &got) in r_buf.iter().enumerate().take(read_len).skip(1) { let expect = base.wrapping_add(j as u8); - if r_buf[j] != expect { + if got != expect { defmt::panic!( "READ MISMATCH at iter={}, k={}, m={}, j={}: got 0x{:02X}, expected 0x{:02X} (base=0x{:02X})", iter, k, read_len, j, - r_buf[j], + got, expect, base ); @@ -403,7 +403,7 @@ async fn master_service(mut master: I2cMaster<'static, Async>) { } } - if iter % REPORT_EVERY == 0 { + if iter.is_multiple_of(REPORT_EVERY) { info!("[master] iter={} k={} m={} write_len={}", iter, k, read_len, write_len); } iter = iter.wrapping_add(1);