diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 34955a02..8c6b52f6 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -2588,8 +2588,8 @@ "bzlTransitiveDigest": "NWG/zr7TmqNjKiFdm1yVDA5cIKBVybQcHs2v6Jj3knc=", "usagesDigest": "fXPTszFzBe4VvGFoJKnLB/E/6WtlKiQ600VfVyI7MFk=", "recordedFileInputs": { - "@@//third_party/crates_io/Cargo.lock": "1e709fd194acdfdb8bd71d208a4f0be6a59c0acfe8042caa645a1ad5c2f5ce7e", - "@@//third_party/crates_io/Cargo.toml": "9a692cfbf8fab58e3d003e53550be0247aaf602cee26c7cea06caffb05280a48", + "@@//third_party/crates_io/Cargo.lock": "6fcf085c5320b3a12b047e82aa1c8d91516f833cf2cfc7733c39bc6f7eed39a1", + "@@//third_party/crates_io/Cargo.toml": "6692cc7e0d688a16fba5d7344f95204ba818953b07af82fc77853c7c2136528a", "@@caliptra_deps+//crates_io/embedded/Cargo.lock": "d6c0101f48da22f2bc2d339f358de79bad3dd03218c6db29a14099b9f7757691", "@@caliptra_deps+//crates_io/embedded/Cargo.toml": "8f9f4ed2721db13476b12fdac045dac2142b38f189a8abb5f4c446dc0c6ac3dd", "@@caliptra_deps+//crates_io/host/Cargo.lock": "ae555b01424917ec61d892c15d7a66af88c2f10be4e0e7b0bc2357b702883b89", @@ -2615,9 +2615,9 @@ "repoRuleId": "@@rules_rust+//crate_universe:extensions.bzl%_generate_repo", "attributes": { "contents": { - "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"aes-0.8.4\",\n actual = \"@rust_crates__aes-0.8.4//:aes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aes\",\n actual = \"@rust_crates__aes-0.8.4//:aes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aes-gcm-0.10.3\",\n actual = \"@rust_crates__aes-gcm-0.10.3//:aes_gcm\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aes-gcm\",\n actual = \"@rust_crates__aes-gcm-0.10.3//:aes_gcm\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aligned-0.4.3\",\n actual = \"@rust_crates__aligned-0.4.3//:aligned\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aligned\",\n actual = \"@rust_crates__aligned-0.4.3//:aligned\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anyhow-1.0.102\",\n actual = \"@rust_crates__anyhow-1.0.102//:anyhow\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anyhow\",\n actual = \"@rust_crates__anyhow-1.0.102//:anyhow\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-0.14.0\",\n actual = \"@rust_crates__bitfield-0.14.0//:bitfield\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield\",\n actual = \"@rust_crates__bitfield-0.14.0//:bitfield\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-struct-0.11.0\",\n actual = \"@rust_crates__bitfield-struct-0.11.0//:bitfield_struct\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-struct\",\n actual = \"@rust_crates__bitfield-struct-0.11.0//:bitfield_struct\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitflags-2.11.0\",\n actual = \"@rust_crates__bitflags-2.11.0//:bitflags\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitflags\",\n actual = \"@rust_crates__bitflags-2.11.0//:bitflags\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"byteorder-1.5.0\",\n actual = \"@rust_crates__byteorder-1.5.0//:byteorder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"byteorder\",\n actual = \"@rust_crates__byteorder-1.5.0//:byteorder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cfg-if-1.0.4\",\n actual = \"@rust_crates__cfg-if-1.0.4//:cfg_if\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cfg-if\",\n actual = \"@rust_crates__cfg-if-1.0.4//:cfg_if\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cipher-0.4.4\",\n actual = \"@rust_crates__cipher-0.4.4//:cipher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cipher\",\n actual = \"@rust_crates__cipher-0.4.4//:cipher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-4.6.0\",\n actual = \"@rust_crates__clap-4.6.0//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap\",\n actual = \"@rust_crates__clap-4.6.0//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"compiler_builtins-0.1.160\",\n actual = \"@rust_crates__compiler_builtins-0.1.160//:compiler_builtins\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"compiler_builtins\",\n actual = \"@rust_crates__compiler_builtins-0.1.160//:compiler_builtins\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cortex-m-0.7.7\",\n actual = \"@rust_crates__cortex-m-0.7.7//:cortex_m\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cortex-m\",\n actual = \"@rust_crates__cortex-m-0.7.7//:cortex_m\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ctr-0.9.2\",\n actual = \"@rust_crates__ctr-0.9.2//:ctr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ctr\",\n actual = \"@rust_crates__ctr-0.9.2//:ctr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ecdsa-0.16.9\",\n actual = \"@rust_crates__ecdsa-0.16.9//:ecdsa\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ecdsa\",\n actual = \"@rust_crates__ecdsa-0.16.9//:ecdsa\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-1.0.0\",\n actual = \"@rust_crates__embedded-hal-1.0.0//:embedded_hal\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal\",\n actual = \"@rust_crates__embedded-hal-1.0.0//:embedded_hal\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-async-1.0.0\",\n actual = \"@rust_crates__embedded-hal-async-1.0.0//:embedded_hal_async\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-async\",\n actual = \"@rust_crates__embedded-hal-async-1.0.0//:embedded_hal_async\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-nb-1.0.0\",\n actual = \"@rust_crates__embedded-hal-nb-1.0.0//:embedded_hal_nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-nb\",\n actual = \"@rust_crates__embedded-hal-nb-1.0.0//:embedded_hal_nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-io-0.6.1\",\n actual = \"@rust_crates__embedded-io-0.6.1//:embedded_io\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-io\",\n actual = \"@rust_crates__embedded-io-0.6.1//:embedded_io\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"futures-0.3.32\",\n actual = \"@rust_crates__futures-0.3.32//:futures\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"futures\",\n actual = \"@rust_crates__futures-0.3.32//:futures\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"heapless-0.9.2\",\n actual = \"@rust_crates__heapless-0.9.2//:heapless\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"heapless\",\n actual = \"@rust_crates__heapless-0.9.2//:heapless\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex-0.4.3\",\n actual = \"@rust_crates__hex-0.4.3//:hex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex\",\n actual = \"@rust_crates__hex-0.4.3//:hex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hmac-0.12.1\",\n actual = \"@rust_crates__hmac-0.12.1//:hmac\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hmac\",\n actual = \"@rust_crates__hmac-0.12.1//:hmac\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"k256-0.13.4\",\n actual = \"@rust_crates__k256-0.13.4//:k256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"k256\",\n actual = \"@rust_crates__k256-0.13.4//:k256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log-0.4.29\",\n actual = \"@rust_crates__log-0.4.29//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log\",\n actual = \"@rust_crates__log-0.4.29//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"memoffset-0.9.1\",\n actual = \"@rust_crates__memoffset-0.9.1//:memoffset\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"memoffset\",\n actual = \"@rust_crates__memoffset-0.9.1//:memoffset\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja-2.19.0\",\n actual = \"@rust_crates__minijinja-2.19.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja\",\n actual = \"@rust_crates__minijinja-2.19.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nb-1.1.0\",\n actual = \"@rust_crates__nb-1.1.0//:nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nb\",\n actual = \"@rust_crates__nb-1.1.0//:nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nom-7.1.3\",\n actual = \"@rust_crates__nom-7.1.3//:nom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nom\",\n actual = \"@rust_crates__nom-7.1.3//:nom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"object-0.37.3\",\n actual = \"@rust_crates__object-0.37.3//:object\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"object\",\n actual = \"@rust_crates__object-0.37.3//:object\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p256-0.13.2\",\n actual = \"@rust_crates__p256-0.13.2//:p256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p256\",\n actual = \"@rust_crates__p256-0.13.2//:p256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p384-0.13.1\",\n actual = \"@rust_crates__p384-0.13.1//:p384\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p384\",\n actual = \"@rust_crates__p384-0.13.1//:p384\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"paste-1.0.15\",\n actual = \"@rust_crates__paste-1.0.15//:paste\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"paste\",\n actual = \"@rust_crates__paste-1.0.15//:paste\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"proc-macro2-1.0.106\",\n actual = \"@rust_crates__proc-macro2-1.0.106//:proc_macro2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"proc-macro2\",\n actual = \"@rust_crates__proc-macro2-1.0.106//:proc_macro2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"prost-0.13.5\",\n actual = \"@rust_crates__prost-0.13.5//:prost\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"prost\",\n actual = \"@rust_crates__prost-0.13.5//:prost\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote-1.0.45\",\n actual = \"@rust_crates__quote-1.0.45//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote\",\n actual = \"@rust_crates__quote-1.0.45//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand_core-0.9.5\",\n actual = \"@rust_crates__rand_core-0.9.5//:rand_core\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand_core\",\n actual = \"@rust_crates__rand_core-0.9.5//:rand_core\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-0.12.1\",\n actual = \"@rust_crates__riscv-0.12.1//:riscv\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv\",\n actual = \"@rust_crates__riscv-0.12.1//:riscv\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-rt-0.12.2\",\n actual = \"@rust_crates__riscv-rt-0.12.2//:riscv_rt\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-rt\",\n actual = \"@rust_crates__riscv-rt-0.12.2//:riscv_rt\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-semihosting-0.1.3\",\n actual = \"@rust_crates__riscv-semihosting-0.1.3//:riscv_semihosting\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-semihosting\",\n actual = \"@rust_crates__riscv-semihosting-0.1.3//:riscv_semihosting\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rustc-demangle-0.1.27\",\n actual = \"@rust_crates__rustc-demangle-0.1.27//:rustc_demangle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rustc-demangle\",\n actual = \"@rust_crates__rustc-demangle-0.1.27//:rustc_demangle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sec1-0.7.3\",\n actual = \"@rust_crates__sec1-0.7.3//:sec1\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sec1\",\n actual = \"@rust_crates__sec1-0.7.3//:sec1\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-1.0.228\",\n actual = \"@rust_crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde\",\n actual = \"@rust_crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_derive-1.0.228\",\n actual = \"@rust_crates__serde_derive-1.0.228//:serde_derive\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_derive\",\n actual = \"@rust_crates__serde_derive-1.0.228//:serde_derive\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json5-0.2.1\",\n actual = \"@rust_crates__serde_json5-0.2.1//:serde_json5\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json5\",\n actual = \"@rust_crates__serde_json5-0.2.1//:serde_json5\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha2-0.10.9\",\n actual = \"@rust_crates__sha2-0.10.9//:sha2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha2\",\n actual = \"@rust_crates__sha2-0.10.9//:sha2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha3-0.10.8\",\n actual = \"@rust_crates__sha3-0.10.8//:sha3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha3\",\n actual = \"@rust_crates__sha3-0.10.8//:sha3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smlang-0.8.0\",\n actual = \"@rust_crates__smlang-0.8.0//:smlang\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smlang\",\n actual = \"@rust_crates__smlang-0.8.0//:smlang\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"subtle-2.6.1\",\n actual = \"@rust_crates__subtle-2.6.1//:subtle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"subtle\",\n actual = \"@rust_crates__subtle-2.6.1//:subtle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn-1.0.109\",\n actual = \"@rust_crates__syn-1.0.109//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn1-1.0.109\",\n actual = \"@rust_crates__syn-1.0.109//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn1\",\n actual = \"@rust_crates__syn-1.0.109//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn-2.0.117\",\n actual = \"@rust_crates__syn-2.0.117//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn\",\n actual = \"@rust_crates__syn-2.0.117//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror-2.0.18\",\n actual = \"@rust_crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror\",\n actual = \"@rust_crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tock-registers-0.9.0\",\n actual = \"@rust_crates__tock-registers-0.9.0//:tock_registers\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tock-registers\",\n actual = \"@rust_crates__tock-registers-0.9.0//:tock_registers\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-1.51.1\",\n actual = \"@rust_crates__tokio-1.51.1//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio\",\n actual = \"@rust_crates__tokio-1.51.1//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-util-0.7.18\",\n actual = \"@rust_crates__tokio-util-0.7.18//:tokio_util\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-util\",\n actual = \"@rust_crates__tokio-util-0.7.18//:tokio_util\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zerocopy-0.8.48\",\n actual = \"@rust_crates__zerocopy-0.8.48//:zerocopy\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zerocopy\",\n actual = \"@rust_crates__zerocopy-0.8.48//:zerocopy\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zeroize-1.8.2\",\n actual = \"@rust_crates__zeroize-1.8.2//:zeroize\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zeroize\",\n actual = \"@rust_crates__zeroize-1.8.2//:zeroize\",\n tags = [\"manual\"],\n)\n", + "BUILD.bazel": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\npackage(default_visibility = [\"//visibility:public\"])\n\nexports_files(\n [\n \"cargo-bazel.json\",\n \"crates.bzl\",\n \"defs.bzl\",\n ] + glob(\n allow_empty = True,\n include = [\"*.bazel\"],\n ),\n)\n\nfilegroup(\n name = \"srcs\",\n srcs = glob(\n allow_empty = True,\n include = [\n \"*.bazel\",\n \"*.bzl\",\n ],\n ),\n)\n\n# Workspace Member Dependencies\nalias(\n name = \"aes-0.8.4\",\n actual = \"@rust_crates__aes-0.8.4//:aes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aes\",\n actual = \"@rust_crates__aes-0.8.4//:aes\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aes-gcm-0.10.3\",\n actual = \"@rust_crates__aes-gcm-0.10.3//:aes_gcm\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aes-gcm\",\n actual = \"@rust_crates__aes-gcm-0.10.3//:aes_gcm\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aligned-0.4.3\",\n actual = \"@rust_crates__aligned-0.4.3//:aligned\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"aligned\",\n actual = \"@rust_crates__aligned-0.4.3//:aligned\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anyhow-1.0.102\",\n actual = \"@rust_crates__anyhow-1.0.102//:anyhow\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"anyhow\",\n actual = \"@rust_crates__anyhow-1.0.102//:anyhow\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-0.14.0\",\n actual = \"@rust_crates__bitfield-0.14.0//:bitfield\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield\",\n actual = \"@rust_crates__bitfield-0.14.0//:bitfield\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-struct-0.11.0\",\n actual = \"@rust_crates__bitfield-struct-0.11.0//:bitfield_struct\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitfield-struct\",\n actual = \"@rust_crates__bitfield-struct-0.11.0//:bitfield_struct\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitflags-2.11.0\",\n actual = \"@rust_crates__bitflags-2.11.0//:bitflags\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"bitflags\",\n actual = \"@rust_crates__bitflags-2.11.0//:bitflags\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"byteorder-1.5.0\",\n actual = \"@rust_crates__byteorder-1.5.0//:byteorder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"byteorder\",\n actual = \"@rust_crates__byteorder-1.5.0//:byteorder\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cfg-if-1.0.4\",\n actual = \"@rust_crates__cfg-if-1.0.4//:cfg_if\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cfg-if\",\n actual = \"@rust_crates__cfg-if-1.0.4//:cfg_if\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cipher-0.4.4\",\n actual = \"@rust_crates__cipher-0.4.4//:cipher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cipher\",\n actual = \"@rust_crates__cipher-0.4.4//:cipher\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap-4.6.0\",\n actual = \"@rust_crates__clap-4.6.0//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"clap\",\n actual = \"@rust_crates__clap-4.6.0//:clap\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"compiler_builtins-0.1.160\",\n actual = \"@rust_crates__compiler_builtins-0.1.160//:compiler_builtins\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"compiler_builtins\",\n actual = \"@rust_crates__compiler_builtins-0.1.160//:compiler_builtins\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cortex-m-0.7.7\",\n actual = \"@rust_crates__cortex-m-0.7.7//:cortex_m\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"cortex-m\",\n actual = \"@rust_crates__cortex-m-0.7.7//:cortex_m\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ctr-0.9.2\",\n actual = \"@rust_crates__ctr-0.9.2//:ctr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ctr\",\n actual = \"@rust_crates__ctr-0.9.2//:ctr\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ecdsa-0.16.9\",\n actual = \"@rust_crates__ecdsa-0.16.9//:ecdsa\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ecdsa\",\n actual = \"@rust_crates__ecdsa-0.16.9//:ecdsa\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-1.0.0\",\n actual = \"@rust_crates__embedded-hal-1.0.0//:embedded_hal\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal\",\n actual = \"@rust_crates__embedded-hal-1.0.0//:embedded_hal\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-async-1.0.0\",\n actual = \"@rust_crates__embedded-hal-async-1.0.0//:embedded_hal_async\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-async\",\n actual = \"@rust_crates__embedded-hal-async-1.0.0//:embedded_hal_async\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-nb-1.0.0\",\n actual = \"@rust_crates__embedded-hal-nb-1.0.0//:embedded_hal_nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-hal-nb\",\n actual = \"@rust_crates__embedded-hal-nb-1.0.0//:embedded_hal_nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-io-0.6.1\",\n actual = \"@rust_crates__embedded-io-0.6.1//:embedded_io\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"embedded-io\",\n actual = \"@rust_crates__embedded-io-0.6.1//:embedded_io\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"futures-0.3.32\",\n actual = \"@rust_crates__futures-0.3.32//:futures\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"futures\",\n actual = \"@rust_crates__futures-0.3.32//:futures\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"heapless-0.9.2\",\n actual = \"@rust_crates__heapless-0.9.2//:heapless\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"heapless\",\n actual = \"@rust_crates__heapless-0.9.2//:heapless\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex-0.4.3\",\n actual = \"@rust_crates__hex-0.4.3//:hex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hex\",\n actual = \"@rust_crates__hex-0.4.3//:hex\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hmac-0.12.1\",\n actual = \"@rust_crates__hmac-0.12.1//:hmac\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"hmac\",\n actual = \"@rust_crates__hmac-0.12.1//:hmac\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"k256-0.13.4\",\n actual = \"@rust_crates__k256-0.13.4//:k256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"k256\",\n actual = \"@rust_crates__k256-0.13.4//:k256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log-0.4.29\",\n actual = \"@rust_crates__log-0.4.29//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"log\",\n actual = \"@rust_crates__log-0.4.29//:log\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"memoffset-0.9.1\",\n actual = \"@rust_crates__memoffset-0.9.1//:memoffset\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"memoffset\",\n actual = \"@rust_crates__memoffset-0.9.1//:memoffset\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja-2.19.0\",\n actual = \"@rust_crates__minijinja-2.19.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"minijinja\",\n actual = \"@rust_crates__minijinja-2.19.0//:minijinja\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nb-1.1.0\",\n actual = \"@rust_crates__nb-1.1.0//:nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nb\",\n actual = \"@rust_crates__nb-1.1.0//:nb\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nom-7.1.3\",\n actual = \"@rust_crates__nom-7.1.3//:nom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"nom\",\n actual = \"@rust_crates__nom-7.1.3//:nom\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"object-0.37.3\",\n actual = \"@rust_crates__object-0.37.3//:object\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"object\",\n actual = \"@rust_crates__object-0.37.3//:object\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p256-0.13.2\",\n actual = \"@rust_crates__p256-0.13.2//:p256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p256\",\n actual = \"@rust_crates__p256-0.13.2//:p256\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p384-0.13.1\",\n actual = \"@rust_crates__p384-0.13.1//:p384\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"p384\",\n actual = \"@rust_crates__p384-0.13.1//:p384\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"paste-1.0.15\",\n actual = \"@rust_crates__paste-1.0.15//:paste\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"paste\",\n actual = \"@rust_crates__paste-1.0.15//:paste\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"proc-macro2-1.0.106\",\n actual = \"@rust_crates__proc-macro2-1.0.106//:proc_macro2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"proc-macro2\",\n actual = \"@rust_crates__proc-macro2-1.0.106//:proc_macro2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"prost-0.13.5\",\n actual = \"@rust_crates__prost-0.13.5//:prost\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"prost\",\n actual = \"@rust_crates__prost-0.13.5//:prost\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote-1.0.45\",\n actual = \"@rust_crates__quote-1.0.45//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"quote\",\n actual = \"@rust_crates__quote-1.0.45//:quote\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand_core-0.9.5\",\n actual = \"@rust_crates__rand_core-0.9.5//:rand_core\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rand_core\",\n actual = \"@rust_crates__rand_core-0.9.5//:rand_core\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-0.12.1\",\n actual = \"@rust_crates__riscv-0.12.1//:riscv\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv\",\n actual = \"@rust_crates__riscv-0.12.1//:riscv\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-rt-0.12.2\",\n actual = \"@rust_crates__riscv-rt-0.12.2//:riscv_rt\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-rt\",\n actual = \"@rust_crates__riscv-rt-0.12.2//:riscv_rt\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-semihosting-0.1.3\",\n actual = \"@rust_crates__riscv-semihosting-0.1.3//:riscv_semihosting\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"riscv-semihosting\",\n actual = \"@rust_crates__riscv-semihosting-0.1.3//:riscv_semihosting\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rustc-demangle-0.1.27\",\n actual = \"@rust_crates__rustc-demangle-0.1.27//:rustc_demangle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"rustc-demangle\",\n actual = \"@rust_crates__rustc-demangle-0.1.27//:rustc_demangle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sec1-0.7.3\",\n actual = \"@rust_crates__sec1-0.7.3//:sec1\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sec1\",\n actual = \"@rust_crates__sec1-0.7.3//:sec1\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde-1.0.228\",\n actual = \"@rust_crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde\",\n actual = \"@rust_crates__serde-1.0.228//:serde\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_derive-1.0.228\",\n actual = \"@rust_crates__serde_derive-1.0.228//:serde_derive\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_derive\",\n actual = \"@rust_crates__serde_derive-1.0.228//:serde_derive\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json5-0.2.1\",\n actual = \"@rust_crates__serde_json5-0.2.1//:serde_json5\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"serde_json5\",\n actual = \"@rust_crates__serde_json5-0.2.1//:serde_json5\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha2-0.10.9\",\n actual = \"@rust_crates__sha2-0.10.9//:sha2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha2\",\n actual = \"@rust_crates__sha2-0.10.9//:sha2\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha3-0.10.8\",\n actual = \"@rust_crates__sha3-0.10.8//:sha3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"sha3\",\n actual = \"@rust_crates__sha3-0.10.8//:sha3\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smlang-0.8.0\",\n actual = \"@rust_crates__smlang-0.8.0//:smlang\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"smlang\",\n actual = \"@rust_crates__smlang-0.8.0//:smlang\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"subtle-2.6.1\",\n actual = \"@rust_crates__subtle-2.6.1//:subtle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"subtle\",\n actual = \"@rust_crates__subtle-2.6.1//:subtle\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn-1.0.109\",\n actual = \"@rust_crates__syn-1.0.109//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn1-1.0.109\",\n actual = \"@rust_crates__syn-1.0.109//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn1\",\n actual = \"@rust_crates__syn-1.0.109//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn-2.0.117\",\n actual = \"@rust_crates__syn-2.0.117//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"syn\",\n actual = \"@rust_crates__syn-2.0.117//:syn\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror-2.0.18\",\n actual = \"@rust_crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"thiserror\",\n actual = \"@rust_crates__thiserror-2.0.18//:thiserror\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tock-registers-0.9.0\",\n actual = \"@rust_crates__tock-registers-0.9.0//:tock_registers\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tock-registers\",\n actual = \"@rust_crates__tock-registers-0.9.0//:tock_registers\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-1.51.1\",\n actual = \"@rust_crates__tokio-1.51.1//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio\",\n actual = \"@rust_crates__tokio-1.51.1//:tokio\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-util-0.7.18\",\n actual = \"@rust_crates__tokio-util-0.7.18//:tokio_util\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"tokio-util\",\n actual = \"@rust_crates__tokio-util-0.7.18//:tokio_util\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ufmt-0.2.0\",\n actual = \"@rust_crates__ufmt-0.2.0//:ufmt\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"ufmt\",\n actual = \"@rust_crates__ufmt-0.2.0//:ufmt\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zerocopy-0.8.48\",\n actual = \"@rust_crates__zerocopy-0.8.48//:zerocopy\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zerocopy\",\n actual = \"@rust_crates__zerocopy-0.8.48//:zerocopy\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zeroize-1.8.2\",\n actual = \"@rust_crates__zeroize-1.8.2//:zeroize\",\n tags = [\"manual\"],\n)\n\nalias(\n name = \"zeroize\",\n actual = \"@rust_crates__zeroize-1.8.2//:zeroize\",\n tags = [\"manual\"],\n)\n", "alias_rules.bzl": "\"\"\"Alias that transitions its target to `compilation_mode=opt`. Use `transition_alias=\"opt\"` to enable.\"\"\"\n\nload(\"@rules_cc//cc:defs.bzl\", \"CcInfo\")\nload(\"@rules_rust//rust:rust_common.bzl\", \"COMMON_PROVIDERS\")\n\ndef _transition_alias_impl(ctx):\n # `ctx.attr.actual` is a list of 1 item due to the transition\n providers = [ctx.attr.actual[0][provider] for provider in COMMON_PROVIDERS]\n if CcInfo in ctx.attr.actual[0]:\n providers.append(ctx.attr.actual[0][CcInfo])\n return providers\n\ndef _change_compilation_mode(compilation_mode):\n def _change_compilation_mode_impl(_settings, _attr):\n return {\n \"//command_line_option:compilation_mode\": compilation_mode,\n }\n\n return transition(\n implementation = _change_compilation_mode_impl,\n inputs = [],\n outputs = [\n \"//command_line_option:compilation_mode\",\n ],\n )\n\ndef _transition_alias_rule(compilation_mode):\n return rule(\n implementation = _transition_alias_impl,\n provides = COMMON_PROVIDERS,\n attrs = {\n \"actual\": attr.label(\n mandatory = True,\n doc = \"`rust_library()` target to transition to `compilation_mode=opt`.\",\n providers = COMMON_PROVIDERS,\n cfg = _change_compilation_mode(compilation_mode),\n ),\n \"_allowlist_function_transition\": attr.label(\n default = \"@bazel_tools//tools/allowlists/function_transition_allowlist\",\n ),\n },\n doc = \"Transitions a Rust library crate to the `compilation_mode=opt`.\",\n )\n\ntransition_alias_dbg = _transition_alias_rule(\"dbg\")\ntransition_alias_fastbuild = _transition_alias_rule(\"fastbuild\")\ntransition_alias_opt = _transition_alias_rule(\"opt\")\n", - "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"third_party/crates_io\": {\n _COMMON_CONDITION: {\n \"aes\": Label(\"@rust_crates//:aes-0.8.4\"),\n \"aes-gcm\": Label(\"@rust_crates//:aes-gcm-0.10.3\"),\n \"aligned\": Label(\"@rust_crates//:aligned-0.4.3\"),\n \"anyhow\": Label(\"@rust_crates//:anyhow-1.0.102\"),\n \"bitfield\": Label(\"@rust_crates//:bitfield-0.14.0\"),\n \"bitflags\": Label(\"@rust_crates//:bitflags-2.11.0\"),\n \"byteorder\": Label(\"@rust_crates//:byteorder-1.5.0\"),\n \"cfg-if\": Label(\"@rust_crates//:cfg-if-1.0.4\"),\n \"cipher\": Label(\"@rust_crates//:cipher-0.4.4\"),\n \"clap\": Label(\"@rust_crates//:clap-4.6.0\"),\n \"compiler_builtins\": Label(\"@rust_crates//:compiler_builtins-0.1.160\"),\n \"cortex-m\": Label(\"@rust_crates//:cortex-m-0.7.7\"),\n \"ctr\": Label(\"@rust_crates//:ctr-0.9.2\"),\n \"ecdsa\": Label(\"@rust_crates//:ecdsa-0.16.9\"),\n \"embedded-hal\": Label(\"@rust_crates//:embedded-hal-1.0.0\"),\n \"embedded-hal-async\": Label(\"@rust_crates//:embedded-hal-async-1.0.0\"),\n \"embedded-hal-nb\": Label(\"@rust_crates//:embedded-hal-nb-1.0.0\"),\n \"embedded-io\": Label(\"@rust_crates//:embedded-io-0.6.1\"),\n \"futures\": Label(\"@rust_crates//:futures-0.3.32\"),\n \"heapless\": Label(\"@rust_crates//:heapless-0.9.2\"),\n \"hex\": Label(\"@rust_crates//:hex-0.4.3\"),\n \"hmac\": Label(\"@rust_crates//:hmac-0.12.1\"),\n \"k256\": Label(\"@rust_crates//:k256-0.13.4\"),\n \"log\": Label(\"@rust_crates//:log-0.4.29\"),\n \"memoffset\": Label(\"@rust_crates//:memoffset-0.9.1\"),\n \"minijinja\": Label(\"@rust_crates//:minijinja-2.19.0\"),\n \"nb\": Label(\"@rust_crates//:nb-1.1.0\"),\n \"nom\": Label(\"@rust_crates//:nom-7.1.3\"),\n \"object\": Label(\"@rust_crates//:object-0.37.3\"),\n \"p256\": Label(\"@rust_crates//:p256-0.13.2\"),\n \"p384\": Label(\"@rust_crates//:p384-0.13.1\"),\n \"proc-macro2\": Label(\"@rust_crates//:proc-macro2-1.0.106\"),\n \"prost\": Label(\"@rust_crates//:prost-0.13.5\"),\n \"quote\": Label(\"@rust_crates//:quote-1.0.45\"),\n \"rand_core\": Label(\"@rust_crates//:rand_core-0.9.5\"),\n \"riscv\": Label(\"@rust_crates//:riscv-0.12.1\"),\n \"riscv-rt\": Label(\"@rust_crates//:riscv-rt-0.12.2\"),\n \"riscv-semihosting\": Label(\"@rust_crates//:riscv-semihosting-0.1.3\"),\n \"rustc-demangle\": Label(\"@rust_crates//:rustc-demangle-0.1.27\"),\n \"sec1\": Label(\"@rust_crates//:sec1-0.7.3\"),\n \"serde\": Label(\"@rust_crates//:serde-1.0.228\"),\n \"serde_json5\": Label(\"@rust_crates//:serde_json5-0.2.1\"),\n \"sha2\": Label(\"@rust_crates//:sha2-0.10.9\"),\n \"sha3\": Label(\"@rust_crates//:sha3-0.10.8\"),\n \"smlang\": Label(\"@rust_crates//:smlang-0.8.0\"),\n \"subtle\": Label(\"@rust_crates//:subtle-2.6.1\"),\n \"syn1\": Label(\"@rust_crates//:syn-1.0.109\"),\n \"syn\": Label(\"@rust_crates//:syn-2.0.117\"),\n \"thiserror\": Label(\"@rust_crates//:thiserror-2.0.18\"),\n \"tock-registers\": Label(\"@rust_crates//:tock-registers-0.9.0\"),\n \"tokio\": Label(\"@rust_crates//:tokio-1.51.1\"),\n \"tokio-util\": Label(\"@rust_crates//:tokio-util-0.7.18\"),\n \"zerocopy\": Label(\"@rust_crates//:zerocopy-0.8.48\"),\n \"zeroize\": Label(\"@rust_crates//:zeroize-1.8.2\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"third_party/crates_io\": {\n _COMMON_CONDITION: {\n Label(\"@rust_crates//:syn-1.0.109\"): \"syn1\",\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"third_party/crates_io\": {\n _COMMON_CONDITION: {\n \"bitfield-struct\": Label(\"@rust_crates//:bitfield-struct-0.11.0\"),\n \"paste\": Label(\"@rust_crates//:paste-1.0.15\"),\n \"serde_derive\": Label(\"@rust_crates//:serde_derive-1.0.228\"),\n },\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-linux-android\": [],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\",\"@rules_rust//rust/platform:x86_64-apple-darwin\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_os = \\\"linux\\\"))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_vendor = \\\"apple\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(all(target_arch = \\\"loongarch64\\\", target_os = \\\"linux\\\"))\": [],\n \"cfg(any())\": [],\n \"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(any(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\", target_pointer_width = \\\"64\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(any(unix, target_os = \\\"hermit\\\", target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(target_arch = \\\"aarch64\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(target_os = \\\"hermit\\\")\": [],\n \"cfg(target_os = \\\"redox\\\")\": [],\n \"cfg(target_os = \\\"wasi\\\")\": [],\n \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(windows)\": [],\n \"riscv32imc-unknown-none-elf\": [\"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\"],\n \"x86_64-apple-darwin\": [\"@rules_rust//rust/platform:x86_64-apple-darwin\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"rust_crates__adler2-2.0.1\",\n sha256 = \"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/adler2/2.0.1/download\"],\n strip_prefix = \"adler2-2.0.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.adler2-2.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aead-0.5.2\",\n sha256 = \"d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aead/0.5.2/download\"],\n strip_prefix = \"aead-0.5.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aead-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aes-0.8.4\",\n sha256 = \"b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aes/0.8.4/download\"],\n strip_prefix = \"aes-0.8.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aes-0.8.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aes-gcm-0.10.3\",\n sha256 = \"831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aes-gcm/0.10.3/download\"],\n strip_prefix = \"aes-gcm-0.10.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aes-gcm-0.10.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aligned-0.4.3\",\n sha256 = \"ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aligned/0.4.3/download\"],\n strip_prefix = \"aligned-0.4.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aligned-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstream-1.0.0\",\n sha256 = \"824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/1.0.0/download\"],\n strip_prefix = \"anstream-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstream-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-1.0.14\",\n sha256 = \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle/1.0.14/download\"],\n strip_prefix = \"anstyle-1.0.14\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-1.0.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-parse-1.0.0\",\n sha256 = \"52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/1.0.0/download\"],\n strip_prefix = \"anstyle-parse-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-parse-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-query-1.1.5\",\n sha256 = \"40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-query/1.1.5/download\"],\n strip_prefix = \"anstyle-query-1.1.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-query-1.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-wincon-3.0.11\",\n sha256 = \"291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-wincon/3.0.11/download\"],\n strip_prefix = \"anstyle-wincon-3.0.11\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-wincon-3.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anyhow-1.0.102\",\n sha256 = \"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anyhow/1.0.102/download\"],\n strip_prefix = \"anyhow-1.0.102\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anyhow-1.0.102.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__as-slice-0.2.1\",\n sha256 = \"516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/as-slice/0.2.1/download\"],\n strip_prefix = \"as-slice-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.as-slice-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__autocfg-1.5.0\",\n sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n strip_prefix = \"autocfg-1.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.autocfg-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bare-metal-0.2.5\",\n sha256 = \"5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bare-metal/0.2.5/download\"],\n strip_prefix = \"bare-metal-0.2.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bare-metal-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__base16ct-0.2.0\",\n sha256 = \"4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base16ct/0.2.0/download\"],\n strip_prefix = \"base16ct-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.base16ct-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitfield-0.13.2\",\n sha256 = \"46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield/0.13.2/download\"],\n strip_prefix = \"bitfield-0.13.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitfield-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitfield-0.14.0\",\n sha256 = \"2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield/0.14.0/download\"],\n strip_prefix = \"bitfield-0.14.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitfield-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitfield-struct-0.11.0\",\n sha256 = \"d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield-struct/0.11.0/download\"],\n strip_prefix = \"bitfield-struct-0.11.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitfield-struct-0.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitflags-2.11.0\",\n sha256 = \"843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/2.11.0/download\"],\n strip_prefix = \"bitflags-2.11.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitflags-2.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__block-buffer-0.10.4\",\n sha256 = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/block-buffer/0.10.4/download\"],\n strip_prefix = \"block-buffer-0.10.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.block-buffer-0.10.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__byteorder-1.5.0\",\n sha256 = \"1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/byteorder/1.5.0/download\"],\n strip_prefix = \"byteorder-1.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.byteorder-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bytes-1.11.1\",\n sha256 = \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytes/1.11.1/download\"],\n strip_prefix = \"bytes-1.11.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bytes-1.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cfg-if-1.0.4\",\n sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n strip_prefix = \"cfg-if-1.0.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cfg-if-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cipher-0.4.4\",\n sha256 = \"773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cipher/0.4.4/download\"],\n strip_prefix = \"cipher-0.4.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cipher-0.4.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap-4.6.0\",\n sha256 = \"b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap/4.6.0/download\"],\n strip_prefix = \"clap-4.6.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap_builder-4.6.0\",\n sha256 = \"714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_builder/4.6.0/download\"],\n strip_prefix = \"clap_builder-4.6.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap_builder-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap_derive-4.6.0\",\n sha256 = \"1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_derive/4.6.0/download\"],\n strip_prefix = \"clap_derive-4.6.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap_derive-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap_lex-1.1.0\",\n sha256 = \"c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_lex/1.1.0/download\"],\n strip_prefix = \"clap_lex-1.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap_lex-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__colorchoice-1.0.5\",\n sha256 = \"1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colorchoice/1.0.5/download\"],\n strip_prefix = \"colorchoice-1.0.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.colorchoice-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__compiler_builtins-0.1.160\",\n sha256 = \"6376049cfa92c0aa8b9ac95fae22184b981c658208d4ed8a1dc553cd83612895\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/compiler_builtins/0.1.160/download\"],\n strip_prefix = \"compiler_builtins-0.1.160\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.compiler_builtins-0.1.160.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__const-oid-0.9.6\",\n sha256 = \"c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/const-oid/0.9.6/download\"],\n strip_prefix = \"const-oid-0.9.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.const-oid-0.9.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cortex-m-0.7.7\",\n sha256 = \"8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cortex-m/0.7.7/download\"],\n strip_prefix = \"cortex-m-0.7.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cortex-m-0.7.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cpufeatures-0.2.17\",\n sha256 = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cpufeatures/0.2.17/download\"],\n strip_prefix = \"cpufeatures-0.2.17\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cpufeatures-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__crc32fast-1.5.0\",\n sha256 = \"9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crc32fast/1.5.0/download\"],\n strip_prefix = \"crc32fast-1.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.crc32fast-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__critical-section-1.2.0\",\n sha256 = \"790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/critical-section/1.2.0/download\"],\n strip_prefix = \"critical-section-1.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.critical-section-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__crypto-bigint-0.5.5\",\n sha256 = \"0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crypto-bigint/0.5.5/download\"],\n strip_prefix = \"crypto-bigint-0.5.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.crypto-bigint-0.5.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__crypto-common-0.1.7\",\n sha256 = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crypto-common/0.1.7/download\"],\n strip_prefix = \"crypto-common-0.1.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.crypto-common-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ctr-0.9.2\",\n sha256 = \"0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ctr/0.9.2/download\"],\n strip_prefix = \"ctr-0.9.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ctr-0.9.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__der-0.7.10\",\n sha256 = \"e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/der/0.7.10/download\"],\n strip_prefix = \"der-0.7.10\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.der-0.7.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__digest-0.10.7\",\n sha256 = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/digest/0.10.7/download\"],\n strip_prefix = \"digest-0.10.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.digest-0.10.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ecdsa-0.16.9\",\n sha256 = \"ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ecdsa/0.16.9/download\"],\n strip_prefix = \"ecdsa-0.16.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ecdsa-0.16.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__either-1.15.0\",\n sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n strip_prefix = \"either-1.15.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.either-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__elliptic-curve-0.13.8\",\n sha256 = \"b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/elliptic-curve/0.13.8/download\"],\n strip_prefix = \"elliptic-curve-0.13.8\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.elliptic-curve-0.13.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-0.2.7\",\n sha256 = \"35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal/0.2.7/download\"],\n strip_prefix = \"embedded-hal-0.2.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-0.2.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-1.0.0\",\n sha256 = \"361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal/1.0.0/download\"],\n strip_prefix = \"embedded-hal-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-async-1.0.0\",\n sha256 = \"0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal-async/1.0.0/download\"],\n strip_prefix = \"embedded-hal-async-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-async-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-nb-1.0.0\",\n sha256 = \"fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal-nb/1.0.0/download\"],\n strip_prefix = \"embedded-hal-nb-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-nb-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-io-0.6.1\",\n sha256 = \"edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-io/0.6.1/download\"],\n strip_prefix = \"embedded-io-0.6.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-io-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__equivalent-1.0.2\",\n sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n strip_prefix = \"equivalent-1.0.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.equivalent-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__errno-0.3.14\",\n sha256 = \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/errno/0.3.14/download\"],\n strip_prefix = \"errno-0.3.14\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.errno-0.3.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ff-0.13.1\",\n sha256 = \"c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ff/0.13.1/download\"],\n strip_prefix = \"ff-0.13.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ff-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__flate2-1.1.9\",\n sha256 = \"843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/flate2/1.1.9/download\"],\n strip_prefix = \"flate2-1.1.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.flate2-1.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__foldhash-0.1.5\",\n sha256 = \"d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.1.5/download\"],\n strip_prefix = \"foldhash-0.1.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.foldhash-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-0.3.32\",\n sha256 = \"8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures/0.3.32/download\"],\n strip_prefix = \"futures-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-channel-0.3.32\",\n sha256 = \"07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-channel/0.3.32/download\"],\n strip_prefix = \"futures-channel-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-channel-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-core-0.3.32\",\n sha256 = \"7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-core/0.3.32/download\"],\n strip_prefix = \"futures-core-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-core-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-executor-0.3.32\",\n sha256 = \"baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-executor/0.3.32/download\"],\n strip_prefix = \"futures-executor-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-executor-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-io-0.3.32\",\n sha256 = \"cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-io/0.3.32/download\"],\n strip_prefix = \"futures-io-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-io-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-macro-0.3.32\",\n sha256 = \"e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-macro/0.3.32/download\"],\n strip_prefix = \"futures-macro-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-macro-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-sink-0.3.32\",\n sha256 = \"c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-sink/0.3.32/download\"],\n strip_prefix = \"futures-sink-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-sink-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-task-0.3.32\",\n sha256 = \"037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-task/0.3.32/download\"],\n strip_prefix = \"futures-task-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-task-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-util-0.3.32\",\n sha256 = \"389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-util/0.3.32/download\"],\n strip_prefix = \"futures-util-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-util-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__generic-array-0.14.7\",\n sha256 = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/generic-array/0.14.7/download\"],\n strip_prefix = \"generic-array-0.14.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.generic-array-0.14.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ghash-0.5.1\",\n sha256 = \"f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ghash/0.5.1/download\"],\n strip_prefix = \"ghash-0.5.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ghash-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__group-0.13.0\",\n sha256 = \"f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/group/0.13.0/download\"],\n strip_prefix = \"group-0.13.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.group-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hash32-0.3.1\",\n sha256 = \"47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hash32/0.3.1/download\"],\n strip_prefix = \"hash32-0.3.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hash32-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hashbrown-0.15.5\",\n sha256 = \"9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.15.5/download\"],\n strip_prefix = \"hashbrown-0.15.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hashbrown-0.15.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hashbrown-0.17.0\",\n sha256 = \"4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.17.0/download\"],\n strip_prefix = \"hashbrown-0.17.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hashbrown-0.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__heapless-0.9.2\",\n sha256 = \"2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heapless/0.9.2/download\"],\n strip_prefix = \"heapless-0.9.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.heapless-0.9.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__heck-0.5.0\",\n sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n strip_prefix = \"heck-0.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.heck-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hex-0.4.3\",\n sha256 = \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex/0.4.3/download\"],\n strip_prefix = \"hex-0.4.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hex-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hmac-0.12.1\",\n sha256 = \"6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hmac/0.12.1/download\"],\n strip_prefix = \"hmac-0.12.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hmac-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__indexmap-2.14.0\",\n sha256 = \"d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/2.14.0/download\"],\n strip_prefix = \"indexmap-2.14.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.indexmap-2.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__inout-0.1.4\",\n sha256 = \"879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/inout/0.1.4/download\"],\n strip_prefix = \"inout-0.1.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.inout-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__is_terminal_polyfill-1.70.2\",\n sha256 = \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download\"],\n strip_prefix = \"is_terminal_polyfill-1.70.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.is_terminal_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__itertools-0.14.0\",\n sha256 = \"2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.14.0/download\"],\n strip_prefix = \"itertools-0.14.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.itertools-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__k256-0.13.4\",\n sha256 = \"f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/k256/0.13.4/download\"],\n strip_prefix = \"k256-0.13.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.k256-0.13.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__keccak-0.1.6\",\n sha256 = \"cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/keccak/0.1.6/download\"],\n strip_prefix = \"keccak-0.1.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.keccak-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__libc-0.2.184\",\n sha256 = \"48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libc/0.2.184/download\"],\n strip_prefix = \"libc-0.2.184\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.libc-0.2.184.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__linux-raw-sys-0.12.1\",\n sha256 = \"32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.12.1/download\"],\n strip_prefix = \"linux-raw-sys-0.12.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.linux-raw-sys-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__lock_api-0.4.14\",\n sha256 = \"224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lock_api/0.4.14/download\"],\n strip_prefix = \"lock_api-0.4.14\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.lock_api-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__log-0.4.29\",\n sha256 = \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/log/0.4.29/download\"],\n strip_prefix = \"log-0.4.29\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.log-0.4.29.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__memchr-2.8.0\",\n sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n strip_prefix = \"memchr-2.8.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.memchr-2.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__memo-map-0.3.3\",\n sha256 = \"38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memo-map/0.3.3/download\"],\n strip_prefix = \"memo-map-0.3.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.memo-map-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__memoffset-0.9.1\",\n sha256 = \"488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memoffset/0.9.1/download\"],\n strip_prefix = \"memoffset-0.9.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.memoffset-0.9.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__minijinja-2.19.0\",\n sha256 = \"805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minijinja/2.19.0/download\"],\n strip_prefix = \"minijinja-2.19.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.minijinja-2.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__minimal-lexical-0.2.1\",\n sha256 = \"68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minimal-lexical/0.2.1/download\"],\n strip_prefix = \"minimal-lexical-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.minimal-lexical-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__miniz_oxide-0.8.9\",\n sha256 = \"1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/miniz_oxide/0.8.9/download\"],\n strip_prefix = \"miniz_oxide-0.8.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.miniz_oxide-0.8.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__mio-1.2.0\",\n sha256 = \"50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mio/1.2.0/download\"],\n strip_prefix = \"mio-1.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.mio-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__nb-0.1.3\",\n sha256 = \"801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nb/0.1.3/download\"],\n strip_prefix = \"nb-0.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.nb-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__nb-1.1.0\",\n sha256 = \"8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nb/1.1.0/download\"],\n strip_prefix = \"nb-1.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.nb-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__nom-7.1.3\",\n sha256 = \"d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nom/7.1.3/download\"],\n strip_prefix = \"nom-7.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.nom-7.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__object-0.37.3\",\n sha256 = \"ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/object/0.37.3/download\"],\n strip_prefix = \"object-0.37.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.object-0.37.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__once_cell_polyfill-1.70.2\",\n sha256 = \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell_polyfill/1.70.2/download\"],\n strip_prefix = \"once_cell_polyfill-1.70.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.once_cell_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__opaque-debug-0.3.1\",\n sha256 = \"c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opaque-debug/0.3.1/download\"],\n strip_prefix = \"opaque-debug-0.3.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.opaque-debug-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__p256-0.13.2\",\n sha256 = \"c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/p256/0.13.2/download\"],\n strip_prefix = \"p256-0.13.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.p256-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__p384-0.13.1\",\n sha256 = \"fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/p384/0.13.1/download\"],\n strip_prefix = \"p384-0.13.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.p384-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__parking_lot-0.12.5\",\n sha256 = \"93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot/0.12.5/download\"],\n strip_prefix = \"parking_lot-0.12.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.parking_lot-0.12.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__parking_lot_core-0.9.12\",\n sha256 = \"2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot_core/0.9.12/download\"],\n strip_prefix = \"parking_lot_core-0.9.12\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.parking_lot_core-0.9.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__paste-1.0.15\",\n sha256 = \"57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/paste/1.0.15/download\"],\n strip_prefix = \"paste-1.0.15\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.paste-1.0.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest-2.8.6\",\n sha256 = \"e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest/2.8.6/download\"],\n strip_prefix = \"pest-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest_derive-2.8.6\",\n sha256 = \"11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest_derive/2.8.6/download\"],\n strip_prefix = \"pest_derive-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest_derive-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest_generator-2.8.6\",\n sha256 = \"8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest_generator/2.8.6/download\"],\n strip_prefix = \"pest_generator-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest_generator-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest_meta-2.8.6\",\n sha256 = \"89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest_meta/2.8.6/download\"],\n strip_prefix = \"pest_meta-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest_meta-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pin-project-lite-0.2.17\",\n sha256 = \"a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project-lite/0.2.17/download\"],\n strip_prefix = \"pin-project-lite-0.2.17\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pin-project-lite-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__polyval-0.6.2\",\n sha256 = \"9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/polyval/0.6.2/download\"],\n strip_prefix = \"polyval-0.6.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.polyval-0.6.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__primeorder-0.13.6\",\n sha256 = \"353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/primeorder/0.13.6/download\"],\n strip_prefix = \"primeorder-0.13.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.primeorder-0.13.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__prost-0.13.5\",\n sha256 = \"2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost/0.13.5/download\"],\n strip_prefix = \"prost-0.13.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.prost-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__prost-derive-0.13.5\",\n sha256 = \"8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost-derive/0.13.5/download\"],\n strip_prefix = \"prost-derive-0.13.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.prost-derive-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__quote-1.0.45\",\n sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n strip_prefix = \"quote-1.0.45\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.quote-1.0.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rand_core-0.6.4\",\n sha256 = \"ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.6.4/download\"],\n strip_prefix = \"rand_core-0.6.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rand_core-0.6.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rand_core-0.9.5\",\n sha256 = \"76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.9.5/download\"],\n strip_prefix = \"rand_core-0.9.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rand_core-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__redox_syscall-0.5.18\",\n sha256 = \"ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/redox_syscall/0.5.18/download\"],\n strip_prefix = \"redox_syscall-0.5.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.redox_syscall-0.5.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rfc6979-0.4.0\",\n sha256 = \"f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rfc6979/0.4.0/download\"],\n strip_prefix = \"rfc6979-0.4.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rfc6979-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-0.11.1\",\n sha256 = \"2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv/0.11.1/download\"],\n strip_prefix = \"riscv-0.11.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-0.12.1\",\n sha256 = \"5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv/0.12.1/download\"],\n strip_prefix = \"riscv-0.12.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-0.13.0\",\n sha256 = \"afa3cdbeccae4359f6839a00e8b77e5736caa200ba216caf38d24e4c16e2b586\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv/0.13.0/download\"],\n strip_prefix = \"riscv-0.13.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-macros-0.1.0\",\n sha256 = \"f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-macros/0.1.0/download\"],\n strip_prefix = \"riscv-macros-0.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-macros-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-macros-0.2.0\",\n sha256 = \"e8c4aa1ea1af6dcc83a61be12e8189f9b293c3ba5a487778a4cd89fb060fdbbc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-macros/0.2.0/download\"],\n strip_prefix = \"riscv-macros-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-macros-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-pac-0.2.0\",\n sha256 = \"8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-pac/0.2.0/download\"],\n strip_prefix = \"riscv-pac-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-pac-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-rt-0.12.2\",\n sha256 = \"c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-rt/0.12.2/download\"],\n strip_prefix = \"riscv-rt-0.12.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-rt-0.12.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-rt-macros-0.2.2\",\n sha256 = \"30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-rt-macros/0.2.2/download\"],\n strip_prefix = \"riscv-rt-macros-0.2.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-rt-macros-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-semihosting-0.1.3\",\n sha256 = \"1086dd4bcc13de1cb14b93849411e3466de7e5907d2d8eb269032536e93facc6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-semihosting/0.1.3/download\"],\n strip_prefix = \"riscv-semihosting-0.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-semihosting-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rustc-demangle-0.1.27\",\n sha256 = \"b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc-demangle/0.1.27/download\"],\n strip_prefix = \"rustc-demangle-0.1.27\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rustc-demangle-0.1.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rustc_version-0.2.3\",\n sha256 = \"138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc_version/0.2.3/download\"],\n strip_prefix = \"rustc_version-0.2.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rustc_version-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rustix-1.1.4\",\n sha256 = \"b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/1.1.4/download\"],\n strip_prefix = \"rustix-1.1.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rustix-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ruzstd-0.8.2\",\n sha256 = \"e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ruzstd/0.8.2/download\"],\n strip_prefix = \"ruzstd-0.8.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ruzstd-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__scopeguard-1.2.0\",\n sha256 = \"94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/scopeguard/1.2.0/download\"],\n strip_prefix = \"scopeguard-1.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.scopeguard-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__sec1-0.7.3\",\n sha256 = \"d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sec1/0.7.3/download\"],\n strip_prefix = \"sec1-0.7.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.sec1-0.7.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__semver-0.9.0\",\n sha256 = \"1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver/0.9.0/download\"],\n strip_prefix = \"semver-0.9.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.semver-0.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__semver-parser-0.7.0\",\n sha256 = \"388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver-parser/0.7.0/download\"],\n strip_prefix = \"semver-parser-0.7.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.semver-parser-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde_json5-0.2.1\",\n sha256 = \"5d34d03f54462862f2a42918391c9526337f53171eaa4d8894562be7f252edd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json5/0.2.1/download\"],\n strip_prefix = \"serde_json5-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde_json5-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__sha2-0.10.9\",\n sha256 = \"a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha2/0.10.9/download\"],\n strip_prefix = \"sha2-0.10.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.sha2-0.10.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__sha3-0.10.8\",\n sha256 = \"75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha3/0.10.8/download\"],\n strip_prefix = \"sha3-0.10.8\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.sha3-0.10.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__signal-hook-registry-1.4.8\",\n sha256 = \"c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/signal-hook-registry/1.4.8/download\"],\n strip_prefix = \"signal-hook-registry-1.4.8\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.signal-hook-registry-1.4.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__signature-2.2.0\",\n sha256 = \"77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/signature/2.2.0/download\"],\n strip_prefix = \"signature-2.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.signature-2.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__simd-adler32-0.3.9\",\n sha256 = \"703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/simd-adler32/0.3.9/download\"],\n strip_prefix = \"simd-adler32-0.3.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.simd-adler32-0.3.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__slab-0.4.12\",\n sha256 = \"0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/slab/0.4.12/download\"],\n strip_prefix = \"slab-0.4.12\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.slab-0.4.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__smallvec-1.15.1\",\n sha256 = \"67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smallvec/1.15.1/download\"],\n strip_prefix = \"smallvec-1.15.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.smallvec-1.15.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__smlang-0.8.0\",\n sha256 = \"1de84f9f80bbe6272174e2bfdb8cf7ce4815b218038a42161c2f21c1d872c215\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smlang/0.8.0/download\"],\n strip_prefix = \"smlang-0.8.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.smlang-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__smlang-macros-0.8.0\",\n sha256 = \"231b4425dcc43afc7e18c34e7c6738cd252d42d91d909c948df14107c9ae79f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smlang-macros/0.8.0/download\"],\n strip_prefix = \"smlang-macros-0.8.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.smlang-macros-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__socket2-0.6.3\",\n sha256 = \"3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/socket2/0.6.3/download\"],\n strip_prefix = \"socket2-0.6.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.socket2-0.6.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__stable_deref_trait-1.2.1\",\n sha256 = \"6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/stable_deref_trait/1.2.1/download\"],\n strip_prefix = \"stable_deref_trait-1.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.stable_deref_trait-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__string_morph-0.1.0\",\n sha256 = \"183aaf7fa637cc7b5f54c45b8f7cb6e8d73831f9f75a56b6defa5bf8c51d1699\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/string_morph/0.1.0/download\"],\n strip_prefix = \"string_morph-0.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.string_morph-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__strsim-0.11.1\",\n sha256 = \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strsim/0.11.1/download\"],\n strip_prefix = \"strsim-0.11.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.strsim-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__subtle-2.6.1\",\n sha256 = \"13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/subtle/2.6.1/download\"],\n strip_prefix = \"subtle-2.6.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.subtle-2.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__syn-1.0.109\",\n sha256 = \"72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/1.0.109/download\"],\n strip_prefix = \"syn-1.0.109\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.syn-1.0.109.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__terminal_size-0.4.4\",\n sha256 = \"230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/terminal_size/0.4.4/download\"],\n strip_prefix = \"terminal_size-0.4.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.terminal_size-0.4.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__thiserror-2.0.18\",\n sha256 = \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/2.0.18/download\"],\n strip_prefix = \"thiserror-2.0.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.thiserror-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__thiserror-impl-2.0.18\",\n sha256 = \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.18/download\"],\n strip_prefix = \"thiserror-impl-2.0.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.thiserror-impl-2.0.18.bazel\"),\n )\n\n maybe(\n new_git_repository,\n name = \"rust_crates__tock-registers-0.9.0\",\n commit = \"9554639b17501a9f5940cef7a1770a0823e790c3\",\n init_submodules = True,\n remote = \"https://github.com/tock/tock.git\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tock-registers-0.9.0.bazel\"),\n strip_prefix = \"libraries/tock-register-interface\",\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__tokio-1.51.1\",\n sha256 = \"f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio/1.51.1/download\"],\n strip_prefix = \"tokio-1.51.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tokio-1.51.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__tokio-macros-2.7.0\",\n sha256 = \"385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-macros/2.7.0/download\"],\n strip_prefix = \"tokio-macros-2.7.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tokio-macros-2.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__tokio-util-0.7.18\",\n sha256 = \"9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-util/0.7.18/download\"],\n strip_prefix = \"tokio-util-0.7.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tokio-util-0.7.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__twox-hash-2.1.2\",\n sha256 = \"9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/twox-hash/2.1.2/download\"],\n strip_prefix = \"twox-hash-2.1.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.twox-hash-2.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__typenum-1.19.0\",\n sha256 = \"562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typenum/1.19.0/download\"],\n strip_prefix = \"typenum-1.19.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.typenum-1.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ucd-trie-0.1.7\",\n sha256 = \"2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ucd-trie/0.1.7/download\"],\n strip_prefix = \"ucd-trie-0.1.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ucd-trie-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__universal-hash-0.5.1\",\n sha256 = \"fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/universal-hash/0.5.1/download\"],\n strip_prefix = \"universal-hash-0.5.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.universal-hash-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__utf8parse-0.2.2\",\n sha256 = \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8parse/0.2.2/download\"],\n strip_prefix = \"utf8parse-0.2.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.utf8parse-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__vcell-0.1.3\",\n sha256 = \"77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/vcell/0.1.3/download\"],\n strip_prefix = \"vcell-0.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.vcell-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__version_check-0.9.5\",\n sha256 = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/version_check/0.9.5/download\"],\n strip_prefix = \"version_check-0.9.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.version_check-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__void-1.0.2\",\n sha256 = \"6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/void/1.0.2/download\"],\n strip_prefix = \"void-1.0.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.void-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__volatile-register-0.2.2\",\n sha256 = \"de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/volatile-register/0.2.2/download\"],\n strip_prefix = \"volatile-register-0.2.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.volatile-register-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__wasi-0.11.1-wasi-snapshot-preview1\",\n sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__windows-link-0.2.1\",\n sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n strip_prefix = \"windows-link-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.windows-link-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__windows-sys-0.61.2\",\n sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n strip_prefix = \"windows-sys-0.61.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.windows-sys-0.61.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zerocopy-0.8.48\",\n sha256 = \"eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy/0.8.48/download\"],\n strip_prefix = \"zerocopy-0.8.48\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zerocopy-0.8.48.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zerocopy-derive-0.8.48\",\n sha256 = \"70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.48/download\"],\n strip_prefix = \"zerocopy-derive-0.8.48\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zerocopy-derive-0.8.48.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zeroize-1.8.2\",\n sha256 = \"b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zeroize/1.8.2/download\"],\n strip_prefix = \"zeroize-1.8.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zeroize-1.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zeroize_derive-1.4.3\",\n sha256 = \"85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zeroize_derive/1.4.3/download\"],\n strip_prefix = \"zeroize_derive-1.4.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zeroize_derive-1.4.3.bazel\"),\n )\n\n return [\n struct(repo=\"rust_crates__aes-0.8.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__aes-gcm-0.10.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__aligned-0.4.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__anyhow-1.0.102\", is_dev_dep = False),\n struct(repo=\"rust_crates__bitfield-0.14.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__bitfield-struct-0.11.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__bitflags-2.11.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__byteorder-1.5.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__cfg-if-1.0.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__cipher-0.4.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__clap-4.6.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__compiler_builtins-0.1.160\", is_dev_dep = False),\n struct(repo=\"rust_crates__cortex-m-0.7.7\", is_dev_dep = False),\n struct(repo=\"rust_crates__ctr-0.9.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__ecdsa-0.16.9\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-hal-1.0.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-hal-async-1.0.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-hal-nb-1.0.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-io-0.6.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__futures-0.3.32\", is_dev_dep = False),\n struct(repo=\"rust_crates__heapless-0.9.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__hex-0.4.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__hmac-0.12.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__k256-0.13.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__log-0.4.29\", is_dev_dep = False),\n struct(repo=\"rust_crates__memoffset-0.9.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__minijinja-2.19.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__nb-1.1.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__nom-7.1.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__object-0.37.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__p256-0.13.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__p384-0.13.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__paste-1.0.15\", is_dev_dep = False),\n struct(repo=\"rust_crates__proc-macro2-1.0.106\", is_dev_dep = False),\n struct(repo=\"rust_crates__prost-0.13.5\", is_dev_dep = False),\n struct(repo=\"rust_crates__quote-1.0.45\", is_dev_dep = False),\n struct(repo=\"rust_crates__rand_core-0.9.5\", is_dev_dep = False),\n struct(repo=\"rust_crates__riscv-0.12.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__riscv-rt-0.12.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__riscv-semihosting-0.1.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__rustc-demangle-0.1.27\", is_dev_dep = False),\n struct(repo=\"rust_crates__sec1-0.7.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__serde-1.0.228\", is_dev_dep = False),\n struct(repo=\"rust_crates__serde_derive-1.0.228\", is_dev_dep = False),\n struct(repo=\"rust_crates__serde_json5-0.2.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__sha2-0.10.9\", is_dev_dep = False),\n struct(repo=\"rust_crates__sha3-0.10.8\", is_dev_dep = False),\n struct(repo=\"rust_crates__smlang-0.8.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__subtle-2.6.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__syn-1.0.109\", is_dev_dep = False),\n struct(repo=\"rust_crates__syn-2.0.117\", is_dev_dep = False),\n struct(repo=\"rust_crates__thiserror-2.0.18\", is_dev_dep = False),\n struct(repo=\"rust_crates__tock-registers-0.9.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__tokio-1.51.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__tokio-util-0.7.18\", is_dev_dep = False),\n struct(repo=\"rust_crates__zerocopy-0.8.48\", is_dev_dep = False),\n struct(repo=\"rust_crates__zeroize-1.8.2\", is_dev_dep = False),\n ]\n" + "defs.bzl": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\"\"\"\n# `crates_repository` API\n\n- [aliases](#aliases)\n- [crate_deps](#crate_deps)\n- [all_crate_deps](#all_crate_deps)\n- [crate_repositories](#crate_repositories)\n\n\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:git.bzl\", \"new_git_repository\")\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"@bazel_tools//tools/build_defs/repo:utils.bzl\", \"maybe\")\nload(\"@bazel_skylib//lib:selects.bzl\", \"selects\")\nload(\"@rules_rust//crate_universe/private:local_crate_mirror.bzl\", \"local_crate_mirror\")\n\n###############################################################################\n# MACROS API\n###############################################################################\n\n# An identifier that represent common dependencies (unconditional).\n_COMMON_CONDITION = \"\"\n\ndef _flatten_dependency_maps(all_dependency_maps):\n \"\"\"Flatten a list of dependency maps into one dictionary.\n\n Dependency maps have the following structure:\n\n ```python\n DEPENDENCIES_MAP = {\n # The first key in the map is a Bazel package\n # name of the workspace this file is defined in.\n \"workspace_member_package\": {\n\n # Not all dependencies are supported for all platforms.\n # the condition key is the condition required to be true\n # on the host platform.\n \"condition\": {\n\n # An alias to a crate target. # The label of the crate target the\n # Aliases are only crate names. # package name refers to.\n \"package_name\": \"@full//:label\",\n }\n }\n }\n ```\n\n Args:\n all_dependency_maps (list): A list of dicts as described above\n\n Returns:\n dict: A dictionary as described above\n \"\"\"\n dependencies = {}\n\n for workspace_deps_map in all_dependency_maps:\n for pkg_name, conditional_deps_map in workspace_deps_map.items():\n if pkg_name not in dependencies:\n non_frozen_map = dict()\n for key, values in conditional_deps_map.items():\n non_frozen_map.update({key: dict(values.items())})\n dependencies.setdefault(pkg_name, non_frozen_map)\n continue\n\n for condition, deps_map in conditional_deps_map.items():\n # If the condition has not been recorded, do so and continue\n if condition not in dependencies[pkg_name]:\n dependencies[pkg_name].setdefault(condition, dict(deps_map.items()))\n continue\n\n # Alert on any miss-matched dependencies\n inconsistent_entries = []\n for crate_name, crate_label in deps_map.items():\n existing = dependencies[pkg_name][condition].get(crate_name)\n if existing and existing != crate_label:\n inconsistent_entries.append((crate_name, existing, crate_label))\n dependencies[pkg_name][condition].update({crate_name: crate_label})\n\n return dependencies\n\ndef crate_deps(deps, package_name = None):\n \"\"\"Finds the fully qualified label of the requested crates for the package where this macro is called.\n\n Args:\n deps (list): The desired list of crate targets.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()`.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if not deps:\n return []\n\n if package_name == None:\n package_name = native.package_name()\n\n # Join both sets of dependencies\n dependencies = _flatten_dependency_maps([\n _NORMAL_DEPENDENCIES,\n _NORMAL_DEV_DEPENDENCIES,\n _PROC_MACRO_DEPENDENCIES,\n _PROC_MACRO_DEV_DEPENDENCIES,\n _BUILD_DEPENDENCIES,\n _BUILD_PROC_MACRO_DEPENDENCIES,\n ]).pop(package_name, {})\n\n # Combine all conditional packages so we can easily index over a flat list\n # TODO: Perhaps this should actually return select statements and maintain\n # the conditionals of the dependencies\n flat_deps = {}\n for deps_set in dependencies.values():\n for crate_name, crate_label in deps_set.items():\n flat_deps.update({crate_name: crate_label})\n\n missing_crates = []\n crate_targets = []\n for crate_target in deps:\n if crate_target not in flat_deps:\n missing_crates.append(crate_target)\n else:\n crate_targets.append(flat_deps[crate_target])\n\n if missing_crates:\n fail(\"Could not find crates `{}` among dependencies of `{}`. Available dependencies were `{}`\".format(\n missing_crates,\n package_name,\n dependencies,\n ))\n\n return crate_targets\n\ndef all_crate_deps(\n normal = False, \n normal_dev = False, \n proc_macro = False, \n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Finds the fully qualified label of all requested direct crate dependencies \\\n for the package where this macro is called.\n\n If no parameters are set, all normal dependencies are returned. Setting any one flag will\n otherwise impact the contents of the returned list.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n list: A list of labels to generated rust targets (str)\n \"\"\"\n\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_dependency_maps = []\n if normal:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n if normal_dev:\n all_dependency_maps.append(_NORMAL_DEV_DEPENDENCIES)\n if proc_macro:\n all_dependency_maps.append(_PROC_MACRO_DEPENDENCIES)\n if proc_macro_dev:\n all_dependency_maps.append(_PROC_MACRO_DEV_DEPENDENCIES)\n if build:\n all_dependency_maps.append(_BUILD_DEPENDENCIES)\n if build_proc_macro:\n all_dependency_maps.append(_BUILD_PROC_MACRO_DEPENDENCIES)\n\n # Default to always using normal dependencies\n if not all_dependency_maps:\n all_dependency_maps.append(_NORMAL_DEPENDENCIES)\n\n dependencies = _flatten_dependency_maps(all_dependency_maps).pop(package_name, None)\n\n if not dependencies:\n if dependencies == None:\n fail(\"Tried to get all_crate_deps for package \" + package_name + \" but that package had no Cargo.toml file\")\n else:\n return []\n\n crate_deps = list(dependencies.pop(_COMMON_CONDITION, {}).values())\n for condition, deps in dependencies.items():\n crate_deps += selects.with_or({\n tuple(_CONDITIONS[condition]): deps.values(),\n \"//conditions:default\": [],\n })\n\n return crate_deps\n\ndef aliases(\n normal = False,\n normal_dev = False,\n proc_macro = False,\n proc_macro_dev = False,\n build = False,\n build_proc_macro = False,\n package_name = None):\n \"\"\"Produces a map of Crate alias names to their original label\n\n If no dependency kinds are specified, `normal` and `proc_macro` are used by default.\n Setting any one flag will otherwise determine the contents of the returned dict.\n\n Args:\n normal (bool, optional): If True, normal dependencies are included in the\n output list.\n normal_dev (bool, optional): If True, normal dev dependencies will be\n included in the output list..\n proc_macro (bool, optional): If True, proc_macro dependencies are included\n in the output list.\n proc_macro_dev (bool, optional): If True, dev proc_macro dependencies are\n included in the output list.\n build (bool, optional): If True, build dependencies are included\n in the output list.\n build_proc_macro (bool, optional): If True, build proc_macro dependencies are\n included in the output list.\n package_name (str, optional): The package name of the set of dependencies to look up.\n Defaults to `native.package_name()` when unset.\n\n Returns:\n dict: The aliases of all associated packages\n \"\"\"\n if package_name == None:\n package_name = native.package_name()\n\n # Determine the relevant maps to use\n all_aliases_maps = []\n if normal:\n all_aliases_maps.append(_NORMAL_ALIASES)\n if normal_dev:\n all_aliases_maps.append(_NORMAL_DEV_ALIASES)\n if proc_macro:\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n if proc_macro_dev:\n all_aliases_maps.append(_PROC_MACRO_DEV_ALIASES)\n if build:\n all_aliases_maps.append(_BUILD_ALIASES)\n if build_proc_macro:\n all_aliases_maps.append(_BUILD_PROC_MACRO_ALIASES)\n\n # Default to always using normal aliases\n if not all_aliases_maps:\n all_aliases_maps.append(_NORMAL_ALIASES)\n all_aliases_maps.append(_PROC_MACRO_ALIASES)\n\n aliases = _flatten_dependency_maps(all_aliases_maps).pop(package_name, None)\n\n if not aliases:\n return dict()\n\n common_items = aliases.pop(_COMMON_CONDITION, {}).items()\n\n # If there are only common items in the dictionary, immediately return them\n if not len(aliases.keys()) == 1:\n return dict(common_items)\n\n # Build a single select statement where each conditional has accounted for the\n # common set of aliases.\n crate_aliases = {\"//conditions:default\": dict(common_items)}\n for condition, deps in aliases.items():\n condition_triples = _CONDITIONS[condition]\n for triple in condition_triples:\n if triple in crate_aliases:\n crate_aliases[triple].update(deps)\n else:\n crate_aliases.update({triple: dict(deps.items() + common_items)})\n\n return select(crate_aliases)\n\n###############################################################################\n# WORKSPACE MEMBER DEPS AND ALIASES\n###############################################################################\n\n_NORMAL_DEPENDENCIES = {\n \"third_party/crates_io\": {\n _COMMON_CONDITION: {\n \"aes\": Label(\"@rust_crates//:aes-0.8.4\"),\n \"aes-gcm\": Label(\"@rust_crates//:aes-gcm-0.10.3\"),\n \"aligned\": Label(\"@rust_crates//:aligned-0.4.3\"),\n \"anyhow\": Label(\"@rust_crates//:anyhow-1.0.102\"),\n \"bitfield\": Label(\"@rust_crates//:bitfield-0.14.0\"),\n \"bitflags\": Label(\"@rust_crates//:bitflags-2.11.0\"),\n \"byteorder\": Label(\"@rust_crates//:byteorder-1.5.0\"),\n \"cfg-if\": Label(\"@rust_crates//:cfg-if-1.0.4\"),\n \"cipher\": Label(\"@rust_crates//:cipher-0.4.4\"),\n \"clap\": Label(\"@rust_crates//:clap-4.6.0\"),\n \"compiler_builtins\": Label(\"@rust_crates//:compiler_builtins-0.1.160\"),\n \"cortex-m\": Label(\"@rust_crates//:cortex-m-0.7.7\"),\n \"ctr\": Label(\"@rust_crates//:ctr-0.9.2\"),\n \"ecdsa\": Label(\"@rust_crates//:ecdsa-0.16.9\"),\n \"embedded-hal\": Label(\"@rust_crates//:embedded-hal-1.0.0\"),\n \"embedded-hal-async\": Label(\"@rust_crates//:embedded-hal-async-1.0.0\"),\n \"embedded-hal-nb\": Label(\"@rust_crates//:embedded-hal-nb-1.0.0\"),\n \"embedded-io\": Label(\"@rust_crates//:embedded-io-0.6.1\"),\n \"futures\": Label(\"@rust_crates//:futures-0.3.32\"),\n \"heapless\": Label(\"@rust_crates//:heapless-0.9.2\"),\n \"hex\": Label(\"@rust_crates//:hex-0.4.3\"),\n \"hmac\": Label(\"@rust_crates//:hmac-0.12.1\"),\n \"k256\": Label(\"@rust_crates//:k256-0.13.4\"),\n \"log\": Label(\"@rust_crates//:log-0.4.29\"),\n \"memoffset\": Label(\"@rust_crates//:memoffset-0.9.1\"),\n \"minijinja\": Label(\"@rust_crates//:minijinja-2.19.0\"),\n \"nb\": Label(\"@rust_crates//:nb-1.1.0\"),\n \"nom\": Label(\"@rust_crates//:nom-7.1.3\"),\n \"object\": Label(\"@rust_crates//:object-0.37.3\"),\n \"p256\": Label(\"@rust_crates//:p256-0.13.2\"),\n \"p384\": Label(\"@rust_crates//:p384-0.13.1\"),\n \"proc-macro2\": Label(\"@rust_crates//:proc-macro2-1.0.106\"),\n \"prost\": Label(\"@rust_crates//:prost-0.13.5\"),\n \"quote\": Label(\"@rust_crates//:quote-1.0.45\"),\n \"rand_core\": Label(\"@rust_crates//:rand_core-0.9.5\"),\n \"riscv\": Label(\"@rust_crates//:riscv-0.12.1\"),\n \"riscv-rt\": Label(\"@rust_crates//:riscv-rt-0.12.2\"),\n \"riscv-semihosting\": Label(\"@rust_crates//:riscv-semihosting-0.1.3\"),\n \"rustc-demangle\": Label(\"@rust_crates//:rustc-demangle-0.1.27\"),\n \"sec1\": Label(\"@rust_crates//:sec1-0.7.3\"),\n \"serde\": Label(\"@rust_crates//:serde-1.0.228\"),\n \"serde_json5\": Label(\"@rust_crates//:serde_json5-0.2.1\"),\n \"sha2\": Label(\"@rust_crates//:sha2-0.10.9\"),\n \"sha3\": Label(\"@rust_crates//:sha3-0.10.8\"),\n \"smlang\": Label(\"@rust_crates//:smlang-0.8.0\"),\n \"subtle\": Label(\"@rust_crates//:subtle-2.6.1\"),\n \"syn1\": Label(\"@rust_crates//:syn-1.0.109\"),\n \"syn\": Label(\"@rust_crates//:syn-2.0.117\"),\n \"thiserror\": Label(\"@rust_crates//:thiserror-2.0.18\"),\n \"tock-registers\": Label(\"@rust_crates//:tock-registers-0.9.0\"),\n \"tokio\": Label(\"@rust_crates//:tokio-1.51.1\"),\n \"tokio-util\": Label(\"@rust_crates//:tokio-util-0.7.18\"),\n \"ufmt\": Label(\"@rust_crates//:ufmt-0.2.0\"),\n \"zerocopy\": Label(\"@rust_crates//:zerocopy-0.8.48\"),\n \"zeroize\": Label(\"@rust_crates//:zeroize-1.8.2\"),\n },\n },\n}\n\n\n_NORMAL_ALIASES = {\n \"third_party/crates_io\": {\n _COMMON_CONDITION: {\n Label(\"@rust_crates//:syn-1.0.109\"): \"syn1\",\n },\n },\n}\n\n\n_NORMAL_DEV_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_NORMAL_DEV_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_PROC_MACRO_DEPENDENCIES = {\n \"third_party/crates_io\": {\n _COMMON_CONDITION: {\n \"bitfield-struct\": Label(\"@rust_crates//:bitfield-struct-0.11.0\"),\n \"paste\": Label(\"@rust_crates//:paste-1.0.15\"),\n \"serde_derive\": Label(\"@rust_crates//:serde_derive-1.0.228\"),\n },\n },\n}\n\n\n_PROC_MACRO_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_PROC_MACRO_DEV_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_PROC_MACRO_DEV_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_DEPENDENCIES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_BUILD_PROC_MACRO_ALIASES = {\n \"third_party/crates_io\": {\n },\n}\n\n\n_CONDITIONS = {\n \"aarch64-apple-darwin\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"aarch64-linux-android\": [],\n \"aarch64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(any(target_os = \\\"linux\\\", target_os = \\\"android\\\"), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [],\n \"cfg(all(not(rustix_use_libc), not(miri), target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\"))))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(all(not(windows), any(rustix_use_libc, miri, not(all(target_os = \\\"linux\\\", any(target_endian = \\\"little\\\", any(target_arch = \\\"s390x\\\", target_arch = \\\"powerpc\\\")), any(target_arch = \\\"arm\\\", all(target_arch = \\\"aarch64\\\", target_pointer_width = \\\"64\\\"), target_arch = \\\"riscv64\\\", all(rustix_use_experimental_asm, target_arch = \\\"powerpc\\\"), all(rustix_use_experimental_asm, target_arch = \\\"powerpc64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"s390x\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips32r6\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64\\\"), all(rustix_use_experimental_asm, target_arch = \\\"mips64r6\\\"), target_arch = \\\"x86\\\", all(target_arch = \\\"x86_64\\\", target_pointer_width = \\\"64\\\")))))))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\",\"@rules_rust//rust/platform:x86_64-apple-darwin\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_os = \\\"linux\\\"))\": [\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(all(target_arch = \\\"aarch64\\\", target_vendor = \\\"apple\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\"],\n \"cfg(all(target_arch = \\\"loongarch64\\\", target_os = \\\"linux\\\"))\": [],\n \"cfg(any())\": [],\n \"cfg(any(target_arch = \\\"aarch64\\\", target_arch = \\\"x86_64\\\", target_arch = \\\"x86\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(any(target_arch = \\\"arm\\\", target_pointer_width = \\\"32\\\", target_pointer_width = \\\"64\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(any(unix, target_os = \\\"hermit\\\", target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(any(unix, target_os = \\\"wasi\\\"))\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(target_arch = \\\"aarch64\\\")\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\"],\n \"cfg(target_os = \\\"hermit\\\")\": [],\n \"cfg(target_os = \\\"redox\\\")\": [],\n \"cfg(target_os = \\\"wasi\\\")\": [],\n \"cfg(unix)\": [\"@rules_rust//rust/platform:aarch64-apple-darwin\",\"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\",\"@rules_rust//rust/platform:x86_64-apple-darwin\",\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n \"cfg(windows)\": [],\n \"riscv32imc-unknown-none-elf\": [\"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\"],\n \"x86_64-apple-darwin\": [\"@rules_rust//rust/platform:x86_64-apple-darwin\"],\n \"x86_64-unknown-linux-gnu\": [\"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\"],\n}\n\n###############################################################################\n\ndef crate_repositories():\n \"\"\"A macro for defining repositories for all generated crates.\n\n Returns:\n A list of repos visible to the module through the module extension.\n \"\"\"\n maybe(\n http_archive,\n name = \"rust_crates__adler2-2.0.1\",\n sha256 = \"320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/adler2/2.0.1/download\"],\n strip_prefix = \"adler2-2.0.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.adler2-2.0.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aead-0.5.2\",\n sha256 = \"d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aead/0.5.2/download\"],\n strip_prefix = \"aead-0.5.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aead-0.5.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aes-0.8.4\",\n sha256 = \"b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aes/0.8.4/download\"],\n strip_prefix = \"aes-0.8.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aes-0.8.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aes-gcm-0.10.3\",\n sha256 = \"831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aes-gcm/0.10.3/download\"],\n strip_prefix = \"aes-gcm-0.10.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aes-gcm-0.10.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__aligned-0.4.3\",\n sha256 = \"ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/aligned/0.4.3/download\"],\n strip_prefix = \"aligned-0.4.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.aligned-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstream-1.0.0\",\n sha256 = \"824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstream/1.0.0/download\"],\n strip_prefix = \"anstream-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstream-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-1.0.14\",\n sha256 = \"940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle/1.0.14/download\"],\n strip_prefix = \"anstyle-1.0.14\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-1.0.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-parse-1.0.0\",\n sha256 = \"52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-parse/1.0.0/download\"],\n strip_prefix = \"anstyle-parse-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-parse-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-query-1.1.5\",\n sha256 = \"40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-query/1.1.5/download\"],\n strip_prefix = \"anstyle-query-1.1.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-query-1.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anstyle-wincon-3.0.11\",\n sha256 = \"291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anstyle-wincon/3.0.11/download\"],\n strip_prefix = \"anstyle-wincon-3.0.11\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anstyle-wincon-3.0.11.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__anyhow-1.0.102\",\n sha256 = \"7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/anyhow/1.0.102/download\"],\n strip_prefix = \"anyhow-1.0.102\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.anyhow-1.0.102.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__as-slice-0.2.1\",\n sha256 = \"516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/as-slice/0.2.1/download\"],\n strip_prefix = \"as-slice-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.as-slice-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__autocfg-1.5.0\",\n sha256 = \"c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/autocfg/1.5.0/download\"],\n strip_prefix = \"autocfg-1.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.autocfg-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bare-metal-0.2.5\",\n sha256 = \"5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bare-metal/0.2.5/download\"],\n strip_prefix = \"bare-metal-0.2.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bare-metal-0.2.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__base16ct-0.2.0\",\n sha256 = \"4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/base16ct/0.2.0/download\"],\n strip_prefix = \"base16ct-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.base16ct-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitfield-0.13.2\",\n sha256 = \"46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield/0.13.2/download\"],\n strip_prefix = \"bitfield-0.13.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitfield-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitfield-0.14.0\",\n sha256 = \"2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield/0.14.0/download\"],\n strip_prefix = \"bitfield-0.14.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitfield-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitfield-struct-0.11.0\",\n sha256 = \"d3ca019570363e800b05ad4fd890734f28ac7b72f563ad8a35079efb793616f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitfield-struct/0.11.0/download\"],\n strip_prefix = \"bitfield-struct-0.11.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitfield-struct-0.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bitflags-2.11.0\",\n sha256 = \"843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bitflags/2.11.0/download\"],\n strip_prefix = \"bitflags-2.11.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bitflags-2.11.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__block-buffer-0.10.4\",\n sha256 = \"3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/block-buffer/0.10.4/download\"],\n strip_prefix = \"block-buffer-0.10.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.block-buffer-0.10.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__byteorder-1.5.0\",\n sha256 = \"1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/byteorder/1.5.0/download\"],\n strip_prefix = \"byteorder-1.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.byteorder-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__bytes-1.11.1\",\n sha256 = \"1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/bytes/1.11.1/download\"],\n strip_prefix = \"bytes-1.11.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.bytes-1.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cfg-if-1.0.4\",\n sha256 = \"9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cfg-if/1.0.4/download\"],\n strip_prefix = \"cfg-if-1.0.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cfg-if-1.0.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cipher-0.4.4\",\n sha256 = \"773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cipher/0.4.4/download\"],\n strip_prefix = \"cipher-0.4.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cipher-0.4.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap-4.6.0\",\n sha256 = \"b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap/4.6.0/download\"],\n strip_prefix = \"clap-4.6.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap_builder-4.6.0\",\n sha256 = \"714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_builder/4.6.0/download\"],\n strip_prefix = \"clap_builder-4.6.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap_builder-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap_derive-4.6.0\",\n sha256 = \"1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_derive/4.6.0/download\"],\n strip_prefix = \"clap_derive-4.6.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap_derive-4.6.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__clap_lex-1.1.0\",\n sha256 = \"c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/clap_lex/1.1.0/download\"],\n strip_prefix = \"clap_lex-1.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.clap_lex-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__colorchoice-1.0.5\",\n sha256 = \"1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/colorchoice/1.0.5/download\"],\n strip_prefix = \"colorchoice-1.0.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.colorchoice-1.0.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__compiler_builtins-0.1.160\",\n sha256 = \"6376049cfa92c0aa8b9ac95fae22184b981c658208d4ed8a1dc553cd83612895\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/compiler_builtins/0.1.160/download\"],\n strip_prefix = \"compiler_builtins-0.1.160\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.compiler_builtins-0.1.160.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__const-oid-0.9.6\",\n sha256 = \"c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/const-oid/0.9.6/download\"],\n strip_prefix = \"const-oid-0.9.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.const-oid-0.9.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cortex-m-0.7.7\",\n sha256 = \"8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cortex-m/0.7.7/download\"],\n strip_prefix = \"cortex-m-0.7.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cortex-m-0.7.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__cpufeatures-0.2.17\",\n sha256 = \"59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/cpufeatures/0.2.17/download\"],\n strip_prefix = \"cpufeatures-0.2.17\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.cpufeatures-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__crc32fast-1.5.0\",\n sha256 = \"9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crc32fast/1.5.0/download\"],\n strip_prefix = \"crc32fast-1.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.crc32fast-1.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__critical-section-1.2.0\",\n sha256 = \"790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/critical-section/1.2.0/download\"],\n strip_prefix = \"critical-section-1.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.critical-section-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__crypto-bigint-0.5.5\",\n sha256 = \"0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crypto-bigint/0.5.5/download\"],\n strip_prefix = \"crypto-bigint-0.5.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.crypto-bigint-0.5.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__crypto-common-0.1.7\",\n sha256 = \"78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/crypto-common/0.1.7/download\"],\n strip_prefix = \"crypto-common-0.1.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.crypto-common-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ctr-0.9.2\",\n sha256 = \"0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ctr/0.9.2/download\"],\n strip_prefix = \"ctr-0.9.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ctr-0.9.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__der-0.7.10\",\n sha256 = \"e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/der/0.7.10/download\"],\n strip_prefix = \"der-0.7.10\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.der-0.7.10.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__digest-0.10.7\",\n sha256 = \"9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/digest/0.10.7/download\"],\n strip_prefix = \"digest-0.10.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.digest-0.10.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ecdsa-0.16.9\",\n sha256 = \"ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ecdsa/0.16.9/download\"],\n strip_prefix = \"ecdsa-0.16.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ecdsa-0.16.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__either-1.15.0\",\n sha256 = \"48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/either/1.15.0/download\"],\n strip_prefix = \"either-1.15.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.either-1.15.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__elliptic-curve-0.13.8\",\n sha256 = \"b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/elliptic-curve/0.13.8/download\"],\n strip_prefix = \"elliptic-curve-0.13.8\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.elliptic-curve-0.13.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-0.2.7\",\n sha256 = \"35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal/0.2.7/download\"],\n strip_prefix = \"embedded-hal-0.2.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-0.2.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-1.0.0\",\n sha256 = \"361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal/1.0.0/download\"],\n strip_prefix = \"embedded-hal-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-async-1.0.0\",\n sha256 = \"0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal-async/1.0.0/download\"],\n strip_prefix = \"embedded-hal-async-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-async-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-hal-nb-1.0.0\",\n sha256 = \"fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-hal-nb/1.0.0/download\"],\n strip_prefix = \"embedded-hal-nb-1.0.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-hal-nb-1.0.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__embedded-io-0.6.1\",\n sha256 = \"edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/embedded-io/0.6.1/download\"],\n strip_prefix = \"embedded-io-0.6.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.embedded-io-0.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__equivalent-1.0.2\",\n sha256 = \"877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/equivalent/1.0.2/download\"],\n strip_prefix = \"equivalent-1.0.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.equivalent-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__errno-0.3.14\",\n sha256 = \"39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/errno/0.3.14/download\"],\n strip_prefix = \"errno-0.3.14\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.errno-0.3.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ff-0.13.1\",\n sha256 = \"c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ff/0.13.1/download\"],\n strip_prefix = \"ff-0.13.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ff-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__flate2-1.1.9\",\n sha256 = \"843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/flate2/1.1.9/download\"],\n strip_prefix = \"flate2-1.1.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.flate2-1.1.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__foldhash-0.1.5\",\n sha256 = \"d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/foldhash/0.1.5/download\"],\n strip_prefix = \"foldhash-0.1.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.foldhash-0.1.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-0.3.32\",\n sha256 = \"8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures/0.3.32/download\"],\n strip_prefix = \"futures-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-channel-0.3.32\",\n sha256 = \"07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-channel/0.3.32/download\"],\n strip_prefix = \"futures-channel-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-channel-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-core-0.3.32\",\n sha256 = \"7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-core/0.3.32/download\"],\n strip_prefix = \"futures-core-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-core-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-executor-0.3.32\",\n sha256 = \"baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-executor/0.3.32/download\"],\n strip_prefix = \"futures-executor-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-executor-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-io-0.3.32\",\n sha256 = \"cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-io/0.3.32/download\"],\n strip_prefix = \"futures-io-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-io-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-macro-0.3.32\",\n sha256 = \"e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-macro/0.3.32/download\"],\n strip_prefix = \"futures-macro-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-macro-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-sink-0.3.32\",\n sha256 = \"c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-sink/0.3.32/download\"],\n strip_prefix = \"futures-sink-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-sink-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-task-0.3.32\",\n sha256 = \"037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-task/0.3.32/download\"],\n strip_prefix = \"futures-task-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-task-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__futures-util-0.3.32\",\n sha256 = \"389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/futures-util/0.3.32/download\"],\n strip_prefix = \"futures-util-0.3.32\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.futures-util-0.3.32.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__generic-array-0.14.7\",\n sha256 = \"85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/generic-array/0.14.7/download\"],\n strip_prefix = \"generic-array-0.14.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.generic-array-0.14.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ghash-0.5.1\",\n sha256 = \"f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ghash/0.5.1/download\"],\n strip_prefix = \"ghash-0.5.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ghash-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__group-0.13.0\",\n sha256 = \"f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/group/0.13.0/download\"],\n strip_prefix = \"group-0.13.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.group-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hash32-0.3.1\",\n sha256 = \"47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hash32/0.3.1/download\"],\n strip_prefix = \"hash32-0.3.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hash32-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hashbrown-0.15.5\",\n sha256 = \"9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.15.5/download\"],\n strip_prefix = \"hashbrown-0.15.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hashbrown-0.15.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hashbrown-0.17.0\",\n sha256 = \"4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hashbrown/0.17.0/download\"],\n strip_prefix = \"hashbrown-0.17.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hashbrown-0.17.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__heapless-0.9.2\",\n sha256 = \"2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heapless/0.9.2/download\"],\n strip_prefix = \"heapless-0.9.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.heapless-0.9.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__heck-0.5.0\",\n sha256 = \"2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/heck/0.5.0/download\"],\n strip_prefix = \"heck-0.5.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.heck-0.5.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hex-0.4.3\",\n sha256 = \"7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hex/0.4.3/download\"],\n strip_prefix = \"hex-0.4.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hex-0.4.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__hmac-0.12.1\",\n sha256 = \"6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/hmac/0.12.1/download\"],\n strip_prefix = \"hmac-0.12.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.hmac-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__indexmap-2.14.0\",\n sha256 = \"d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/indexmap/2.14.0/download\"],\n strip_prefix = \"indexmap-2.14.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.indexmap-2.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__inout-0.1.4\",\n sha256 = \"879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/inout/0.1.4/download\"],\n strip_prefix = \"inout-0.1.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.inout-0.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__is_terminal_polyfill-1.70.2\",\n sha256 = \"a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/is_terminal_polyfill/1.70.2/download\"],\n strip_prefix = \"is_terminal_polyfill-1.70.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.is_terminal_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__itertools-0.14.0\",\n sha256 = \"2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/itertools/0.14.0/download\"],\n strip_prefix = \"itertools-0.14.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.itertools-0.14.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__k256-0.13.4\",\n sha256 = \"f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/k256/0.13.4/download\"],\n strip_prefix = \"k256-0.13.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.k256-0.13.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__keccak-0.1.6\",\n sha256 = \"cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/keccak/0.1.6/download\"],\n strip_prefix = \"keccak-0.1.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.keccak-0.1.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__libc-0.2.184\",\n sha256 = \"48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/libc/0.2.184/download\"],\n strip_prefix = \"libc-0.2.184\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.libc-0.2.184.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__linux-raw-sys-0.12.1\",\n sha256 = \"32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/linux-raw-sys/0.12.1/download\"],\n strip_prefix = \"linux-raw-sys-0.12.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.linux-raw-sys-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__lock_api-0.4.14\",\n sha256 = \"224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/lock_api/0.4.14/download\"],\n strip_prefix = \"lock_api-0.4.14\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.lock_api-0.4.14.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__log-0.4.29\",\n sha256 = \"5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/log/0.4.29/download\"],\n strip_prefix = \"log-0.4.29\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.log-0.4.29.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__memchr-2.8.0\",\n sha256 = \"f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memchr/2.8.0/download\"],\n strip_prefix = \"memchr-2.8.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.memchr-2.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__memo-map-0.3.3\",\n sha256 = \"38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memo-map/0.3.3/download\"],\n strip_prefix = \"memo-map-0.3.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.memo-map-0.3.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__memoffset-0.9.1\",\n sha256 = \"488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/memoffset/0.9.1/download\"],\n strip_prefix = \"memoffset-0.9.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.memoffset-0.9.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__minijinja-2.19.0\",\n sha256 = \"805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minijinja/2.19.0/download\"],\n strip_prefix = \"minijinja-2.19.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.minijinja-2.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__minimal-lexical-0.2.1\",\n sha256 = \"68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/minimal-lexical/0.2.1/download\"],\n strip_prefix = \"minimal-lexical-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.minimal-lexical-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__miniz_oxide-0.8.9\",\n sha256 = \"1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/miniz_oxide/0.8.9/download\"],\n strip_prefix = \"miniz_oxide-0.8.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.miniz_oxide-0.8.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__mio-1.2.0\",\n sha256 = \"50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/mio/1.2.0/download\"],\n strip_prefix = \"mio-1.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.mio-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__nb-0.1.3\",\n sha256 = \"801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nb/0.1.3/download\"],\n strip_prefix = \"nb-0.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.nb-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__nb-1.1.0\",\n sha256 = \"8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nb/1.1.0/download\"],\n strip_prefix = \"nb-1.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.nb-1.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__nom-7.1.3\",\n sha256 = \"d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/nom/7.1.3/download\"],\n strip_prefix = \"nom-7.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.nom-7.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__object-0.37.3\",\n sha256 = \"ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/object/0.37.3/download\"],\n strip_prefix = \"object-0.37.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.object-0.37.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__once_cell_polyfill-1.70.2\",\n sha256 = \"384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/once_cell_polyfill/1.70.2/download\"],\n strip_prefix = \"once_cell_polyfill-1.70.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.once_cell_polyfill-1.70.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__opaque-debug-0.3.1\",\n sha256 = \"c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/opaque-debug/0.3.1/download\"],\n strip_prefix = \"opaque-debug-0.3.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.opaque-debug-0.3.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__p256-0.13.2\",\n sha256 = \"c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/p256/0.13.2/download\"],\n strip_prefix = \"p256-0.13.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.p256-0.13.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__p384-0.13.1\",\n sha256 = \"fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/p384/0.13.1/download\"],\n strip_prefix = \"p384-0.13.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.p384-0.13.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__parking_lot-0.12.5\",\n sha256 = \"93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot/0.12.5/download\"],\n strip_prefix = \"parking_lot-0.12.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.parking_lot-0.12.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__parking_lot_core-0.9.12\",\n sha256 = \"2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/parking_lot_core/0.9.12/download\"],\n strip_prefix = \"parking_lot_core-0.9.12\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.parking_lot_core-0.9.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__paste-1.0.15\",\n sha256 = \"57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/paste/1.0.15/download\"],\n strip_prefix = \"paste-1.0.15\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.paste-1.0.15.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest-2.8.6\",\n sha256 = \"e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest/2.8.6/download\"],\n strip_prefix = \"pest-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest_derive-2.8.6\",\n sha256 = \"11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest_derive/2.8.6/download\"],\n strip_prefix = \"pest_derive-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest_derive-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest_generator-2.8.6\",\n sha256 = \"8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest_generator/2.8.6/download\"],\n strip_prefix = \"pest_generator-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest_generator-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pest_meta-2.8.6\",\n sha256 = \"89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pest_meta/2.8.6/download\"],\n strip_prefix = \"pest_meta-2.8.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pest_meta-2.8.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__pin-project-lite-0.2.17\",\n sha256 = \"a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/pin-project-lite/0.2.17/download\"],\n strip_prefix = \"pin-project-lite-0.2.17\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.pin-project-lite-0.2.17.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__polyval-0.6.2\",\n sha256 = \"9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/polyval/0.6.2/download\"],\n strip_prefix = \"polyval-0.6.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.polyval-0.6.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__primeorder-0.13.6\",\n sha256 = \"353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/primeorder/0.13.6/download\"],\n strip_prefix = \"primeorder-0.13.6\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.primeorder-0.13.6.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__proc-macro2-1.0.106\",\n sha256 = \"8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/proc-macro2/1.0.106/download\"],\n strip_prefix = \"proc-macro2-1.0.106\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.proc-macro2-1.0.106.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__prost-0.13.5\",\n sha256 = \"2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost/0.13.5/download\"],\n strip_prefix = \"prost-0.13.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.prost-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__prost-derive-0.13.5\",\n sha256 = \"8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/prost-derive/0.13.5/download\"],\n strip_prefix = \"prost-derive-0.13.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.prost-derive-0.13.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__quote-1.0.45\",\n sha256 = \"41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/quote/1.0.45/download\"],\n strip_prefix = \"quote-1.0.45\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.quote-1.0.45.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rand_core-0.6.4\",\n sha256 = \"ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.6.4/download\"],\n strip_prefix = \"rand_core-0.6.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rand_core-0.6.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rand_core-0.9.5\",\n sha256 = \"76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rand_core/0.9.5/download\"],\n strip_prefix = \"rand_core-0.9.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rand_core-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__redox_syscall-0.5.18\",\n sha256 = \"ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/redox_syscall/0.5.18/download\"],\n strip_prefix = \"redox_syscall-0.5.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.redox_syscall-0.5.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rfc6979-0.4.0\",\n sha256 = \"f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rfc6979/0.4.0/download\"],\n strip_prefix = \"rfc6979-0.4.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rfc6979-0.4.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-0.11.1\",\n sha256 = \"2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv/0.11.1/download\"],\n strip_prefix = \"riscv-0.11.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-0.12.1\",\n sha256 = \"5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv/0.12.1/download\"],\n strip_prefix = \"riscv-0.12.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-0.12.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-0.13.0\",\n sha256 = \"afa3cdbeccae4359f6839a00e8b77e5736caa200ba216caf38d24e4c16e2b586\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv/0.13.0/download\"],\n strip_prefix = \"riscv-0.13.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-0.13.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-macros-0.1.0\",\n sha256 = \"f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-macros/0.1.0/download\"],\n strip_prefix = \"riscv-macros-0.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-macros-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-macros-0.2.0\",\n sha256 = \"e8c4aa1ea1af6dcc83a61be12e8189f9b293c3ba5a487778a4cd89fb060fdbbc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-macros/0.2.0/download\"],\n strip_prefix = \"riscv-macros-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-macros-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-pac-0.2.0\",\n sha256 = \"8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-pac/0.2.0/download\"],\n strip_prefix = \"riscv-pac-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-pac-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-rt-0.12.2\",\n sha256 = \"c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-rt/0.12.2/download\"],\n strip_prefix = \"riscv-rt-0.12.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-rt-0.12.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-rt-macros-0.2.2\",\n sha256 = \"30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-rt-macros/0.2.2/download\"],\n strip_prefix = \"riscv-rt-macros-0.2.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-rt-macros-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__riscv-semihosting-0.1.3\",\n sha256 = \"1086dd4bcc13de1cb14b93849411e3466de7e5907d2d8eb269032536e93facc6\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/riscv-semihosting/0.1.3/download\"],\n strip_prefix = \"riscv-semihosting-0.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.riscv-semihosting-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rustc-demangle-0.1.27\",\n sha256 = \"b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc-demangle/0.1.27/download\"],\n strip_prefix = \"rustc-demangle-0.1.27\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rustc-demangle-0.1.27.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rustc_version-0.2.3\",\n sha256 = \"138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustc_version/0.2.3/download\"],\n strip_prefix = \"rustc_version-0.2.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rustc_version-0.2.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__rustix-1.1.4\",\n sha256 = \"b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/rustix/1.1.4/download\"],\n strip_prefix = \"rustix-1.1.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.rustix-1.1.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ruzstd-0.8.2\",\n sha256 = \"e5ff0cc5e135c8870a775d3320910cd9b564ec036b4dc0b8741629020be63f01\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ruzstd/0.8.2/download\"],\n strip_prefix = \"ruzstd-0.8.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ruzstd-0.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__scopeguard-1.2.0\",\n sha256 = \"94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/scopeguard/1.2.0/download\"],\n strip_prefix = \"scopeguard-1.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.scopeguard-1.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__sec1-0.7.3\",\n sha256 = \"d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sec1/0.7.3/download\"],\n strip_prefix = \"sec1-0.7.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.sec1-0.7.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__semver-0.9.0\",\n sha256 = \"1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver/0.9.0/download\"],\n strip_prefix = \"semver-0.9.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.semver-0.9.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__semver-parser-0.7.0\",\n sha256 = \"388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/semver-parser/0.7.0/download\"],\n strip_prefix = \"semver-parser-0.7.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.semver-parser-0.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde-1.0.228\",\n sha256 = \"9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde/1.0.228/download\"],\n strip_prefix = \"serde-1.0.228\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde_core-1.0.228\",\n sha256 = \"41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_core/1.0.228/download\"],\n strip_prefix = \"serde_core-1.0.228\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde_core-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde_derive-1.0.228\",\n sha256 = \"d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_derive/1.0.228/download\"],\n strip_prefix = \"serde_derive-1.0.228\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde_derive-1.0.228.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__serde_json5-0.2.1\",\n sha256 = \"5d34d03f54462862f2a42918391c9526337f53171eaa4d8894562be7f252edd3\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/serde_json5/0.2.1/download\"],\n strip_prefix = \"serde_json5-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.serde_json5-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__sha2-0.10.9\",\n sha256 = \"a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha2/0.10.9/download\"],\n strip_prefix = \"sha2-0.10.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.sha2-0.10.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__sha3-0.10.8\",\n sha256 = \"75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/sha3/0.10.8/download\"],\n strip_prefix = \"sha3-0.10.8\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.sha3-0.10.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__signal-hook-registry-1.4.8\",\n sha256 = \"c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/signal-hook-registry/1.4.8/download\"],\n strip_prefix = \"signal-hook-registry-1.4.8\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.signal-hook-registry-1.4.8.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__signature-2.2.0\",\n sha256 = \"77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/signature/2.2.0/download\"],\n strip_prefix = \"signature-2.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.signature-2.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__simd-adler32-0.3.9\",\n sha256 = \"703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/simd-adler32/0.3.9/download\"],\n strip_prefix = \"simd-adler32-0.3.9\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.simd-adler32-0.3.9.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__slab-0.4.12\",\n sha256 = \"0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/slab/0.4.12/download\"],\n strip_prefix = \"slab-0.4.12\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.slab-0.4.12.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__smallvec-1.15.1\",\n sha256 = \"67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smallvec/1.15.1/download\"],\n strip_prefix = \"smallvec-1.15.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.smallvec-1.15.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__smlang-0.8.0\",\n sha256 = \"1de84f9f80bbe6272174e2bfdb8cf7ce4815b218038a42161c2f21c1d872c215\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smlang/0.8.0/download\"],\n strip_prefix = \"smlang-0.8.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.smlang-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__smlang-macros-0.8.0\",\n sha256 = \"231b4425dcc43afc7e18c34e7c6738cd252d42d91d909c948df14107c9ae79f1\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/smlang-macros/0.8.0/download\"],\n strip_prefix = \"smlang-macros-0.8.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.smlang-macros-0.8.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__socket2-0.6.3\",\n sha256 = \"3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/socket2/0.6.3/download\"],\n strip_prefix = \"socket2-0.6.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.socket2-0.6.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__stable_deref_trait-1.2.1\",\n sha256 = \"6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/stable_deref_trait/1.2.1/download\"],\n strip_prefix = \"stable_deref_trait-1.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.stable_deref_trait-1.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__string_morph-0.1.0\",\n sha256 = \"183aaf7fa637cc7b5f54c45b8f7cb6e8d73831f9f75a56b6defa5bf8c51d1699\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/string_morph/0.1.0/download\"],\n strip_prefix = \"string_morph-0.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.string_morph-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__strsim-0.11.1\",\n sha256 = \"7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/strsim/0.11.1/download\"],\n strip_prefix = \"strsim-0.11.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.strsim-0.11.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__subtle-2.6.1\",\n sha256 = \"13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/subtle/2.6.1/download\"],\n strip_prefix = \"subtle-2.6.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.subtle-2.6.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__syn-1.0.109\",\n sha256 = \"72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/1.0.109/download\"],\n strip_prefix = \"syn-1.0.109\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.syn-1.0.109.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__syn-2.0.117\",\n sha256 = \"e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/syn/2.0.117/download\"],\n strip_prefix = \"syn-2.0.117\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.syn-2.0.117.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__terminal_size-0.4.4\",\n sha256 = \"230a1b821ccbd75b185820a1f1ff7b14d21da1e442e22c0863ea5f08771a8874\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/terminal_size/0.4.4/download\"],\n strip_prefix = \"terminal_size-0.4.4\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.terminal_size-0.4.4.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__thiserror-2.0.18\",\n sha256 = \"4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror/2.0.18/download\"],\n strip_prefix = \"thiserror-2.0.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.thiserror-2.0.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__thiserror-impl-2.0.18\",\n sha256 = \"ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/thiserror-impl/2.0.18/download\"],\n strip_prefix = \"thiserror-impl-2.0.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.thiserror-impl-2.0.18.bazel\"),\n )\n\n maybe(\n new_git_repository,\n name = \"rust_crates__tock-registers-0.9.0\",\n commit = \"9554639b17501a9f5940cef7a1770a0823e790c3\",\n init_submodules = True,\n remote = \"https://github.com/tock/tock.git\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tock-registers-0.9.0.bazel\"),\n strip_prefix = \"libraries/tock-register-interface\",\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__tokio-1.51.1\",\n sha256 = \"f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio/1.51.1/download\"],\n strip_prefix = \"tokio-1.51.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tokio-1.51.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__tokio-macros-2.7.0\",\n sha256 = \"385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-macros/2.7.0/download\"],\n strip_prefix = \"tokio-macros-2.7.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tokio-macros-2.7.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__tokio-util-0.7.18\",\n sha256 = \"9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/tokio-util/0.7.18/download\"],\n strip_prefix = \"tokio-util-0.7.18\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.tokio-util-0.7.18.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__twox-hash-2.1.2\",\n sha256 = \"9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/twox-hash/2.1.2/download\"],\n strip_prefix = \"twox-hash-2.1.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.twox-hash-2.1.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__typenum-1.19.0\",\n sha256 = \"562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/typenum/1.19.0/download\"],\n strip_prefix = \"typenum-1.19.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.typenum-1.19.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ucd-trie-0.1.7\",\n sha256 = \"2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ucd-trie/0.1.7/download\"],\n strip_prefix = \"ucd-trie-0.1.7\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ucd-trie-0.1.7.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ufmt-0.2.0\",\n sha256 = \"1a64846ec02b57e9108d6469d98d1648782ad6bb150a95a9baac26900bbeab9d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ufmt/0.2.0/download\"],\n strip_prefix = \"ufmt-0.2.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ufmt-0.2.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ufmt-macros-0.3.0\",\n sha256 = \"d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ufmt-macros/0.3.0/download\"],\n strip_prefix = \"ufmt-macros-0.3.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ufmt-macros-0.3.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__ufmt-write-0.1.0\",\n sha256 = \"e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/ufmt-write/0.1.0/download\"],\n strip_prefix = \"ufmt-write-0.1.0\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.ufmt-write-0.1.0.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__unicode-ident-1.0.24\",\n sha256 = \"e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/unicode-ident/1.0.24/download\"],\n strip_prefix = \"unicode-ident-1.0.24\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.unicode-ident-1.0.24.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__universal-hash-0.5.1\",\n sha256 = \"fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/universal-hash/0.5.1/download\"],\n strip_prefix = \"universal-hash-0.5.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.universal-hash-0.5.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__utf8parse-0.2.2\",\n sha256 = \"06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/utf8parse/0.2.2/download\"],\n strip_prefix = \"utf8parse-0.2.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.utf8parse-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__vcell-0.1.3\",\n sha256 = \"77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/vcell/0.1.3/download\"],\n strip_prefix = \"vcell-0.1.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.vcell-0.1.3.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__version_check-0.9.5\",\n sha256 = \"0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/version_check/0.9.5/download\"],\n strip_prefix = \"version_check-0.9.5\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.version_check-0.9.5.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__void-1.0.2\",\n sha256 = \"6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/void/1.0.2/download\"],\n strip_prefix = \"void-1.0.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.void-1.0.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__volatile-register-0.2.2\",\n sha256 = \"de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/volatile-register/0.2.2/download\"],\n strip_prefix = \"volatile-register-0.2.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.volatile-register-0.2.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__wasi-0.11.1-wasi-snapshot-preview1\",\n sha256 = \"ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/wasi/0.11.1+wasi-snapshot-preview1/download\"],\n strip_prefix = \"wasi-0.11.1+wasi-snapshot-preview1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.wasi-0.11.1+wasi-snapshot-preview1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__windows-link-0.2.1\",\n sha256 = \"f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-link/0.2.1/download\"],\n strip_prefix = \"windows-link-0.2.1\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.windows-link-0.2.1.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__windows-sys-0.61.2\",\n sha256 = \"ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/windows-sys/0.61.2/download\"],\n strip_prefix = \"windows-sys-0.61.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.windows-sys-0.61.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zerocopy-0.8.48\",\n sha256 = \"eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy/0.8.48/download\"],\n strip_prefix = \"zerocopy-0.8.48\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zerocopy-0.8.48.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zerocopy-derive-0.8.48\",\n sha256 = \"70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zerocopy-derive/0.8.48/download\"],\n strip_prefix = \"zerocopy-derive-0.8.48\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zerocopy-derive-0.8.48.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zeroize-1.8.2\",\n sha256 = \"b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zeroize/1.8.2/download\"],\n strip_prefix = \"zeroize-1.8.2\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zeroize-1.8.2.bazel\"),\n )\n\n maybe(\n http_archive,\n name = \"rust_crates__zeroize_derive-1.4.3\",\n sha256 = \"85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e\",\n type = \"tar.gz\",\n urls = [\"https://static.crates.io/crates/zeroize_derive/1.4.3/download\"],\n strip_prefix = \"zeroize_derive-1.4.3\",\n build_file = Label(\"@rust_crates//rust_crates:BUILD.zeroize_derive-1.4.3.bazel\"),\n )\n\n return [\n struct(repo=\"rust_crates__aes-0.8.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__aes-gcm-0.10.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__aligned-0.4.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__anyhow-1.0.102\", is_dev_dep = False),\n struct(repo=\"rust_crates__bitfield-0.14.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__bitfield-struct-0.11.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__bitflags-2.11.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__byteorder-1.5.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__cfg-if-1.0.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__cipher-0.4.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__clap-4.6.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__compiler_builtins-0.1.160\", is_dev_dep = False),\n struct(repo=\"rust_crates__cortex-m-0.7.7\", is_dev_dep = False),\n struct(repo=\"rust_crates__ctr-0.9.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__ecdsa-0.16.9\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-hal-1.0.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-hal-async-1.0.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-hal-nb-1.0.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__embedded-io-0.6.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__futures-0.3.32\", is_dev_dep = False),\n struct(repo=\"rust_crates__heapless-0.9.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__hex-0.4.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__hmac-0.12.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__k256-0.13.4\", is_dev_dep = False),\n struct(repo=\"rust_crates__log-0.4.29\", is_dev_dep = False),\n struct(repo=\"rust_crates__memoffset-0.9.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__minijinja-2.19.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__nb-1.1.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__nom-7.1.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__object-0.37.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__p256-0.13.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__p384-0.13.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__paste-1.0.15\", is_dev_dep = False),\n struct(repo=\"rust_crates__proc-macro2-1.0.106\", is_dev_dep = False),\n struct(repo=\"rust_crates__prost-0.13.5\", is_dev_dep = False),\n struct(repo=\"rust_crates__quote-1.0.45\", is_dev_dep = False),\n struct(repo=\"rust_crates__rand_core-0.9.5\", is_dev_dep = False),\n struct(repo=\"rust_crates__riscv-0.12.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__riscv-rt-0.12.2\", is_dev_dep = False),\n struct(repo=\"rust_crates__riscv-semihosting-0.1.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__rustc-demangle-0.1.27\", is_dev_dep = False),\n struct(repo=\"rust_crates__sec1-0.7.3\", is_dev_dep = False),\n struct(repo=\"rust_crates__serde-1.0.228\", is_dev_dep = False),\n struct(repo=\"rust_crates__serde_derive-1.0.228\", is_dev_dep = False),\n struct(repo=\"rust_crates__serde_json5-0.2.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__sha2-0.10.9\", is_dev_dep = False),\n struct(repo=\"rust_crates__sha3-0.10.8\", is_dev_dep = False),\n struct(repo=\"rust_crates__smlang-0.8.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__subtle-2.6.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__syn-1.0.109\", is_dev_dep = False),\n struct(repo=\"rust_crates__syn-2.0.117\", is_dev_dep = False),\n struct(repo=\"rust_crates__thiserror-2.0.18\", is_dev_dep = False),\n struct(repo=\"rust_crates__tock-registers-0.9.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__tokio-1.51.1\", is_dev_dep = False),\n struct(repo=\"rust_crates__tokio-util-0.7.18\", is_dev_dep = False),\n struct(repo=\"rust_crates__ufmt-0.2.0\", is_dev_dep = False),\n struct(repo=\"rust_crates__zerocopy-0.8.48\", is_dev_dep = False),\n struct(repo=\"rust_crates__zeroize-1.8.2\", is_dev_dep = False),\n ]\n" } } }, @@ -5211,6 +5211,54 @@ "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ucd_trie\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_features = [\n \"std\",\n ],\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ucd-trie\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\": [],\n \"@rules_rust//rust/platform:x86_64-apple-darwin\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.7\",\n)\n" } }, + "rust_crates__ufmt-0.2.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "1a64846ec02b57e9108d6469d98d1648782ad6bb150a95a9baac26900bbeab9d", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ufmt/0.2.0/download" + ], + "strip_prefix": "ufmt-0.2.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ufmt\",\n deps = [\n \"@rust_crates__ufmt-write-0.1.0//:ufmt_write\",\n ],\n proc_macro_deps = [\n \"@rust_crates__ufmt-macros-0.3.0//:ufmt_macros\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ufmt\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\": [],\n \"@rules_rust//rust/platform:x86_64-apple-darwin\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.2.0\",\n)\n" + } + }, + "rust_crates__ufmt-macros-0.3.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ufmt-macros/0.3.0/download" + ], + "strip_prefix": "ufmt-macros-0.3.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_proc_macro\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_proc_macro(\n name = \"ufmt_macros\",\n deps = [\n \"@rust_crates__proc-macro2-1.0.106//:proc_macro2\",\n \"@rust_crates__quote-1.0.45//:quote\",\n \"@rust_crates__syn-1.0.109//:syn\",\n ],\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2021\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ufmt-macros\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\": [],\n \"@rules_rust//rust/platform:x86_64-apple-darwin\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.3.0\",\n)\n" + } + }, + "rust_crates__ufmt-write-0.1.0": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "patch_args": [], + "patch_tool": "", + "patches": [], + "remote_patch_strip": 1, + "sha256": "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69", + "type": "tar.gz", + "urls": [ + "https://static.crates.io/crates/ufmt-write/0.1.0/download" + ], + "strip_prefix": "ufmt-write-0.1.0", + "build_file_content": "###############################################################################\n# @generated\n# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To \n# regenerate this file, run the following:\n#\n# bazel mod show_repo 'openprot'\n###############################################################################\n\nload(\"@rules_rust//cargo:defs.bzl\", \"cargo_toml_env_vars\")\n\nload(\"@rules_rust//rust:defs.bzl\", \"rust_library\")\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_rust//crate_universe/private:selects.bzl\", \"selects\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ncargo_toml_env_vars(\n name = \"cargo_toml_env_vars\",\n src = \"Cargo.toml\",\n)\n\nrust_library(\n name = \"ufmt_write\",\n compile_data = glob(\n allow_empty = True,\n include = [\"**\"],\n exclude = [\n \"**/* *\",\n \".tmp_git_root/**/*\",\n \"BUILD\",\n \"BUILD.bazel\",\n \"WORKSPACE\",\n \"WORKSPACE.bazel\",\n ],\n ),\n crate_root = \"src/lib.rs\",\n edition = \"2018\",\n rustc_env_files = [\n \":cargo_toml_env_vars\",\n ],\n rustc_flags = [\n \"--cap-lints=allow\",\n ],\n srcs = glob(\n allow_empty = True,\n include = [\"**/*.rs\"],\n ),\n tags = [\n \"cargo-bazel\",\n \"crate-name=ufmt-write\",\n \"manual\",\n \"noclippy\",\n \"norustfmt\",\n ],\n target_compatible_with = select({\n \"@rules_rust//rust/platform:aarch64-apple-darwin\": [],\n \"@rules_rust//rust/platform:aarch64-unknown-linux-gnu\": [],\n \"@rules_rust//rust/platform:riscv32imc-unknown-none-elf\": [],\n \"@rules_rust//rust/platform:x86_64-apple-darwin\": [],\n \"@rules_rust//rust/platform:x86_64-unknown-linux-gnu\": [],\n \"//conditions:default\": [\"@platforms//:incompatible\"],\n }),\n version = \"0.1.0\",\n)\n" + } + }, "rust_crates__unicode-ident-1.0.24": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { diff --git a/hal/blocking/flash/BUILD.bazel b/hal/blocking/flash/BUILD.bazel new file mode 100644 index 00000000..c871a068 --- /dev/null +++ b/hal/blocking/flash/BUILD.bazel @@ -0,0 +1,43 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "driver", + srcs = [ + "driver.rs", + ], + crate_name = "hal_flash_driver", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//util/error", + "@rust_crates//:zerocopy", + ], +) + +rust_library( + name = "flash", + srcs = [ + "flash.rs", + ], + crate_name = "hal_flash", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + ":driver", + "//util/error", + "//util/io", + "//util/types", + ], +) + +rust_test( + name = "flash_test", + crate = ":hal_flash", + rustc_flags = [ + "-C", + "debug-assertions", + ], +) diff --git a/hal/blocking/flash/driver.rs b/hal/blocking/flash/driver.rs new file mode 100644 index 00000000..28a7c628 --- /dev/null +++ b/hal/blocking/flash/driver.rs @@ -0,0 +1,125 @@ +#![no_std] + +use core::num::NonZero; + +use util_error::ErrorCode; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +pub trait FlashDriver { + const PAGE_SIZE: usize; + const PROGRAM_WINDOW_SIZE: usize; + const MAX_READ_SIZE: usize; + const READ_ALIGNMENT: usize; + const PROGRAM_ALIGNMENT: usize; + + fn size(&self) -> NonZero; + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode>; + fn start_erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode>; + fn start_program(&mut self, start_address: FlashAddress, data: &[u8]) -> Result<(), ErrorCode>; + fn is_busy(&mut self) -> bool; + fn complete_op(&mut self) -> Result<(), ErrorCode>; +} + +#[derive(Default, Clone, Copy, PartialEq, Eq, IntoBytes, Immutable, FromBytes, KnownLayout)] +pub struct FlashAddress { + address: u32, +} + +impl FlashAddress { + /// Constructs a flash address for flash data pages. + pub fn data(address: u32) -> Self { + Self { + address: address & 0x7FFF_FFFF, + } + } + + /// Constructs a flash address for flash info pages. + pub fn info(bank: u32, page: u32, offset: u32) -> Self { + Self { + address: 0x8000_0000 | (bank & 0x7f) << 24 | (page & 0xff) << 16 | (offset & 0xFFFF), + } + } + + /// Returns whether the flash address is an info page address. + pub fn is_info(&self) -> bool { + self.address & 0x8000_0000 != 0 + } + + /// Returns the flash offset. For data pages, this is the flash address. For info pages, this + /// is the offset within the page. + pub fn offset(&self) -> u32 { + if self.is_info() { + self.address & 0xFFFF + } else { + self.address + } + } + + /// Returns the bank of a flash info page (only valid when `is_info` returns true). + pub fn bank(&self) -> u32 { + (self.address >> 24) & 0x7f + } + + /// Returns the page number of a flash info page (only valid when `is_info` returns true). + pub fn page(&self) -> u32 { + (self.address >> 16) & 0xff + } +} + +impl core::ops::Add for FlashAddress { + type Output = Self; + fn add(self, other: usize) -> Self { + let other = other as u32; + if self.is_info() { + let offset = self.offset() + other; + FlashAddress { + address: (self.address & !0xFFFF) | (offset & 0xFFFF), + } + } else { + let offset = self.offset() + other; + FlashAddress::data(offset as u32) + } + } +} + +impl core::ops::AddAssign for FlashAddress { + fn add_assign(&mut self, other: usize) { + let other = other as u32; + if self.is_info() { + let offset = self.offset() + other; + self.address = (self.address & !0xFFFF) | (offset & 0xFFFF); + } else { + let offset = self.offset() + other; + self.address = offset as u32; + } + } +} + +impl core::ops::BitAnd for FlashAddress { + type Output = Self; + fn bitand(self, other: usize) -> Self { + let other = other as u32; + if self.is_info() { + let offset = self.offset() & other; + FlashAddress { + address: (self.address & !0xFFFF) | (offset & 0xFFFF), + } + } else { + let offset = self.offset() & other; + FlashAddress::data(offset as u32) + } + } +} + +impl core::ops::BitAndAssign for FlashAddress { + fn bitand_assign(&mut self, other: usize) { + let other = other as u32; + if self.is_info() { + let offset = self.offset() & other; + self.address = (self.address & !0xFFFF) | (offset & 0xFFFF); + } else { + let offset = self.offset() & other; + self.address = offset as u32; + } + } +} diff --git a/hal/blocking/flash/flash.rs b/hal/blocking/flash/flash.rs new file mode 100644 index 00000000..9fc05224 --- /dev/null +++ b/hal/blocking/flash/flash.rs @@ -0,0 +1,328 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![cfg_attr(not(test), no_std)] + +use core::{cmp::min, num::NonZero}; +pub use hal_flash_driver::FlashAddress; +use hal_flash_driver::FlashDriver; +use util_error::ErrorCode; +use util_io::RandomRead; +use util_types::{Blocking, PowerOf2Usize}; + +pub trait Flash { + /// The size (and alignment) of erase operations. + // By returning a non-zero type, we can prevent divide-by-zero + // panic-handling code from being generated at the call-site. + fn page_size(&self) -> PowerOf2Usize; + + fn size(&self) -> NonZero; + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode>; + fn erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode>; + fn program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), ErrorCode>; + + fn random_reader(&mut self) -> impl RandomRead + where + Self: Sized, + { + FlashRandomReader(self) + } +} +impl Flash for &mut F { + #[inline(always)] + fn page_size(&self) -> PowerOf2Usize { + (**self).page_size() + } + #[inline(always)] + fn size(&self) -> NonZero { + (**self).size() + } + #[inline(always)] + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode> { + (**self).read(start_addr, buf) + } + #[inline(always)] + fn program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), ErrorCode> { + (**self).program(start_addr, data) + } + #[inline(always)] + fn erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode> { + (**self).erase_page(start_addr) + } +} + +/// A trait that can be used to constrain the page-size of the flash. If you +/// just need to read the page size at runtime, use Flash::page_size() instead. +pub trait FlashPageSize { + const PAGE_SIZE: usize; +} + +pub struct BlockingFlash { + pub driver: TDriver, + pub blocking: TBlocking, +} + +impl FlashPageSize + for BlockingFlash +{ + const PAGE_SIZE: usize = TDriver::PAGE_SIZE; +} + +impl Flash for BlockingFlash { + fn page_size(&self) -> PowerOf2Usize { + const { PowerOf2Usize::new(TDriver::PAGE_SIZE).unwrap() } + } + fn size(&self) -> NonZero { + self.driver.size() + } + fn read(&mut self, start_addr: FlashAddress, mut buf: &mut [u8]) -> Result<(), ErrorCode> { + let mut addr = start_addr; + let align_skip_len = (addr & (TDriver::READ_ALIGNMENT - 1)).offset() as usize; + if (align_skip_len) != 0 { + assert!(TDriver::READ_ALIGNMENT <= 16); + let mut tmp = [0_u8; 16]; + let prefix_count = min(TDriver::READ_ALIGNMENT - align_skip_len, buf.len()); + self.driver + .read(addr & !(TDriver::READ_ALIGNMENT - 1), &mut tmp)?; + buf[..prefix_count].copy_from_slice(&tmp[align_skip_len..][..prefix_count]); + buf = &mut buf[prefix_count..]; + addr += prefix_count; + } + for buf_chunk in buf.chunks_mut(TDriver::MAX_READ_SIZE) { + self.driver.read(addr, buf_chunk)?; + addr += buf_chunk.len(); + } + Ok(()) + } + fn erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode> { + self.driver.start_erase_page(start_addr)?; + self.blocking.wait_for_notification(); + self.driver.complete_op() + } + fn program(&mut self, start_addr: FlashAddress, mut data: &[u8]) -> Result<(), ErrorCode> { + assert!( + TDriver::PROGRAM_WINDOW_SIZE.count_ones() == 1, + "TDriver::PROGRAM_WINDOW_SIZE must be a power of 2" + ); + let window_mask = TDriver::PROGRAM_WINDOW_SIZE - 1; + let mut addr = start_addr; + while !data.is_empty() { + let chunk = &data[..min( + data.len(), + TDriver::PROGRAM_WINDOW_SIZE - ((addr & window_mask).offset() as usize), + )]; + self.driver.start_program(addr, chunk)?; + self.blocking.wait_for_notification(); + self.driver.complete_op()?; + data = &data[chunk.len()..]; + addr += chunk.len(); + } + Ok(()) + } +} + +struct FlashRandomReader<'a, F: Flash>(&'a mut F); +impl RandomRead for FlashRandomReader<'_, F> { + fn read(&mut self, start_addr: usize, buf: &mut [u8]) -> Result<(), ErrorCode> { + self.0.read(FlashAddress::data(start_addr as u32), buf) + } + fn size(&self) -> usize { + self.0.size().get() + } +} + +#[cfg(test)] +mod test { + use super::*; + + pub struct FakeBlocking(); + impl Blocking for FakeBlocking { + fn wait_for_notification(&self) {} + } + + #[derive(Clone)] + pub struct FakeFlashDriver { + pub data: Vec, + pub check_err_result: Result<(), ErrorCode>, + } + impl FakeFlashDriver { + pub fn new(data: Vec) -> Self { + Self { + data, + check_err_result: Ok(()), + } + } + } + impl FlashDriver for FakeFlashDriver { + const PAGE_SIZE: usize = 2048; + const PROGRAM_WINDOW_SIZE: usize = 64; + const MAX_READ_SIZE: usize = 4096; + const READ_ALIGNMENT: usize = 4; + const PROGRAM_ALIGNMENT: usize = 8; + + fn size(&self) -> NonZero { + NonZero::new(self.data.len()).unwrap() + } + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode> { + assert!(start_addr.checked_add(buf.len()).unwrap() <= self.data.len()); + assert!(buf.len() <= Self::MAX_READ_SIZE); + assert!(start_addr % Self::READ_ALIGNMENT == 0); + buf.copy_from_slice(&self.data[start_addr..][..buf.len()]); + Ok(()) + } + fn start_erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode> { + assert!(start_addr.checked_add(Self::PAGE_SIZE).unwrap() <= self.data.len()); + assert!(start_addr % Self::PAGE_SIZE == 0); + self.data[start_addr..][..Self::PAGE_SIZE].fill(0xff); + Ok(()) + } + fn start_program( + &mut self, + start_addr: FlashAddress, + data: &[u8], + ) -> Result<(), ErrorCode> { + let start_addr = start_addr.offset() as usize; + assert!(start_addr.checked_add(data.len()).unwrap() <= self.data.len()); + assert!( + data.len() <= Self::PROGRAM_WINDOW_SIZE, + "Program window violation" + ); + let end_addr = start_addr.wrapping_add(data.len()); + assert!( + start_addr / Self::PROGRAM_WINDOW_SIZE + == (end_addr - 1) / Self::PROGRAM_WINDOW_SIZE, + "Program window violation" + ); + for (dest, src) in self.data[start_addr..end_addr].iter_mut().zip(data) { + *dest &= *src; + } + Ok(()) + } + fn is_busy(&mut self) -> bool { + false + } + fn complete_op(&mut self) -> Result<(), ErrorCode> { + self.check_err_result + } + } + + #[test] + #[should_panic(expected = "Program window violation")] + pub fn test_fake_flash_program_window_violation_0() { + let mut flash_driver = FakeFlashDriver::new((0..255).collect()); + flash_driver.start_program(0x3c, &[0x42; 5]).unwrap(); + } + + #[test] + #[should_panic(expected = "Program window violation")] + pub fn test_fake_flash_program_window_violation_1() { + let mut flash_driver = FakeFlashDriver::new((0..255).collect()); + flash_driver.start_program(0x0, &[0; 68]).unwrap(); + } + + #[test] + pub fn test_fake_flash_full_program_window() { + let mut flash_driver = FakeFlashDriver::new((0..255).collect()); + flash_driver.start_program(0x40, &[0; 0x40]).unwrap(); + assert_eq!(flash_driver.data[0x40..0x80], [0; 0x40]); + } + + #[test] + pub fn test_size() { + let flash_driver = FakeFlashDriver::new((0..255).collect()); + let mut flash = BlockingFlash { + driver: flash_driver, + blocking: FakeBlocking(), + }; + + assert_eq!(flash.size().get(), 255); + assert_eq!(flash.random_reader().size(), 255); + } + + #[test] + pub fn test_read() { + let flash_driver = FakeFlashDriver::new((0..255).collect()); + + let mut flash = BlockingFlash { + driver: flash_driver, + blocking: FakeBlocking(), + }; + + let mut buf = [0_u8; 4]; + flash.read(0, &mut buf).unwrap(); + assert_eq!(buf, [0_u8, 1, 2, 3]); + + let mut buf = [0_u8; 4]; + flash.read(1, &mut buf).unwrap(); + assert_eq!(buf, [1, 2, 3, 4]); + + let mut buf = [0_u8; 4]; + flash.read(2, &mut buf).unwrap(); + assert_eq!(buf, [2, 3, 4, 5]); + + let mut buf = [0_u8; 4]; + flash.random_reader().read(2, &mut buf).unwrap(); + assert_eq!(buf, [2, 3, 4, 5]); + + let mut buf = [0_u8; 4]; + flash.read(3, &mut buf).unwrap(); + assert_eq!(buf, [3, 4, 5, 6]); + + let mut buf = [0_u8; 6]; + flash.read(3, &mut buf).unwrap(); + assert_eq!(buf, [3, 4, 5, 6, 7, 8]); + + for i in 0..32 { + let mut buf = [0_u8; 32]; + flash.read(0, &mut buf[..i]).unwrap(); + assert_eq!(&buf[..i], &flash.driver.data[..i]); + } + + for i in 0..32 { + let mut buf = [0_u8; 32]; + flash.read(32 - i, &mut buf[..i]).unwrap(); + assert_eq!(&buf[..i], &flash.driver.data[32 - i..32]); + } + } + + #[test] + pub fn test_erase() { + let mut flash = BlockingFlash { + driver: FakeFlashDriver::new(vec![0x42; 0x4000]), + blocking: FakeBlocking(), + }; + flash.erase_page(0x0800).unwrap(); + assert_eq!(flash.driver.data[0x0000..0x0800], [0x42; 0x0800]); + assert_eq!(flash.driver.data[0x0800..0x1000], [0xff; 0x0800]); + assert_eq!(flash.driver.data[0x1000..0x4000], [0x42; 0x3000]); + + flash.erase_page(0x3000).unwrap(); + assert_eq!(flash.driver.data[0x0000..0x0800], [0x42; 0x0800]); + assert_eq!(flash.driver.data[0x0800..0x1000], [0xff; 0x0800]); + assert_eq!(flash.driver.data[0x1000..0x3000], [0x42; 0x2000]); + assert_eq!(flash.driver.data[0x3000..0x3800], [0xff; 0x0800]); + assert_eq!(flash.driver.data[0x3800..0x4000], [0x42; 0x0800]); + } + + #[test] + pub fn test_program() { + let mut flash = BlockingFlash { + driver: FakeFlashDriver::new(vec![0xff; 8192]), + blocking: FakeBlocking(), + }; + + flash + .program(0x3c, &[0x10, 0x11, 0x12, 0x13, 0x14, 0x15]) + .unwrap(); + assert_eq!( + flash.driver.data[0x38..0x44], + [0xff, 0xff, 0xff, 0xff, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0xff, 0xff] + ); + + flash.program(0x40, &[0x24, 0x25]).unwrap(); + assert_eq!( + flash.driver.data[0x38..0x44], + [0xff, 0xff, 0xff, 0xff, 0x10, 0x11, 0x12, 0x13, 0x04, 0x05, 0xff, 0xff] + ); + } +} diff --git a/hal/blocking/usb/BUILD.bazel b/hal/blocking/usb/BUILD.bazel new file mode 100644 index 00000000..e9989f4c --- /dev/null +++ b/hal/blocking/usb/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "hal_usb", + srcs = [ + "descriptor.rs", + "driver.rs", + "lib.rs", + ], + crate_name = "hal_usb", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "@rust_crates//:aligned", + "@rust_crates//:ufmt", + "@rust_crates//:zerocopy", + ], +) + +rust_test( + name = "hal_usb_test", + crate = ":hal_usb", + rustc_flags = [ + "-C", + "debug-assertions", + ], +) diff --git a/hal/blocking/usb/descriptor.rs b/hal/blocking/usb/descriptor.rs new file mode 100644 index 00000000..b8ee0154 --- /dev/null +++ b/hal/blocking/usb/descriptor.rs @@ -0,0 +1,923 @@ +//! USB descriptor structures and serialization. +//! +//! This module provides the tools to define and serialize standard USB +//! descriptors, including Device, Configuration, Interface, and Endpoint +//! descriptors. + +use aligned::Aligned; +use aligned::A4; +use ufmt::uWrite; + +/// USB Audio class code. +pub const USB_CLASS_AUDIO: u8 = 0x01; +/// USB Communications and CDC Control class code. +pub const USB_CLASS_COMMUNIATIONS: u8 = 0x02; +/// USB HID (Human Interface Device) class code. +pub const USB_CLASS_HID: u8 = 0x03; +/// USB Physical class code. +pub const USB_CLASS_PHYSICAL: u8 = 0x05; +/// USB Image class code. +pub const USB_CLASS_IMAGE: u8 = 0x06; +/// USB Printer class code. +pub const USB_CLASS_PRINTER: u8 = 0x07; +/// USB Mass Storage class code. +pub const USB_CLASS_MASS_STORAGE: u8 = 0x08; +/// USB Hub class code. +pub const USB_CLASS_HUB: u8 = 0x09; +/// USB CDC-Data class code. +pub const USB_CLASS_CDC_DATA: u8 = 0x0a; +/// USB Smart Card class code. +pub const USB_CLASS_SMART_CARD: u8 = 0x0b; +/// USB Content Security class code. +pub const USB_CLASS_CONTENT_SECURITY: u8 = 0x0d; +/// USB Video class code. +pub const USB_CLASS_VIDEO: u8 = 0x0e; +/// USB Personal Healthcare class code. +pub const USB_CLASS_PERSONAL_HEALTHCARE: u8 = 0x0f; +/// USB Audio/Video class code. +pub const USB_CLASS_AUDIO_VIDEO: u8 = 0x10; +/// USB Billboard class code. +pub const USB_CLASS_BILLBOARD: u8 = 0x11; +/// USB Type-C Bridge class code. +pub const USB_CLASS_USB_TYPEC_BRIDGE: u8 = 0x12; +/// USB Bulk Display class code. +pub const USB_CLASS_BULK_DISPLAY: u8 = 0x13; +/// USB MCTP class code. +pub const USB_CLASS_MCTP: u8 = 0x14; +/// USB I3C class code. +pub const USB_CLASS_I3C: u8 = 0x3c; +/// USB Diagnostic Device class code. +pub const USB_CLASS_DIAGNOSTIC_DEVICE: u8 = 0xdc; +/// USB Wireless Controller class code. +pub const USB_CLASS_WIRELESS_CONTROLLER: u8 = 0xe0; +/// USB Miscellaneous class code. +pub const USB_CLASS_MISC: u8 = 0xef; +/// USB Application Specific class code. +pub const USB_CLASS_APPLICATION_SPECIFIC: u8 = 0xfe; +/// USB Vendor Specific class code. +pub const USB_CLASS_VENDOR: u8 = 0xff; + +/// DFU (Device Firmware Upgrade) subclass code. +pub const USB_SUBCLASS_APPLICATION_SPECIFIC_DFU: u8 = 0x01; + +/// DFU Runtime Mode protocol code. +pub const USB_PROTOCOL_APPLICATION_SPECIFIC_DFU_RUNTIME_MODE: u8 = 0x01; +/// DFU Mode protocol code. +pub const USB_PROTOCOL_APPLICATION_SPECIFIC_DFU_DFU_MODE: u8 = 0x02; + +use crate::DescriptorType; +use crate::Direction; + +/// A handle to a USB string descriptor. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(transparent)] +pub struct StringHandle(pub u8); +impl StringHandle { + /// Indicates that no string descriptor is provided. + pub const NONE: Self = StringHandle(0); +} + +/// A standard USB device descriptor. +#[derive(Clone, Copy)] +pub struct DeviceDescriptor { + /// The class of the device. + pub device_class: DeviceClass, + /// The subclass of the device. + pub device_sub_class: u8, + /// The protocol used by the device. + pub device_protocol: u8, + /// Maximum packet size for Endpoint 0. + pub max_packet_size: u8, + /// Vendor ID assigned by the USB-IF. + pub vendor_id: u16, + /// Product ID assigned by the manufacturer. + pub product_id: u16, + /// Device release number (in binary-coded decimal). + pub device_release_num: u16, + /// Handle for the manufacturer string descriptor. + pub manufacturer: StringHandle, + /// Handle for the product string descriptor. + pub product: StringHandle, + /// Handle for the serial number string descriptor. + pub serial_num: StringHandle, +} +impl DeviceDescriptor { + const SIZE: usize = 18; + + #[allow(dead_code)] + pub(crate) const fn total_size(&self) -> usize { + Self::SIZE + } + + /// Serializes the device descriptor into a byte array. + #[allow(clippy::identity_op)] + pub const fn serialize(&self) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + + // sizeof descriptor + buf[0] = 18; + // bDescriptorType = Device + buf[1] = 1; + // USB version 2.0 + buf[2] = 0x00; + buf[3] = 0x02; + + buf[4] = self.device_class.0; + buf[5] = self.device_sub_class; + buf[6] = self.device_protocol; + buf[7] = self.max_packet_size; + + buf[8] = ((self.vendor_id & 0x00ff) >> 0) as u8; + buf[9] = ((self.vendor_id & 0xff00) >> 8) as u8; + + buf[10] = ((self.product_id & 0x00ff) >> 0) as u8; + buf[11] = ((self.product_id & 0xff00) >> 8) as u8; + + buf[12] = ((self.device_release_num & 0x00ff) >> 0) as u8; + buf[13] = ((self.device_release_num & 0xff00) >> 8) as u8; + + buf[14] = self.manufacturer.0; + buf[15] = self.product.0; + buf[16] = self.serial_num.0; + + // num configurations + buf[17] = 1; + buf + } +} + +/// A standard USB configuration descriptor. +#[derive(Clone, Copy)] +pub struct ConfigDescriptor { + /// The configuration value for this configuration. + pub configuration_value: u8, + /// Maximum power consumption in 2 mA units. + pub max_power: u8, + /// Indicates if the device is self-powered. + pub self_powered: bool, + /// Indicates if the device supports remote wakeup. + pub remote_wakeup: bool, + /// List of interfaces included in this configuration. + pub interfaces: &'static [InterfaceDescriptor], +} + +impl ConfigDescriptor { + const SIZE: usize = 9; + + /// Returns the total size of the configuration descriptor, including + /// all interfaces and endpoints. + pub const fn total_size(&self) -> usize { + let mut result = Self::SIZE; + let mut i = 0; + while i < self.interfaces.len() { + result += self.interfaces[i].total_size(); + i += 1; + } + result + } + + /// Serializes the configuration descriptor and its children into a byte array. + #[allow(clippy::identity_op)] + pub const fn serialize(&self) -> [u8; RESULT_SIZE] { + assert!(self.total_size() == RESULT_SIZE); + let mut buf = [0u8; RESULT_SIZE]; + + // alternates don't count towards total + // as interfaces numbers are per spec monotonically increasing, we can use that as the count + let mut uniq_interface_count = 0; + let mut i = 0; + while i < self.interfaces.len() { + if self.interfaces[i].interface_number + 1 > uniq_interface_count { + uniq_interface_count = self.interfaces[i].interface_number + 1 + } + i += 1; + } + + // sizeof descriptor + buf[0] = 9; + // bDescriptorType = Configuration + buf[1] = 2; + buf[2] = ((RESULT_SIZE & 0x00ff) >> 0) as u8; + buf[3] = ((RESULT_SIZE & 0xff00) >> 8) as u8; + buf[4] = uniq_interface_count; + buf[5] = self.configuration_value; + // iConfiguration + buf[6] = 0; + buf[7] = (1 << 7) | // must be 1 (USB 1.0 bus powered) + if self.self_powered { 1 << 6 } else { 0 } | + if self.remote_wakeup { 1 << 5 } else { 0 }; + buf[8] = self.max_power; + + let mut offset = 9; + + let mut i = 0; + while i < self.interfaces.len() { + let (iface_buf, iface_buf_len) = self.interfaces[i].serialize::(); + let mut iface_offset = 0; + while iface_offset < iface_buf_len { + buf[offset] = iface_buf[iface_offset]; + iface_offset += 1; + offset += 1; + } + i += 1; + } + buf + } +} + +/// A standard USB interface descriptor. +#[derive(Clone, Copy)] +pub struct InterfaceDescriptor { + /// Handle for the interface name string descriptor. + pub name: StringHandle, + /// The alternate setting for this interface. + pub alternate_setting: u8, + /// The interface number. + pub interface_number: u8, + /// The interface class. + pub interface_class: u8, + /// The interface subclass. + pub interface_sub_class: u8, + /// The interface protocol. + pub interface_protocol: u8, + /// List of class-specific functional descriptors. + pub func_descs: &'static [FunctionalDescriptor], + /// List of endpoints used by this interface. + pub endpoints: &'static [EndpointDescriptor], +} +impl InterfaceDescriptor { + const SIZE: usize = 9; + + pub(crate) const fn total_size(&self) -> usize { + let mut result = Self::SIZE; + let mut i = 0; + while i < self.func_descs.len() { + result += self.func_descs[i].total_size(); + i += 1; + } + let mut i = 0; + while i < self.endpoints.len() { + result += self.endpoints[i].total_size(); + i += 1; + } + result + } + /// Serializes the interface descriptor and its children. + pub const fn serialize(&self) -> ([u8; RESULT_SIZE], usize) { + assert!(RESULT_SIZE >= self.total_size()); + + let mut buf = [0u8; RESULT_SIZE]; + + // sizeof descriptor + buf[0] = 9; + // bDescriptorType = Interface + buf[1] = 4; + buf[2] = self.interface_number; + buf[3] = self.alternate_setting; + buf[4] = self.endpoints.len() as u8; + buf[5] = self.interface_class; + buf[6] = self.interface_sub_class; + buf[7] = self.interface_protocol; + // iInterface: Index of string descriptor describing this interface + buf[8] = self.name.0; + let mut offset = 9; + + let mut i = 0; + while i < self.func_descs.len() { + self.func_descs[i].serialize(&mut buf, offset); + offset += self.func_descs[i].total_size(); + i += 1; + } + + let mut i = 0; + while i < self.endpoints.len() { + let ep_buf = self.endpoints[i].serialize(i as u8); + let mut ep_offset = 0; + while ep_offset < ep_buf.len() { + buf[offset] = ep_buf[ep_offset]; + offset += 1; + ep_offset += 1; + } + i += 1; + } + (buf, offset) + } +} + +/// A standard USB endpoint descriptor. +#[derive(Clone, Copy)] +pub struct EndpointDescriptor { + /// The data direction of the endpoint. + pub direction: Direction, + /// The endpoint number (0-15). + pub endpoint_num: u8, + /// The transfer type of the endpoint. + pub transfer_type: TransferType, + /// Maximum packet size for this endpoint. + pub max_packet_size: u16, + /// Polling interval (for interrupt and isochronous endpoints). + pub interval: u8, +} +impl EndpointDescriptor { + const SIZE: usize = 7; + + pub(crate) const fn total_size(&self) -> usize { + Self::SIZE + } + + #[allow(clippy::identity_op)] + const fn serialize(&self, _index: u8) -> [u8; Self::SIZE] { + let mut buf = [0u8; Self::SIZE]; + + // sizeof descriptor + buf[0] = Self::SIZE as u8; + // bDescriptorType = endpoint + buf[1] = 5; + buf[2] = self.endpoint_num & 0x7 + | match self.direction { + Direction::HostToDevice => 0, + Direction::DeviceToHost => 1 << 7, + }; + buf[3] = match self.transfer_type { + TransferType::Control => 0, + TransferType::Isochronous(sync_type, usage_type) => { + 1 | match sync_type { + SynchronizationType::None => 0 << 2, + SynchronizationType::Asynchronous => 1 << 2, + SynchronizationType::Adaptive => 2 << 2, + SynchronizationType::Synchronous => 3 << 3, + } | match usage_type { + UsageType::DataEndpoint => 0 << 4, + UsageType::FeedbackEndpoint => 1 << 4, + UsageType::ExplicitFeedbackDataEndpoint => 2 << 4, + } + } + TransferType::Bulk => 2, + TransferType::Interrupt => 3, + }; + + buf[4] = ((self.max_packet_size & 0x00ff) >> 0) as u8; + buf[5] = ((self.max_packet_size & 0xff00) >> 8) as u8; + buf[6] = self.interval; + buf + } +} + +/// A standard USB String Descriptor 0 (listing supported languages). +#[derive(Clone, Copy)] +pub struct StringDescriptor0 { + /// List of supported LANGIDs. + pub langs: &'static [u16], +} +impl StringDescriptor0 { + /// Returns the total size of the descriptor. + pub const fn total_size(&self) -> usize { + 2 + core::mem::size_of_val(self.langs) + } + + /// Serializes the language list into a byte array. + #[allow(clippy::identity_op)] + pub const fn serialize(&self) -> [u8; RESULT_SIZE] { + assert!(RESULT_SIZE == self.total_size()); + assert!(self.total_size() <= (u8::MAX as usize)); + + let mut buf = [0u8; RESULT_SIZE]; + // sizeof descriptor + buf[0] = self.total_size() as u8; + // bDescriptorType = String + buf[1] = 3; + + let mut offset = 2; + let mut i = 0; + while i < self.langs.len() { + let bytes = self.langs[i].to_le_bytes(); + buf[offset + 0] = bytes[0]; + buf[offset + 1] = bytes[1]; + i += 1; + offset += 2; + } + buf + } +} + +/// USB transfer type. +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +pub enum TransferType { + /// Control transfer. + Control, + /// Isochronous transfer. + Isochronous(SynchronizationType, UsageType), + /// Bulk transfer. + Bulk, + /// Interrupt transfer. + Interrupt, +} +impl TransferType { + #[allow(dead_code)] + fn as_eptyp(self) -> u32 { + match self { + TransferType::Control => 0, + TransferType::Isochronous(_, _) => 1, + TransferType::Bulk => 2, + TransferType::Interrupt => 3, + } + } +} + +/// Isochronous synchronization type. +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +pub enum SynchronizationType { + None = 0, + Asynchronous = 1, + Adaptive = 2, + Synchronous = 3, +} + +/// Isochronous usage type. +#[derive(Clone, Copy, Debug)] +#[allow(dead_code, clippy::enum_variant_names)] +pub enum UsageType { + DataEndpoint, + FeedbackEndpoint, + ExplicitFeedbackDataEndpoint, +} + +/// USB device class code. +#[derive(Clone, Copy)] +pub struct DeviceClass(pub u8); +impl DeviceClass { + /// Class is specified at the interface level. + pub const SPECIFIED_BY_INTERFACE: Self = Self(0x00); + /// CDC (Communication Device Class). + pub const COMMUNICATIONS_AND_CDC: Self = Self(0x02); + /// Hub device. + pub const HUB: Self = Self(0x09); + /// Billboard device. + pub const BILLBOARD: Self = Self(0x11); + /// Diagnostic device. + pub const DIAGNOSTIC_DEVICE: Self = Self(0x3c); + /// Miscellaneous device. + pub const MISCELLANEOUS: Self = Self(0xef); + /// Vendor-specified device class. + pub const VENDOR_SPECIFIED: Self = Self(0xff); +} +impl From for u8 { + fn from(val: DeviceClass) -> Self { + val.0 + } +} + +/// A statically-allocated USB string descriptor. +pub struct StringDescriptor(Aligned); + +impl StringDescriptor { + /// Creates a string descriptor from an ASCII string at compile-time. + pub const fn const_from_ascii(s: &str) -> Self { + assert!(BYTE_LEN <= (u8::MAX as usize)); + assert!(s.len() * 2 + 2 == BYTE_LEN); + let mut result = [0u8; BYTE_LEN]; + result[0] = BYTE_LEN as u8; + result[1] = 0x03; // DescriptorType string + + let s = s.as_bytes(); + let mut i = 0; + while i < s.len() { + if s[i] >= 0x80 { + panic!("ascii characters only"); + } + result[2 + i * 2] = s[i]; + i += 1; + } + StringDescriptor(Aligned(result)) + } + /// Returns a reference to the string descriptor. + pub const fn as_ref(&self) -> StringDescriptorRef<'_> { + StringDescriptorRef(&self.0) + } +} + +/// A reference to an aligned USB string descriptor. +#[derive(Clone, Copy)] +pub struct StringDescriptorRef<'a>(pub &'a Aligned); +impl<'a> StringDescriptorRef<'a> { + /// Returns the descriptor as a byte slice. + pub const fn as_bytes(self) -> &'a Aligned { + self.0 + } +} + +/// Macro for easily creating static string descriptors. +#[macro_export] +macro_rules! string_descriptor { + ($s:expr) => { + $crate::StringDescriptor::<{ $s.len() * 2 + 2 }>::const_from_ascii($s) + }; +} + +/// Descriptor generation error. +#[derive(Debug)] +pub enum DescriptorErr { + /// Buffer is too small. + Overflow, + /// Invalid encoding. + Encoding, +} + +/// Generates a UTF-16 hex-encoded string descriptor from a byte slice. +#[inline(always)] +pub fn hex_utf16_descriptor(dest: &mut [u8], src: &[u8]) -> Result { + const { assert!(cfg!(target_endian = "little")) }; + const HEX_CHARS: [u8; 16] = *b"0123456789abcdef"; + let total_len = src.len() * 4 + 2; + if dest.len() < total_len || total_len > 255 { + return Err(DescriptorErr::Overflow); + } + dest[0] = total_len as u8; + dest[1] = DescriptorType::STRING.0; + + let mut i = 2; + for src_byte in src.iter() { + dest[i] = HEX_CHARS[usize::from(*src_byte >> 4)]; + dest[i + 1] = 0; + dest[i + 2] = HEX_CHARS[usize::from(*src_byte & 0xf)]; + dest[i + 3] = 0; + i += 4; + } + Ok(total_len) +} + +/// Generates an aligned UTF-16 hex-encoded string descriptor. +#[inline(always)] +pub fn hex_utf16_descriptor_aligned<'a>( + dest: &'a mut Aligned, + src: &[u8], +) -> Result, DescriptorErr> { + let len = hex_utf16_descriptor(dest, src)?; + Ok(StringDescriptorRef(&dest[..len])) +} + +/// Utility for dynamically writing content into a USB string descriptor. +pub struct StringDescriptorWritter<'a> { + buf: &'a mut Aligned, + index: usize, +} +impl<'a> StringDescriptorWritter<'a> { + /// Creates a new writer using the provided buffer. + pub fn new(buf: &'a mut Aligned) -> Result { + if buf.len() < 2 || buf.len() > 2 + 255 { + return Err(DescriptorErr::Overflow); + } + *buf.get_mut(1).unwrap() = DescriptorType::STRING.0; + Ok(StringDescriptorWritter { buf, index: 2 }) + } + /// Finalizes the descriptor and returns a reference to it. + pub fn finalize(self) -> Result, DescriptorErr> { + *self.buf.get_mut(0).ok_or(DescriptorErr::Overflow)? = + u8::try_from(self.index).map_err(|_| DescriptorErr::Overflow)?; + + if self.index > self.buf.len() { + return Err(DescriptorErr::Overflow); + } + Ok(StringDescriptorRef(&self.buf[..self.index])) + } +} +impl uWrite for StringDescriptorWritter<'_> { + type Error = core::fmt::Error; + + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + let bytes = s.as_bytes(); + let remaining_buf = self.buf.get_mut(self.index..).ok_or(core::fmt::Error)?; + + if remaining_buf.len() < bytes.len() * 2 { + return Err(core::fmt::Error); + } + + for &b in bytes { + if b >= 0x80 { + return Err(core::fmt::Error); + } + *self.buf.get_mut(self.index).ok_or(core::fmt::Error)? = b; + *self.buf.get_mut(self.index + 1).ok_or(core::fmt::Error)? = 0; + self.index += 2; + } + + Ok(()) + } +} + +#[cfg(test)] +mod test_string_descriptor_writter { + use aligned::Aligned; + use aligned::A4; + use core::ops::Deref; + use ufmt::uwrite; + + use crate::StringDescriptorWritter; + + #[test] + fn works() { + let mut buf = Aligned::([0u8; 30]); + let mut writter = StringDescriptorWritter::new(&mut buf).unwrap(); + uwrite!(writter, "Hello").unwrap(); + uwrite!(writter, " ").unwrap(); + uwrite!(writter, "World").unwrap(); + let result = writter.finalize().unwrap(); + assert_eq!( + result.as_bytes().deref(), + &[ + 24, 3, b'H', 0, b'e', 0, b'l', 0, b'l', 0, b'o', 0, b' ', 0, b'W', 0, b'o', 0, + b'r', 0, b'l', 0, b'd', 0 + ] + ); + } + + #[test] + fn too_small_buffer() { + let mut buf = Aligned::([0u8; 1]); + assert!(StringDescriptorWritter::new(&mut buf).is_err()); + } + + #[test] + fn too_big_buffer() { + let mut buf = Aligned::([0u8; 258]); + assert!(StringDescriptorWritter::new(&mut buf).is_err()); + } + + #[test] + fn too_small_to_fit() { + let mut buf = Aligned::([0u8; 12]); + let mut writter = StringDescriptorWritter::new(&mut buf).unwrap(); + uwrite!(writter, "Hello").unwrap(); + assert!(uwrite!(writter, " ").is_err()); + } + + #[test] + fn non_ascii_char() { + let mut buf = Aligned::([0u8; 20]); + let mut writter = StringDescriptorWritter::new(&mut buf).unwrap(); + assert!(uwrite!(writter, "Héllö").is_err()); + } +} + +/// A DFU functional descriptor. +#[derive(Clone, Copy)] +pub struct DfuFunctionalDescriptor { + /// New firmware can be received from the host + pub can_download: bool, + /// Current firmware can be sent back to the host + pub can_upload: bool, + /// Device can still communicate with the host after the manifestation phase. + pub manifestation_tolerant: bool, + /// Device will detach from the USB bus autonomously after receiving + /// DFU_DETACH; the host does not need to explicitly issue a bus reset. + pub will_detach: bool, + /// Timeout the device will wait to be reset by host after receiving DFU_DETACH. + pub detach_timeout_ms: u16, + /// The number of bytes the device can receive per control request. + pub transfer_size: u16, +} +impl DfuFunctionalDescriptor { + /// Returns the total size of the descriptor. + pub const fn total_size(&self) -> usize { + 9 + } + /// Serializes the DFU functional descriptor. + pub const fn serialize(&self, dest: &mut [u8], offset: usize) { + const fn bit(index: u8, val: bool) -> u8 { + (if val { 1 } else { 0 }) << index + } + const fn copy_u16(dest: &mut [u8], index: usize, val: u16) { + let bytes = val.to_le_bytes(); + dest[index] = bytes[0]; + dest[index + 1] = bytes[1]; + } + // sizeof descriptor + dest[offset] = 9; + // bDescriptorType = DFU Functional + dest[offset + 1] = 0x21; + // bmAttributes + dest[offset + 2] = bit(0, self.can_download) + | bit(1, self.can_upload) + | bit(2, self.manifestation_tolerant) + | bit(3, self.will_detach); + copy_u16(dest, offset + 3, self.detach_timeout_ms); + copy_u16(dest, offset + 5, self.transfer_size); + // bcdDFUVersion + copy_u16(dest, offset + 7, 0x0100); + } +} + +/// A raw class-specific functional descriptor. +#[derive(Clone, Copy)] +pub struct RawFunctionalDescriptor { + /// The type of the descriptor. + pub descriptor_type: u8, + /// The raw content length. + pub len: u8, + /// The raw content of the descriptor. + pub content: [u8; 16], +} +impl RawFunctionalDescriptor { + /// Returns the total size of the descriptor. + pub const fn total_size(&self) -> usize { + (self.len as usize) + 2 + } + /// Serializes the raw functional descriptor. + pub const fn serialize(&self, dest: &mut [u8], offset: usize) { + dest[offset] = self.total_size() as u8; + dest[offset + 1] = self.descriptor_type; + let mut i = 0; + while i < (self.len as usize) { + dest[offset + 2 + i] = self.content[i]; + i += 1; + } + } +} + +/// Represents a class-specific functional descriptor. +#[derive(Clone, Copy)] +pub enum FunctionalDescriptor { + /// DFU functional descriptor. + Dfu(DfuFunctionalDescriptor), + /// Raw class-specific functional descriptor. + Raw(RawFunctionalDescriptor), +} + +impl FunctionalDescriptor { + /// Creates a raw functional descriptor. + pub const fn raw(descriptor_type: u8, content: &[u8]) -> Self { + let mut buf = [0u8; 16]; + let mut i = 0; + while i < content.len() { + buf[i] = content[i]; + i += 1; + } + Self::Raw(RawFunctionalDescriptor { + descriptor_type, + len: content.len() as u8, + content: buf, + }) + } + /// Returns the total size of the descriptor. + pub const fn total_size(&self) -> usize { + match self { + Self::Dfu(dfu) => dfu.total_size(), + Self::Raw(raw) => raw.total_size(), + } + } + /// Serializes the functional descriptor. + #[allow(clippy::identity_op)] + pub const fn serialize(&self, dest: &mut [u8], offset: usize) { + assert!(offset + self.total_size() <= dest.len()); + match self { + Self::Dfu(dfu) => dfu.serialize(dest, offset), + Self::Raw(raw) => raw.serialize(dest, offset), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + const INTERFACE_NAME_HANDLE: StringHandle = StringHandle(5); + + const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { + configuration_value: 1, + max_power: 250, + self_powered: false, + remote_wakeup: false, + interfaces: &[InterfaceDescriptor { + name: INTERFACE_NAME_HANDLE, + interface_number: 0, + alternate_setting: 0, + interface_class: 0xff, + interface_sub_class: 0xff, + interface_protocol: 0xff, + func_descs: &[], + endpoints: &[ + EndpointDescriptor { + direction: Direction::DeviceToHost, + endpoint_num: 1, + transfer_type: TransferType::Bulk, + max_packet_size: 64, + interval: 0, + }, + EndpointDescriptor { + direction: Direction::HostToDevice, + endpoint_num: 2, + transfer_type: TransferType::Bulk, + max_packet_size: 64, + interval: 0, + }, + ], + }], + }; + const CONFIG_DESC_RAW: [u8; CONFIG_DESC.total_size()] = CONFIG_DESC.serialize(); + + #[test] + fn test_config_desc() { + assert_eq!( + &CONFIG_DESC_RAW, + &[ + 0x09, 0x02, 0x20, 0x00, 0x01, 0x01, 0x00, 0x80, 0xfa, 0x09, 0x04, 0x00, 0x00, 0x02, + 0xff, 0xff, 0xff, 0x05, 0x07, 0x05, 0x81, 0x02, 0x40, 0x00, 0x00, 0x07, 0x05, 0x02, + 0x02, 0x40, 0x00, 0x00 + ] + ) + } + + #[test] + fn test_config_desc_dfu() { + const CONFIG_DESC_DFU: ConfigDescriptor = ConfigDescriptor { + configuration_value: 1, + max_power: 250, + self_powered: false, + remote_wakeup: false, + interfaces: &[InterfaceDescriptor { + name: INTERFACE_NAME_HANDLE, + interface_number: 0, + alternate_setting: 0, + interface_class: 0xfe, + interface_sub_class: 0x01, + interface_protocol: 0x02, + func_descs: &[FunctionalDescriptor::Dfu(DfuFunctionalDescriptor { + can_download: true, + can_upload: false, + manifestation_tolerant: true, + will_detach: true, + transfer_size: 2048, + detach_timeout_ms: 8000, + })], + endpoints: &[], + }], + }; + const CONFIG_DESC_BYTES: [u8; CONFIG_DESC_DFU.total_size()] = CONFIG_DESC_DFU.serialize(); + + assert_eq!( + &CONFIG_DESC_BYTES, + &[ + 0x09, 0x02, 0x1b, 0x00, 0x01, 0x01, 0x00, 0x80, 0xfa, 0x09, 0x04, 0x00, 0x00, 0x00, + 0xfe, 0x01, 0x02, 0x05, 0x09, 0x21, 0x0d, 0x40, 0x1f, 0x00, 0x08, 0x00, 0x01 + ] + ) + } + + #[test] + fn test_string_descriptor() { + use core::ops::Deref; + const USB_VENDOR: StringDescriptorRef = string_descriptor!("Mutask").as_ref(); + assert_eq!( + USB_VENDOR.as_bytes().deref(), + &[14, 3, b'M', 0, b'u', 0, b't', 0, b'a', 0, b's', 0, b'k', 0,] + ); + } + + #[test] + pub fn test_hex_utf16_descriptor() { + let mut buf = [0_u8; 80]; + let len = hex_utf16_descriptor(&mut buf, &[0xab, 0x1c, 0xd2, 0xe3, 0x4f, 0x56, 0x78, 0x90]) + .unwrap(); + assert_eq!( + [ + 34, 3, b'a', 0, b'b', 0, b'1', 0, b'c', 0, b'd', 0, b'2', 0, b'e', 0, b'3', 0, + b'4', 0, b'f', 0, b'5', 0, b'6', 0, b'7', 0, b'8', 0, b'9', 0, b'0', 0 + ], + &buf[..len] + ); + + // empty string; tight fit + let mut buf = [0_u8; 2]; + let len = hex_utf16_descriptor(&mut buf, b"").unwrap(); + assert_eq!(&[2, 3], &buf[..len]); + + // 1 byte; tight fit + let mut buf = [0_u8; 6]; + let len = hex_utf16_descriptor(&mut buf, &[0xca]).unwrap(); + assert_eq!(&[6, 3, b'c', 0, b'a', 0], &buf[..len]); + + // 2 bytes; tight fit + let mut buf = [0_u8; 10]; + let len = hex_utf16_descriptor(&mut buf, &[0xca, 0xfe]).unwrap(); + assert_eq!(&[10, 3, b'c', 0, b'a', 0, b'f', 0, b'e', 0], &buf[..len]); + + // too small to fit descriptor + let mut buf = [0_u8; 1]; + hex_utf16_descriptor(&mut buf, b"").unwrap_err(); + + // too small to fit 1 byte hex string + let mut buf = [0_u8; 5]; + hex_utf16_descriptor(&mut buf, b"H").unwrap_err(); + + // too small to fit 2 byte hex string + let mut buf = [0_u8; 9]; + hex_utf16_descriptor(&mut buf, b"Hi").unwrap_err(); + + // length too big to fit in length field + let mut buf = [0_u8; 258]; + hex_utf16_descriptor(&mut buf, &[0x42_u8; 64]).unwrap_err(); + } +} diff --git a/hal/blocking/usb/driver.rs b/hal/blocking/usb/driver.rs new file mode 100644 index 00000000..c5c53c71 --- /dev/null +++ b/hal/blocking/usb/driver.rs @@ -0,0 +1,132 @@ +//! USB peripheral driver traits and events. +//! +//! This module defines the interface between the USB protocol stack and the +//! hardware-specific peripheral driver. + +use aligned::Aligned; +use aligned::A4; +use core::mem::MaybeUninit; + +use crate::SetupPacket; + +/// A trait implemented by drivers for USB peripheral controllers. +pub trait UsbDriver { + /// The maximum packet size supported by the hardware (typically 64 bytes). + const MAX_PACKET_SIZE: usize; + /// The type of packet returned by the driver. + type Packet<'a>: UsbPacket + where + Self: 'a; + + /// Store data in a peripheral buffer that will be transferred to the host + /// when it requests data from the IN endpoint at `endpoint_idx`. + /// + /// If `zlp` is true and `data.len()` is a multiple of `MAX_PACKET_SIZE`, + /// the driver should send a zero-length packet (ZLP) after all data has + /// been acknowledged. + /// + /// The return value is the number of bytes that were copied into the + /// peripheral buffer. It will be either a multiple of `MAX_PACKET_SIZE`, + /// or `data.len()`. + /// + /// This function may fault or panic if `endpoint_idx` is invalid, or if the + /// hardware is in an invalid state. + fn transfer_in(&mut self, endpoint_idx: u8, data: &Aligned, zlp: bool) -> usize; + + /// Store data in a peripheral buffer that will be transferred to the host. + /// + /// This version accepts an unaligned data buffer. + fn transfer_in_unaligned(&mut self, endpoint_idx: u8, data: &[u8], zlp: bool) -> usize; + + /// Stalls or unstalls an endpoint. + /// + /// Note: the driver will automatically unstall all endpoints upon a USB + /// reset or upon receiving a new SETUP packet on Endpoint 0. + fn stall(&mut self, endpoint_num: u8, stalled: bool); + + /// Returns whether the specified endpoint is currently stalled. + fn is_stalled(&mut self, endpoint_num: u8) -> bool; + + /// Sets the address the peripheral responds to. + /// + /// The USB stack must call this function in response to a `SET_ADDRESS` + /// control request on Endpoint 0. + fn set_address(&mut self, address: u8); + + /// Polls the driver for a USB event. + /// + /// When a USB interrupt occurs, the USB stack should call this function + /// repeatedly until it returns `None`. + fn poll(&mut self) -> Option>>; +} + +/// A trait representing a received USB packet. +pub trait UsbPacket { + /// Returns the index of the endpoint the packet was received on. + fn endpoint_index(&self) -> usize; + + /// Returns the length of the packet data in bytes. + fn len(&self) -> usize; + + /// Copies the packet data from the peripheral buffer into system memory. + /// + /// This method allows copying into uninitialized memory. + /// Will fault if `self.len() > dest.len() * 4`. + fn copy_to_uninit(self, dest: &mut [MaybeUninit]) -> &[u8]; + + /// Copies the packet data from the peripheral buffer into system memory. + /// + /// Will fault if `self.len() > dest.len() * 4`. + fn copy_to(self, dest: &mut [u32]) -> &[u8]; + + /// Copies the packet data from the peripheral buffer into system memory. + /// + /// This version accepts an unaligned destination buffer. + fn copy_to_unaligned(self, dest: &mut [u8]) -> &[u8]; + + /// Returns `true` if the packet is empty (zero-length). + fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +/// Events that can be reported by a USB driver. +pub enum UsbEvent { + /// A SETUP packet has been received from the host. + SetupPacket { + /// The decoded SETUP packet. + pkt: SetupPacket, + /// The endpoint index (always 0 for standard SETUP packets). + endpoint: u8, + }, + + /// An OUT packet has been received from the host. + DataOutPacket(TPacket), + + /// A packet has been sent by the peripheral and acknowledged by the host. + /// + /// This indicates that buffer space is now available on the specified + /// endpoint for further `transfer_in` calls. + PacketSent { + /// The index of the endpoint that sent the packet. + endpoint: u32, + }, + + /// VBus presence detected. + VBus, + /// VBus presence lost. + VBusLost, + /// USB link is down. + LinkDown, + /// USB link is up. + LinkUp, + /// USB bus reset received. + UsbReset, + /// USB bus suspend received. + Suspend, + /// USB bus resume received. + Resume, + + /// Unexpected buffer ID error. + ErrorUnexpectedBufId, +} diff --git a/hal/blocking/usb/lib.rs b/hal/blocking/usb/lib.rs new file mode 100644 index 00000000..5396ab42 --- /dev/null +++ b/hal/blocking/usb/lib.rs @@ -0,0 +1,482 @@ +#![cfg_attr(not(test), no_std)] + +//! USB Hardware Abstraction Layer (HAL) for blocking I/O. +//! +//! This module provides the foundational types and traits for implementing +//! USB device drivers and protocol stacks. It includes definitions for +//! standard USB requests, setup packets, and descriptors. + +mod descriptor; +pub mod driver; + +use ufmt::derive::uDebug; + +pub use descriptor::*; + +// Big endian is dead; code in this file assumes little-endian +const _: () = assert!(cfg!(target_endian = "little")); + +/// Represents a USB control request. +/// +/// A request combines the `bmRequestType` and `bRequest` fields from a +/// USB SETUP packet into a single type-safe representation. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(transparent)] +pub struct Request(u16); +#[allow(clippy::identity_op)] +impl Request { + /// Creates a new USB request. + pub const fn new( + direction: Direction, + ty: RequestType, + recipient: Recipient, + request: u8, + ) -> Self { + Self( + ((direction as u16) << 7) + | ((ty as u16) << 5) + | ((recipient as u16) << 0) + | ((request as u16) << 8), + ) + } + /// Returns the direction of the request (Host-to-Device or Device-to-Host). + pub fn direction(&self) -> Direction { + Direction::try_from((u32::from(self.0) >> 7) & 0x1).unwrap() + } + /// Returns the type of the request (Standard, Class, Vendor, or Reserved). + pub fn request_type(&self) -> RequestType { + RequestType::try_from(u32::from((self.0 >> 5) & 0x3)).unwrap() + } + /// Returns the recipient of the request (Device, Interface, Endpoint, or Other). + pub fn recipient(&self) -> Recipient { + Recipient::try_from(u32::from((self.0 >> 0) & 0x1f)).unwrap() + } + /// Returns the specific request code. + pub fn request(&self) -> u8 { + u8::try_from((self.0 >> 8) & 0xff).unwrap() + } +} +impl ufmt::uDebug for Request { + fn fmt( + &self, + f: &mut ufmt::Formatter<'_, W>, + ) -> Result<(), W::Error> { + f.debug_struct("usb::Request")? + .field("request_type", &self.request_type())? + .field("direction", &self.direction())? + .field("recipient", &self.recipient())? + .field("request", &self.request())? + .finish() + } +} +impl Request { + /// Standard DEVICE request to get the current status. + pub const DEVICE_GET_STATUS: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Device, + 0x00, + ); + /// Standard DEVICE request to clear a feature. + pub const DEVICE_CLEAR_FEATURE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Device, + 0x01, + ); + /// Standard DEVICE request to set a feature. + pub const DEVICE_SET_FEATURE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Device, + 0x03, + ); + /// Standard DEVICE request to set the device address. + pub const DEVICE_SET_ADDRESS: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Device, + 0x05, + ); + /// Standard DEVICE request to get a descriptor. + pub const DEVICE_GET_DESCRIPTOR: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Device, + 0x06, + ); + /// Standard DEVICE request to set a descriptor. + pub const DEVICE_SET_DESCRIPTOR: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Device, + 0x07, + ); + /// Standard DEVICE request to get the current configuration. + pub const DEVICE_GET_CONFIGURATION: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Device, + 0x08, + ); + /// Standard DEVICE request to set the current configuration. + pub const DEVICE_SET_CONFIGURATION: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Device, + 0x09, + ); + /// Standard INTERFACE request to get the current status. + pub const INTERFACE_GET_STATUS: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Interface, + 0x00, + ); + /// Standard INTERFACE request to clear a feature. + pub const INTERFACE_CLEAR_FEATURE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Interface, + 0x01, + ); + /// Standard INTERFACE request to set a feature. + pub const INTERFACE_SET_FEATURE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Interface, + 0x03, + ); + /// Standard INTERFACE request to get the current interface setting. + pub const INTERFACE_GET_INTERFACE: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Interface, + 0x0a, + ); + /// Standard INTERFACE request to set the interface setting. + pub const INTERFACE_SET_INTERFACE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Interface, + 0x0b, + ); + /// Standard ENDPOINT request to get the current status. + pub const ENDPOINT_GET_STATUS: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Endpoint, + 0x00, + ); + /// Standard ENDPOINT request to clear a feature. + pub const ENDPOINT_CLEAR_FEATURE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Endpoint, + 0x01, + ); + /// Standard ENDPOINT request to set a feature. + pub const ENDPOINT_SET_FEATURE: Self = Self::new( + Direction::HostToDevice, + RequestType::Standard, + Recipient::Endpoint, + 0x03, + ); + /// Standard ENDPOINT request to synchronize frames. + pub const ENDPOINT_SYNCH_FRAME: Self = Self::new( + Direction::DeviceToHost, + RequestType::Standard, + Recipient::Endpoint, + 0x12, + ); +} +impl From for u16 { + fn from(val: Request) -> Self { + val.0 + } +} +#[cfg(test)] +mod request_tests { + use super::*; + #[test] + fn test_constants() { + assert_eq!(u16::from(Request::DEVICE_GET_STATUS), 0x0080); + assert_eq!(u16::from(Request::DEVICE_CLEAR_FEATURE), 0x0100); + assert_eq!(u16::from(Request::DEVICE_SET_FEATURE), 0x0300); + assert_eq!(u16::from(Request::DEVICE_SET_ADDRESS), 0x0500); + assert_eq!(u16::from(Request::DEVICE_GET_DESCRIPTOR), 0x0680); + assert_eq!(u16::from(Request::DEVICE_SET_DESCRIPTOR), 0x0700); + assert_eq!(u16::from(Request::DEVICE_GET_CONFIGURATION), 0x0880); + assert_eq!(u16::from(Request::DEVICE_SET_CONFIGURATION), 0x0900); + assert_eq!(u16::from(Request::INTERFACE_GET_STATUS), 0x0081); + assert_eq!(u16::from(Request::INTERFACE_CLEAR_FEATURE), 0x0101); + assert_eq!(u16::from(Request::INTERFACE_SET_FEATURE), 0x0301); + assert_eq!(u16::from(Request::INTERFACE_GET_INTERFACE), 0x0a81); + assert_eq!(u16::from(Request::INTERFACE_SET_INTERFACE), 0x0b01); + assert_eq!(u16::from(Request::ENDPOINT_GET_STATUS), 0x0082); + assert_eq!(u16::from(Request::ENDPOINT_CLEAR_FEATURE), 0x0102); + assert_eq!(u16::from(Request::ENDPOINT_SET_FEATURE), 0x0302); + assert_eq!(u16::from(Request::ENDPOINT_SYNCH_FRAME), 0x1282); + } +} + +/// Information about a USB descriptor request. +#[derive(Clone, Copy, Eq, PartialEq, uDebug)] +pub struct DescriptorInfo { + /// The index of the descriptor. + pub index: u8, + /// The type of the descriptor. + pub ty: DescriptorType, + /// The language ID (for string descriptors). + pub lang: u16, +} +impl From<&SetupPacket> for DescriptorInfo { + fn from(pkt: &SetupPacket) -> Self { + DescriptorInfo { + index: u8::try_from(pkt.value() & 0xff).unwrap(), + ty: DescriptorType::from(u8::try_from((pkt.value() >> 8) & 0xff).unwrap()), + lang: pkt.index(), + } + } +} + +/// Represents a standard USB SETUP packet. +/// +/// A SETUP packet is always 8 bytes long and is used for all control transfers +/// on Endpoint 0. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct SetupPacket { + buf: [u32; 2], +} +impl SetupPacket { + /// Creates a new `SetupPacket` from two 32-bit words. + pub fn new(buf: [u32; 2]) -> SetupPacket { + SetupPacket { buf } + } + /// Returns the control request information. + pub fn request(&self) -> Request { + Request(u16::try_from(self.buf[0] & 0xffff).unwrap()) + } + /// Returns the `wValue` field of the SETUP packet. + pub fn value(&self) -> u16 { + u16::try_from((self.buf[0] >> 16) & 0xffff).unwrap() + } + /// Returns the `wIndex` field of the SETUP packet. + #[allow(clippy::identity_op)] + pub fn index(&self) -> u16 { + u16::try_from((self.buf[1] >> 0) & 0xffff).unwrap() + } + /// Returns the `wLength` field of the SETUP packet, which indicates + /// the number of bytes to transfer in the data stage. + pub fn length(&self) -> u16 { + u16::try_from((self.buf[1] >> 16) & 0xffff).unwrap() + } +} +impl ufmt::uDebug for SetupPacket { + fn fmt( + &self, + f: &mut ufmt::Formatter<'_, W>, + ) -> Result<(), W::Error> { + f.debug_struct("usb::SetupPacket")? + .field("request", &self.request())? + .field("value", &self.value())? + .field("index", &self.index())? + .field("length", &self.length())? + .finish() + } +} + +/// USB data transfer direction. +#[derive(Clone, Copy, Eq, PartialEq, uDebug)] +pub enum Direction { + /// Host to Device (OUT). + HostToDevice = 0, + /// Device to Host (IN). + DeviceToHost = 1, +} +impl From for u32 { + fn from(val: Direction) -> u32 { + val as u32 + } +} +impl TryFrom for Direction { + type Error = (); + #[inline(always)] + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::HostToDevice), + 1 => Ok(Self::DeviceToHost), + _ => Err(()), + } + } +} + +/// The type of a USB control request. +#[derive(Clone, Copy, Eq, PartialEq, uDebug)] +pub enum RequestType { + /// Standard USB request. + Standard = 0, + /// Class-specific request. + Class = 1, + /// Vendor-specific request. + Vendor = 2, + /// Reserved for future use. + Reserved = 3, +} +impl TryFrom for RequestType { + type Error = (); + #[inline(always)] + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::Standard), + 1 => Ok(Self::Class), + 2 => Ok(Self::Vendor), + 3 => Ok(Self::Reserved), + _ => Err(()), + } + } +} +impl From for u32 { + fn from(val: RequestType) -> Self { + val as u32 + } +} + +/// The intended recipient of a USB control request. +#[derive(Clone, Copy, Eq, PartialEq, uDebug)] +pub enum Recipient { + /// The device itself. + Device = 0, + /// A specific interface on the device. + Interface = 1, + /// A specific endpoint on the device. + Endpoint = 2, + /// Other recipients (e.g., class-specific). + Other = 3, + Reserved4 = 4, + Reserved5 = 5, + Reserved6 = 6, + Reserved7 = 7, + Reserved8 = 8, + Reserved9 = 9, + Reserved10 = 10, + Reserved11 = 11, + Reserved12 = 12, + Reserved13 = 13, + Reserved14 = 14, + Reserved15 = 15, + Reserved16 = 16, + Reserved17 = 17, + Reserved18 = 18, + Reserved19 = 19, + Reserved20 = 20, + Reserved21 = 21, + Reserved22 = 22, + Reserved23 = 23, + Reserved24 = 24, + Reserved25 = 25, + Reserved26 = 26, + Reserved27 = 27, + Reserved28 = 28, + Reserved29 = 29, + Reserved30 = 30, + Reserved31 = 31, +} +impl TryFrom for Recipient { + type Error = (); + #[inline(always)] + fn try_from(val: u32) -> Result { + // TODO: Evaluate whether the optimizer is smart enough for this, and use + // transmute if it's not. + match val { + 0 => Ok(Self::Device), + 1 => Ok(Self::Interface), + 2 => Ok(Self::Endpoint), + 3 => Ok(Self::Other), + 4 => Ok(Self::Reserved4), + 5 => Ok(Self::Reserved5), + 6 => Ok(Self::Reserved6), + 7 => Ok(Self::Reserved7), + 8 => Ok(Self::Reserved8), + 9 => Ok(Self::Reserved9), + 10 => Ok(Self::Reserved10), + 11 => Ok(Self::Reserved11), + 12 => Ok(Self::Reserved12), + 13 => Ok(Self::Reserved13), + 14 => Ok(Self::Reserved14), + 15 => Ok(Self::Reserved15), + 16 => Ok(Self::Reserved16), + 17 => Ok(Self::Reserved17), + 18 => Ok(Self::Reserved18), + 19 => Ok(Self::Reserved19), + 20 => Ok(Self::Reserved20), + 21 => Ok(Self::Reserved21), + 22 => Ok(Self::Reserved22), + 23 => Ok(Self::Reserved23), + 24 => Ok(Self::Reserved24), + 25 => Ok(Self::Reserved25), + 26 => Ok(Self::Reserved26), + 27 => Ok(Self::Reserved27), + 28 => Ok(Self::Reserved28), + 29 => Ok(Self::Reserved29), + 30 => Ok(Self::Reserved30), + 31 => Ok(Self::Reserved31), + _ => Err(()), + } + } +} +impl From for u32 { + fn from(val: Recipient) -> Self { + val as u32 + } +} + +/// Standard USB descriptor types. +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct DescriptorType(u8); +impl DescriptorType { + /// Device descriptor. + pub const DEVICE: Self = Self(1); + /// Configuration descriptor. + pub const CONFIGURATION: Self = Self(2); + /// String descriptor. + pub const STRING: Self = Self(3); + /// Interface descriptor. + pub const INTERFACE: Self = Self(4); + /// Endpoint descriptor. + pub const ENDPOINT: Self = Self(5); + /// Device qualifier descriptor. + pub const DEVICE_QUALIFIER: Self = Self(6); +} +impl From for DescriptorType { + fn from(val: u8) -> Self { + DescriptorType(val) + } +} +impl From for u8 { + fn from(val: DescriptorType) -> Self { + val.0 + } +} +impl From for u32 { + fn from(val: DescriptorType) -> Self { + u32::from(val.0) + } +} +impl ufmt::uDebug for DescriptorType { + fn fmt( + &self, + f: &mut ufmt::Formatter<'_, W>, + ) -> Result<(), W::Error> { + match *self { + Self::DEVICE => f.write_str("DEVICE"), + Self::CONFIGURATION => f.write_str("CONFIGURATION"), + Self::STRING => f.write_str("STRING"), + Self::INTERFACE => f.write_str("INTERFACE"), + Self::ENDPOINT => f.write_str("ENDPOINT"), + Self::DEVICE_QUALIFIER => f.write_str("DEVICE_QUALIFIER"), + other => ufmt::uwrite!(f, "{}", other.0), + } + } +} diff --git a/presubmit/cpp_include_guard.py b/presubmit/cpp_include_guard.py index 68fec6dc..9908b93b 100644 --- a/presubmit/cpp_include_guard.py +++ b/presubmit/cpp_include_guard.py @@ -17,7 +17,7 @@ def guard_name(path: Path) -> str: # The presubmit tool runs in the root of the project. # Compute the path relative to the project root. path = path.relative_to(os.getcwd()) - guard = f"{PROJECT}_{path}".replace("/", "_").replace(".", "_") + guard = f"{PROJECT}_{path}_".replace("/", "_").replace(".", "_") return guard.upper() diff --git a/protocol/usb/cdc_acm/BUILD.bazel b/protocol/usb/cdc_acm/BUILD.bazel new file mode 100644 index 00000000..2539f259 --- /dev/null +++ b/protocol/usb/cdc_acm/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") + +rust_library( + name = "cdc_acm", + srcs = ["lib.rs"], + crate_name = "protocol_usb_cdc_acm", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/usb:hal_usb", + "//protocol/usb/stack", + "//target/earlgrey/drivers:usb_driver", + "//util/ringbuffer", + "@rust_crates//:aligned", + ], +) diff --git a/protocol/usb/cdc_acm/lib.rs b/protocol/usb/cdc_acm/lib.rs new file mode 100644 index 00000000..ffabf1be --- /dev/null +++ b/protocol/usb/cdc_acm/lib.rs @@ -0,0 +1,400 @@ +//! CDC-ACM (Serial) USB class implementation. + +#![no_std] + +pub use hal_usb::driver::{UsbEvent, UsbPacket, UsbDriver}; +pub use hal_usb::{ + Direction, EndpointDescriptor, FunctionalDescriptor, InterfaceDescriptor, Recipient, Request, + RequestType, SetupPacket, StringHandle, TransferType, +}; +pub use usb_driver::{EpIn, EpOut}; +use usb_stack::{UsbAction, UsbClass, EMPTY}; +use util_ringbuffer::RingBuffer; + +/// CDC-ACM specific constants. +pub const USB_CLASS_CDC: u8 = 0x02; +pub const USB_CLASS_CDC_DATA: u8 = 0x0a; +pub const CDC_SUBCLASS_ACM: u8 = 0x02; +pub const CDC_PROTOCOL_NONE: u8 = 0x00; + +pub const CS_INTERFACE: u8 = 0x24; +pub const CDC_TYPE_HEADER: u8 = 0x00; +pub const CDC_TYPE_ACM: u8 = 0x02; +pub const CDC_TYPE_UNION: u8 = 0x06; + +/// CDC-ACM specific requests. +pub const REQ_SEND_ENCAPSULATED_COMMAND: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 0x00, +); +pub const REQ_GET_ENCAPSULATED_COMMAND: Request = Request::new( + Direction::DeviceToHost, + RequestType::Class, + Recipient::Interface, + 0x01, +); +pub const REQ_SET_LINE_CODING: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 0x20, +); +pub const REQ_GET_LINE_CODING: Request = Request::new( + Direction::DeviceToHost, + RequestType::Class, + Recipient::Interface, + 0x21, +); +pub const REQ_SET_CONTROL_LINE_STATE: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 0x22, +); + +#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[repr(u8)] +pub enum StopBits { + #[default] + One = 0, + OnePointFive = 1, + Two = 2, +} + +impl From for StopBits { + fn from(x: u8) -> Self { + match x { + 0 => StopBits::One, + 1 => StopBits::OnePointFive, + 2 => StopBits::Two, + _ => StopBits::One, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Default)] +#[repr(u8)] +pub enum Parity { + #[default] + None = 0, + Odd = 1, + Even = 2, + Mark = 3, + Space = 4, +} + +impl From for Parity { + fn from(x: u8) -> Self { + match x { + 0 => Parity::None, + 1 => Parity::Odd, + 2 => Parity::Even, + 3 => Parity::Mark, + 4 => Parity::Space, + _ => Parity::None, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(C)] +pub struct LineCoding { + pub data_rate: u32, + pub stop_bits: StopBits, + pub parity: Parity, + pub data_bits: u8, +} + +impl TryFrom<&[u8]> for LineCoding { + type Error = (); + fn try_from(data: &[u8]) -> Result { + if data.len() >= 7 { + Ok(LineCoding { + data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), + stop_bits: StopBits::from(data[4]), + parity: Parity::from(data[5]), + data_bits: data[6], + }) + } else { + Err(()) + } + } +} + +impl Default for LineCoding { + fn default() -> Self { + LineCoding { + data_rate: 9600, + stop_bits: StopBits::default(), + parity: Parity::default(), + data_bits: 8, + } + } +} + +impl LineCoding { + pub fn as_bytes(&self) -> &[u8] { + let x = unsafe { core::mem::transmute::<&LineCoding, &[u8; 7]>(self) }; + &*x + } +} + +/// A builder for CDC-ACM class configuration. +/// +/// This builder encapsulates the constants and configuration logic for a +/// CDC-ACM instance. It provides methods to generate descriptor fragments +/// and the final `InterfaceDescriptor` structs, hiding class-specific details +/// from the application. +/// +/// # Convention +/// USB classes should provide a `Builder` struct with `const` methods that: +/// 1. Return arrays of child descriptors (functional, endpoints). +/// 2. Construct fully-populated `InterfaceDescriptor`s from application-provided +/// handles and static fragments. +/// 3. Provide hardware configuration (`EpIn`, `EpOut`). +#[derive(Copy, Clone)] +pub struct CdcAcmBuilder { + pub comm_if: u8, + pub data_if: u8, + pub comm_ep: u8, + pub data_out_ep: u8, + pub data_in_ep: u8, +} + +impl CdcAcmBuilder { + /// Creates a new CDC-ACM configuration. + pub const fn new(comm_if: u8, data_if: u8, comm_ep: u8, data_out_ep: u8, data_in_ep: u8) -> Self { + Self { + comm_if, + data_if, + comm_ep, + data_out_ep, + data_in_ep, + } + } + + /// Returns the functional descriptors for the control interface. + pub const fn comm_func_descs(&self) -> [FunctionalDescriptor; 3] { + [ + FunctionalDescriptor::raw(CS_INTERFACE, &[CDC_TYPE_HEADER, 0x10, 0x01]), + FunctionalDescriptor::raw(CS_INTERFACE, &[CDC_TYPE_ACM, 0x02]), + FunctionalDescriptor::raw(CS_INTERFACE, &[CDC_TYPE_UNION, self.comm_if, self.data_if]), + ] + } + + /// Returns the endpoints for the control interface. + pub const fn comm_endpoints(&self) -> [EndpointDescriptor; 1] { + [EndpointDescriptor { + direction: Direction::DeviceToHost, + endpoint_num: self.comm_ep, + interval: 255, + max_packet_size: 8, + transfer_type: TransferType::Interrupt, + }] + } + + /// Returns the endpoints for the data interface. + pub const fn data_endpoints(&self) -> [EndpointDescriptor; 2] { + [ + EndpointDescriptor { + direction: Direction::HostToDevice, + endpoint_num: self.data_out_ep, + interval: 0, + max_packet_size: 64, + transfer_type: TransferType::Bulk, + }, + EndpointDescriptor { + direction: Direction::DeviceToHost, + endpoint_num: self.data_in_ep, + interval: 0, + max_packet_size: 64, + transfer_type: TransferType::Bulk, + }, + ] + } + + /// Constructs the CDC-ACM Communication (Control) interface descriptor. + pub const fn comm_interface( + &self, + name: StringHandle, + func_descs: &'static [FunctionalDescriptor], + endpoints: &'static [EndpointDescriptor], + ) -> InterfaceDescriptor { + InterfaceDescriptor { + name, + interface_number: self.comm_if, + alternate_setting: 0, + interface_class: USB_CLASS_CDC, + interface_sub_class: CDC_SUBCLASS_ACM, + interface_protocol: CDC_PROTOCOL_NONE, + func_descs, + endpoints, + } + } + + /// Constructs the CDC-ACM Data interface descriptor. + pub const fn data_interface( + &self, + name: StringHandle, + endpoints: &'static [EndpointDescriptor], + ) -> InterfaceDescriptor { + InterfaceDescriptor { + name, + interface_number: self.data_if, + alternate_setting: 0, + interface_class: USB_CLASS_CDC_DATA, + interface_sub_class: 0, + interface_protocol: CDC_PROTOCOL_NONE, + func_descs: &[], + endpoints, + } + } + + /// Returns the hardware endpoint configuration for this CDC-ACM instance. + pub const fn eps(&self) -> ([EpIn; 2], [EpOut; 1]) { + ( + [ + EpIn { + num: self.comm_ep, + buf_pool_size: 1, + }, + EpIn { + num: self.data_in_ep, + buf_pool_size: 1, + }, + ], + [EpOut { + num: self.data_out_ep, + set_nak: false, + }], + ) + } +} + +/// CDC-ACM class handler. +pub struct CdcAcm { + config: CdcAcmBuilder, + line_coding: LineCoding, + expecting_control_out: bool, + control_buf: [u8; 8], + pub rx_queue: RingBuffer, + pub tx_queue: RingBuffer, +} + +impl CdcAcm { + /// Creates a new CDC-ACM class handler from a builder. + pub fn new(config: CdcAcmBuilder) -> Self { + Self { + config, + line_coding: LineCoding::default(), + expecting_control_out: false, + control_buf: [0u8; 8], + rx_queue: RingBuffer::default(), + tx_queue: RingBuffer::default(), + } + } + + /// Returns the configuration builder for this instance. + pub fn config(&self) -> &CdcAcmBuilder { + &self.config + } + + fn handle_setup<'a>(&'a mut self, pkt: SetupPacket) -> (UsbAction<'a>, bool) { + if !(pkt.request().recipient() == Recipient::Interface + && (pkt.index() as u8) == self.config.comm_if) + { + return (UsbAction::None, false); + } + + match pkt.request() { + REQ_SEND_ENCAPSULATED_COMMAND => ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + false, + ), + REQ_GET_ENCAPSULATED_COMMAND => (UsbAction::StallInAndOut { endpoint: 0 }, false), + REQ_SET_LINE_CODING => { + self.expecting_control_out = true; + (UsbAction::None, true) + } + REQ_GET_LINE_CODING => ( + UsbAction::TransferInUnaligned { + endpoint: 0, + data: self.line_coding.as_bytes(), + zlp: true, + }, + false, + ), + REQ_SET_CONTROL_LINE_STATE => ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + false, + ), + _ => (UsbAction::StallInAndOut { endpoint: 0 }, false), + } + } + + fn handle_control_out<'a>(&'a mut self, pkt: impl UsbPacket) -> UsbAction<'a> { + let buf = pkt.copy_to_unaligned(&mut self.control_buf); + self.expecting_control_out = false; + match LineCoding::try_from(buf) { + Ok(x) => { + self.line_coding = x; + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + } + } + Err(_) => UsbAction::StallInAndOut { endpoint: 0 }, + } + } + + /// Polls the send buffer and initiates IN transfers if data is available. + pub fn poll_transmit(&mut self, driver: &mut D) { + let data = self.tx_queue.as_slice(); + if !data.is_empty() { + let n = driver.transfer_in_unaligned(self.config.data_in_ep, data, true); + self.tx_queue.consume(n); + } + } +} + +impl UsbClass for CdcAcm { + fn handle_event<'a, P: UsbPacket>( + &'a mut self, + event: UsbEvent

, + ) -> Result, UsbEvent

> { + match event { + UsbEvent::SetupPacket { pkt, endpoint } if endpoint == 0 => { + let (action, claimed) = self.handle_setup(pkt); + if action != UsbAction::None || claimed { + Ok(action) + } else { + Err(UsbEvent::SetupPacket { pkt, endpoint }) + } + } + UsbEvent::DataOutPacket(pkt) => { + if pkt.endpoint_index() == 0 && self.expecting_control_out { + Ok(self.handle_control_out(pkt)) + } else if pkt.endpoint_index() == self.config.data_out_ep as usize { + let mut tmp = [0u8; 64]; + let buf = pkt.copy_to_unaligned(&mut tmp); + let _ = self.rx_queue.push_slice(buf); + Ok(UsbAction::None) + } else { + Err(UsbEvent::DataOutPacket(pkt)) + } + } + _ => Err(event), + } + } +} diff --git a/protocol/usb/dfu/BUILD.bazel b/protocol/usb/dfu/BUILD.bazel new file mode 100644 index 00000000..b20ce214 --- /dev/null +++ b/protocol/usb/dfu/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "dfu", + srcs = ["lib.rs"], + crate_name = "protocol_usb_dfu", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/usb:hal_usb", + "//protocol/usb/stack", + "@rust_crates//:aligned", + ], +) + +rust_test( + name = "dfu_test", + crate = ":dfu", + edition = "2024", +) diff --git a/protocol/usb/dfu/SPEC.md b/protocol/usb/dfu/SPEC.md new file mode 100644 index 00000000..8db7846d --- /dev/null +++ b/protocol/usb/dfu/SPEC.md @@ -0,0 +1,68 @@ +# USB Device Firmware Upgrade (DFU) 1.1 Specification + +This document describes the USB DFU 1.1 implementation for this project. + +## 1. Overview +The USB DFU 1.1 protocol allows for firmware upgrades over USB. This implementation is designed for limited-memory devices and supports multiple alternate settings to address different memory regions (e.g., internal flash, external SPI flash, bootloader). + +## 2. USB Descriptors + +### 2.1 DFU Functional Descriptor +The DFU functional descriptor follows the USB DFU 1.1 specification. + +| Offset | Field | Size | Value | Description | +| :--- | :--- | :--- | :--- | :--- | +| 0 | bLength | 1 | 0x09 | Size of this descriptor in bytes. | +| 1 | bDescriptorType | 1 | 0x21 | DFU FUNCTIONAL descriptor type. | +| 2 | bmAttributes | 1 | 0x07 | bitCanDnload, bitCanUpload, bitManifestationTolerant. | +| 3 | wDetachTimeOut | 2 | 0x0000 | Time in ms to wait after DFU_DETACH. | +| 5 | wTransferSize | 2 | Configurable | Maximum number of bytes the device can accept per control-write. | +| 7 | bcdDFUVersion | 2 | 0x0110 | DFU specification release number in BCD. | + +### 2.2 Interface Descriptors +Multiple alternate settings are supported. Each altsetting typically represents a different memory partition. + +| Offset | Field | Size | Value | Description | +| :--- | :--- | :--- | :--- | :--- | +| 0 | bLength | 1 | 0x09 | Size of this descriptor in bytes. | +| 1 | bDescriptorType | 1 | 0x04 | INTERFACE descriptor type. | +| 2 | bInterfaceNumber | 1 | Variable | Number of this interface. | +| 3 | bAlternateSetting | 1 | Variable | Value used to select this alternate setting. | +| 4 | bNumEndpoints | 1 | 0x00 | No endpoints used (control only). | +| 5 | bInterfaceClass | 1 | 0xFE | Application Specific Class. | +| 6 | bInterfaceSubClass | 1 | 0x01 | Device Firmware Upgrade. | +| 7 | bInterfaceProtocol | 1 | 0x02 | DFU Mode. | +| 8 | iInterface | 1 | Variable | Index of string descriptor describing this partition. | + +## 3. DFU Requests + +The following DFU-specific requests are supported on the control endpoint (0): + +| Request | Code | Direction | Description | +| :--- | :--- | :--- | :--- | +| DFU_DETACH | 0 | Host to Device | Requests the device to leave its current mode and enter DFU mode. | +| DFU_DNLOAD | 1 | Host to Device | Data packets sent from the host to the device. | +| DFU_UPLOAD | 2 | Device to Host | Data packets sent from the device to the host. | +| DFU_GETSTATUS | 3 | Device to Host | Returns the current state and status of the device. | +| DFU_CLRSTATUS | 4 | Host to Device | Clears error status and returns to dfuIDLE. | +| DFU_GETSTATE | 5 | Device to Host | Returns the current state of the device. | +| DFU_ABORT | 6 | Host to Device | Aborts the current operation and returns to dfuIDLE. | + +## 4. State Machine +This implementation follows the DFU 1.1 state machine. For simplicity, all operations (flash erase/write) are performed synchronously within the `DFU_DNLOAD` and `DFU_UPLOAD` request handling, or immediately transition from SYNC to IDLE. + +## 5. Application Interface (`DfuHandler`) +The application must provide an implementation of the `DfuHandler` trait. + +```rust +pub trait DfuHandler { + /// Handle a DNLOAD block. + fn dnload(&mut self, alt: u8, block_num: u16, data: &[u8]) -> DfuResult; + /// Handle an UPLOAD block. + fn upload(&mut self, alt: u8, block_num: u16, data: &mut [u8]) -> Result; + /// Perform manifestation. + fn manifest(&mut self) -> DfuResult; + /// Abort current operation. + fn abort(&mut self); +} +``` diff --git a/protocol/usb/dfu/lib.rs b/protocol/usb/dfu/lib.rs new file mode 100644 index 00000000..1548ea68 --- /dev/null +++ b/protocol/usb/dfu/lib.rs @@ -0,0 +1,618 @@ +//! USB Device Firmware Upgrade (DFU) 1.1 implementation. + +#![no_std] + +use hal_usb::driver::{UsbDriver, UsbEvent, UsbPacket}; +use hal_usb::{ + Direction, FunctionalDescriptor, InterfaceDescriptor, Recipient, Request, RequestType, + SetupPacket, StringHandle, +}; +use usb_stack::{UsbAction, UsbClass, EMPTY}; + +/// DFU specific constants. +pub const USB_CLASS_APP_SPECIFIC: u8 = 0xFE; +pub const USB_SUBCLASS_DFU: u8 = 0x01; +pub const USB_PROTOCOL_DFU: u8 = 0x02; + +pub const DFU_DESCRIPTOR_TYPE: u8 = 0x21; + +/// DFU specific requests. +pub const DFU_DETACH: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 0, +); +pub const DFU_DNLOAD: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 1, +); +pub const DFU_UPLOAD: Request = Request::new( + Direction::DeviceToHost, + RequestType::Class, + Recipient::Interface, + 2, +); +pub const DFU_GETSTATUS: Request = Request::new( + Direction::DeviceToHost, + RequestType::Class, + Recipient::Interface, + 3, +); +pub const DFU_CLRSTATUS: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 4, +); +pub const DFU_GETSTATE: Request = Request::new( + Direction::DeviceToHost, + RequestType::Class, + Recipient::Interface, + 5, +); +pub const DFU_ABORT: Request = Request::new( + Direction::HostToDevice, + RequestType::Class, + Recipient::Interface, + 6, +); + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum DfuState { + AppIdle = 0, + AppDetach = 1, + DfuIdle = 2, + DfuDnloadSync = 3, + DfuDnloadBusy = 4, + DfuDnloadIdle = 5, + DfuManifestSync = 6, + DfuManifest = 7, + DfuManifestWaitReset = 8, + DfuUploadIdle = 9, + DfuError = 10, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum DfuStatus { + Ok = 0, + ErrTarget = 1, + ErrFile = 2, + ErrWrite = 3, + ErrErase = 4, + ErrCheckChunked = 5, + ErrProg = 6, + ErrVerify = 7, + ErrAddress = 8, + ErrNotDone = 9, + ErrFirmware = 10, + ErrVendor = 11, + ErrUsbr = 12, + ErrPor = 13, + ErrUnknown = 14, + ErrStalledPkt = 15, +} + +pub enum DfuResult { + Ok, + Err(DfuStatus), +} + +/// A trait for providing the backend storage for the DFU implementation. +pub trait DfuHandler { + /// Write a block of data to the device. + fn dnload(&mut self, alt: u8, block_num: u16, data: &[u8]) -> DfuResult; + /// Read a block of data from the device. + fn upload(&mut self, alt: u8, block_num: u16, data: &mut [u8]) -> Result; + /// Finalize the download. + fn manifest(&mut self) -> DfuResult; + /// Abort the current operation. + fn abort(&mut self); +} + +/// A builder for DFU class configuration. +#[derive(Copy, Clone)] +pub struct DfuBuilder { + pub interface_num: u8, + pub alt_settings: u8, + pub transfer_size: u16, + pub attributes: u8, + pub detach_timeout: u16, +} + +impl DfuBuilder { + pub const fn new(interface_num: u8, alt_settings: u8, transfer_size: u16) -> Self { + Self { + interface_num, + alt_settings, + transfer_size, + attributes: 0x07, // bitCanDnload | bitCanUpload | bitManifestationTolerant + detach_timeout: 0, + } + } + + pub const fn functional_descriptor(&self) -> FunctionalDescriptor { + FunctionalDescriptor::raw( + DFU_DESCRIPTOR_TYPE, + &[ + self.attributes, + (self.detach_timeout & 0xFF) as u8, + ((self.detach_timeout >> 8) & 0xFF) as u8, + (self.transfer_size & 0xFF) as u8, + ((self.transfer_size >> 8) & 0xFF) as u8, + 0x10, // bcdDFUVersion 1.1 (0x0110) + 0x01, + ], + ) + } + + pub const fn interface( + &self, + alt: u8, + name: StringHandle, + func_descs: &'static [FunctionalDescriptor], + ) -> InterfaceDescriptor { + InterfaceDescriptor { + name, + interface_number: self.interface_num, + alternate_setting: alt, + interface_class: USB_CLASS_APP_SPECIFIC, + interface_sub_class: USB_SUBCLASS_DFU, + interface_protocol: USB_PROTOCOL_DFU, + func_descs, + endpoints: &[], + } + } +} + +pub struct DfuClass +where + H: DfuHandler, +{ + config: DfuBuilder, + handler: H, + state: DfuState, + status: DfuStatus, + alt: u8, + expecting_dnload: bool, + block_num: u16, + buffer: [u8; BLOCK_SIZE], + transfer_offset: usize, + transfer_total: usize, +} + +impl DfuClass +where + H: DfuHandler, +{ + pub fn new(config: DfuBuilder, handler: H) -> Self { + assert!(BLOCK_SIZE >= config.transfer_size as usize); + Self { + config, + handler, + state: DfuState::DfuIdle, + status: DfuStatus::Ok, + alt: 0, + expecting_dnload: false, + block_num: 0, + buffer: [0u8; BLOCK_SIZE], + transfer_offset: 0, + transfer_total: 0, + } + } + + /// Polls the send buffer and initiates IN transfers if data is available. + pub fn poll(&mut self, driver: &mut D) { + if (self.state == DfuState::DfuUploadIdle || self.state == DfuState::DfuIdle) + && self.transfer_offset < self.transfer_total + { + let data = &self.buffer[self.transfer_offset..self.transfer_total]; + let n = driver.transfer_in_unaligned(0, data, true); + self.transfer_offset += n; + + if self.transfer_offset == self.transfer_total { + if self.transfer_total < self.config.transfer_size as usize { + self.state = DfuState::DfuIdle; + } else { + self.state = DfuState::DfuUploadIdle; + } + } + } + } + + fn handle_setup<'a>(&'a mut self, pkt: SetupPacket) -> (UsbAction<'a>, bool) { + if pkt.request().recipient() != Recipient::Interface + || (pkt.index() as u8) != self.config.interface_num + { + return (UsbAction::None, false); + } + + match pkt.request() { + DFU_DETACH => { + // In DFU mode, DETACH is a no-op or transitions back to APP mode. + // We'll just ACK it. + ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + true, + ) + } + DFU_DNLOAD => { + if self.state == DfuState::DfuError { + return (UsbAction::StallInAndOut { endpoint: 0 }, true); + } + let len = pkt.length() as usize; + if len > BLOCK_SIZE { + self.state = DfuState::DfuError; + self.status = DfuStatus::ErrStalledPkt; + return (UsbAction::StallInAndOut { endpoint: 0 }, true); + } + self.block_num = pkt.value(); + self.transfer_offset = 0; + self.transfer_total = len; + + if len == 0 { + // Transition to manifest sync + self.state = DfuState::DfuManifestSync; + ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + true, + ) + } else { + self.expecting_dnload = true; + self.state = DfuState::DfuDnloadSync; + (UsbAction::None, true) + } + } + DFU_UPLOAD => { + if self.state != DfuState::DfuIdle && self.state != DfuState::DfuUploadIdle { + return (UsbAction::StallInAndOut { endpoint: 0 }, true); + } + self.block_num = pkt.value(); + match self + .handler + .upload(self.alt, self.block_num, &mut self.buffer) + { + Ok(n) => { + self.transfer_offset = 0; + self.transfer_total = n; + // poll_transmit will handle the actual transfer + (UsbAction::None, true) + } + Err(s) => { + self.state = DfuState::DfuError; + self.status = s; + (UsbAction::StallInAndOut { endpoint: 0 }, true) + } + } + } + DFU_GETSTATUS => { + // status[0]: bStatus + // status[1-3]: bwPollTimeout (24-bit, little endian) + // status[4]: bState + // status[5]: iString + self.buffer[0] = self.status as u8; + self.buffer[1] = 0; // bwPollTimeout = 0 + self.buffer[2] = 0; + self.buffer[3] = 0; + self.buffer[4] = self.state as u8; + self.buffer[5] = 0; + + // State transitions after GETSTATUS + match self.state { + DfuState::DfuDnloadSync => self.state = DfuState::DfuDnloadIdle, + DfuState::DfuManifestSync => { + match self.handler.manifest() { + DfuResult::Ok => self.state = DfuState::DfuIdle, // ManifestationTolerant = 1 + DfuResult::Err(s) => { + self.state = DfuState::DfuError; + self.status = s; + } + } + } + _ => {} + } + + ( + UsbAction::TransferInUnaligned { + endpoint: 0, + data: &self.buffer[..6], + zlp: true, + }, + true, + ) + } + DFU_CLRSTATUS => { + self.state = DfuState::DfuIdle; + self.status = DfuStatus::Ok; + ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + true, + ) + } + DFU_GETSTATE => { + self.buffer[0] = self.state as u8; + ( + UsbAction::TransferInUnaligned { + endpoint: 0, + data: &self.buffer[..1], + zlp: true, + }, + true, + ) + } + DFU_ABORT => { + self.handler.abort(); + self.state = DfuState::DfuIdle; + self.status = DfuStatus::Ok; + self.transfer_total = 0; + self.transfer_offset = 0; + ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + true, + ) + } + Request::INTERFACE_SET_INTERFACE => { + self.alt = pkt.value() as u8; + ( + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + true, + ) + } + _ => (UsbAction::None, false), + } + } + + fn handle_control_out<'a>(&'a mut self, pkt: impl UsbPacket) -> UsbAction<'a> { + if !self.expecting_dnload { + return UsbAction::StallInAndOut { endpoint: 0 }; + } + + let data = pkt.copy_to_unaligned(&mut self.buffer[self.transfer_offset..]); + self.transfer_offset += data.len(); + + if self.transfer_offset >= self.transfer_total { + self.expecting_dnload = false; + match self.handler.dnload( + self.alt, + self.block_num, + &self.buffer[..self.transfer_total], + ) { + DfuResult::Ok => UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + }, + DfuResult::Err(s) => { + self.state = DfuState::DfuError; + self.status = s; + UsbAction::StallInAndOut { endpoint: 0 } + } + } + } else { + UsbAction::None + } + } +} + +impl UsbClass for DfuClass +where + H: DfuHandler, +{ + fn handle_event<'a, P: UsbPacket>( + &'a mut self, + event: UsbEvent

, + ) -> Result, UsbEvent

> { + match event { + UsbEvent::SetupPacket { pkt, endpoint } if endpoint == 0 => { + let (action, claimed) = self.handle_setup(pkt); + if claimed { + Ok(action) + } else { + Err(UsbEvent::SetupPacket { pkt, endpoint }) + } + } + UsbEvent::DataOutPacket(pkt) if pkt.endpoint_index() == 0 => { + Ok(self.handle_control_out(pkt)) + } + _ => Err(event), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use aligned::{Aligned, A4}; + use hal_usb::driver::UsbEvent; + use usb_stack::testing::FakeUsbPacket; + + struct MockHandler { + dnload_called: bool, + upload_called: bool, + manifest_called: bool, + abort_called: bool, + } + + impl MockHandler { + fn new() -> Self { + Self { + dnload_called: false, + upload_called: false, + manifest_called: false, + abort_called: false, + } + } + } + + impl DfuHandler for MockHandler { + fn dnload(&mut self, _alt: u8, _block_num: u16, _data: &[u8]) -> DfuResult { + self.dnload_called = true; + DfuResult::Ok + } + fn upload( + &mut self, + _alt: u8, + _block_num: u16, + data: &mut [u8], + ) -> Result { + self.upload_called = true; + data[0] = 0xAA; + Ok(1) + } + fn manifest(&mut self) -> DfuResult { + self.manifest_called = true; + DfuResult::Ok + } + fn abort(&mut self) { + self.abort_called = true; + } + } + + fn setup_packet(req: Request, val: u16, idx: u16, len: u16) -> SetupPacket { + SetupPacket::new([ + (u16::from(req) as u32) | ((val as u32) << 16), + (idx as u32) | ((len as u32) << 16), + ]) + } + + #[test] + fn test_dfu_dnload_sequence() { + let config = DfuBuilder::new(1, 1, 64); + let mut dfu = DfuClass::<_, 64>::new(config, MockHandler::new()); + + // 1. DNLOAD request (block 0, 4 bytes) + let setup = setup_packet(DFU_DNLOAD, 0, 1, 4); + let event: UsbEvent> = UsbEvent::SetupPacket { + endpoint: 0, + pkt: setup, + }; + let action = dfu.handle_event(event).map_err(|_| ()).unwrap(); + assert!(matches!(action, UsbAction::None)); + assert_eq!(dfu.state, DfuState::DfuDnloadSync); + + // 2. Data OUT packet + let data = [1, 2, 3, 4]; + let pkt = FakeUsbPacket { ep: 0, data: &data }; + let action = dfu + .handle_event(UsbEvent::DataOutPacket(pkt)) + .map_err(|_| ()) + .unwrap(); + assert!(matches!(action, UsbAction::TransferIn { endpoint: 0, .. })); + assert!(dfu.handler.dnload_called); + + // 3. GETSTATUS + let setup = setup_packet(DFU_GETSTATUS, 0, 1, 6); + let event: UsbEvent> = UsbEvent::SetupPacket { + endpoint: 0, + pkt: setup, + }; + let action = dfu.handle_event(event).map_err(|_| ()).unwrap(); + assert!(matches!(action, UsbAction::TransferInUnaligned { .. })); + assert_eq!(dfu.state, DfuState::DfuDnloadIdle); + + // 4. DNLOAD ZLP (Manifestation) + let setup = setup_packet(DFU_DNLOAD, 1, 1, 0); + let event: UsbEvent> = UsbEvent::SetupPacket { + endpoint: 0, + pkt: setup, + }; + let action = dfu.handle_event(event).map_err(|_| ()).unwrap(); + assert!(matches!(action, UsbAction::TransferIn { endpoint: 0, .. })); + assert_eq!(dfu.state, DfuState::DfuManifestSync); + + // 5. GETSTATUS (Manifest) + let setup = setup_packet(DFU_GETSTATUS, 0, 1, 6); + let event: UsbEvent> = UsbEvent::SetupPacket { + endpoint: 0, + pkt: setup, + }; + let _ = dfu.handle_event(event).map_err(|_| ()).unwrap(); + assert!(dfu.handler.manifest_called); + assert_eq!(dfu.state, DfuState::DfuIdle); + } + + #[test] + fn test_dfu_upload() { + let config = DfuBuilder::new(1, 1, 64); + let mut dfu = DfuClass::<_, 64>::new(config, MockHandler::new()); + + let setup = setup_packet(DFU_UPLOAD, 0, 1, 64); + let event: UsbEvent> = UsbEvent::SetupPacket { + endpoint: 0, + pkt: setup, + }; + let action = dfu.handle_event(event).map_err(|_| ()).unwrap(); + assert!(matches!(action, UsbAction::None)); + + // Mock driver for poll_transmit + struct MockDriver { + transferred: usize, + } + impl UsbDriver for MockDriver { + const MAX_PACKET_SIZE: usize = 64; + type Packet<'a> = FakeUsbPacket<'a>; + fn transfer_in(&mut self, _ep: u8, _data: &Aligned, _zlp: bool) -> usize { + 0 + } + fn transfer_in_unaligned(&mut self, _ep: u8, data: &[u8], _zlp: bool) -> usize { + self.transferred = data.len(); + data.len() + } + fn stall(&mut self, _ep: u8, _stall: bool) {} + fn is_stalled(&mut self, _ep: u8) -> bool { + false + } + fn set_address(&mut self, _addr: u8) {} + fn poll(&mut self) -> Option>> { + None + } + } + + let mut driver = MockDriver { transferred: 0 }; + dfu.poll_transmit(&mut driver); + assert_eq!(driver.transferred, 1); + assert!(dfu.handler.upload_called); + assert_eq!(dfu.state, DfuState::DfuIdle); // n < transfer_size + } + + #[test] + fn test_dfu_abort() { + let config = DfuBuilder::new(1, 1, 64); + let mut dfu = DfuClass::<_, 64>::new(config, MockHandler::new()); + dfu.state = DfuState::DfuDnloadIdle; + + let setup = setup_packet(DFU_ABORT, 0, 1, 0); + let event: UsbEvent> = UsbEvent::SetupPacket { + endpoint: 0, + pkt: setup, + }; + let action = dfu.handle_event(event).map_err(|_| ()).unwrap(); + assert!(matches!(action, UsbAction::TransferIn { endpoint: 0, .. })); + assert!(dfu.handler.abort_called); + assert_eq!(dfu.state, DfuState::DfuIdle); + } +} diff --git a/protocol/usb/stack/BUILD.bazel b/protocol/usb/stack/BUILD.bazel new file mode 100644 index 00000000..275a7805 --- /dev/null +++ b/protocol/usb/stack/BUILD.bazel @@ -0,0 +1,26 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "stack", + srcs = [ + "lib.rs", + ], + crate_name = "usb_stack", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/usb:hal_usb", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:aligned", + "@rust_crates//:zerocopy", + ], +) + +rust_test( + name = "stack_test", + crate = ":stack", + rustc_flags = [ + "-C", + "debug-assertions", + ], +) diff --git a/protocol/usb/stack/lib.rs b/protocol/usb/stack/lib.rs new file mode 100644 index 00000000..856e8225 --- /dev/null +++ b/protocol/usb/stack/lib.rs @@ -0,0 +1,611 @@ +//! Generic USB protocol stack. +//! +//! This module provides the core logic for a USB device stack, including +//! Endpoint 0 control request handling, descriptor management, and +//! multi-packet transfer accumulation. + +#![no_std] + +use aligned::Aligned; +use aligned::A4; +use core::mem::size_of; +use hal_usb::driver::UsbDriver; +use hal_usb::driver::UsbEvent; +use hal_usb::driver::UsbPacket; +use hal_usb::DescriptorInfo; +use hal_usb::DescriptorType; +use hal_usb::Request; +use hal_usb::SetupPacket; +use hal_usb::StringDescriptorRef; +use hal_usb::StringHandle; +use zerocopy::IntoBytes; + +use pw_status::Error; + +/// A trait for providing USB descriptors to the stack. +/// +/// Applications must implement this trait to define the device's identity +/// and capabilities. +pub trait DescriptorSource { + /// Device descriptor bytes. + const DEVICE_DESC_BYTES: &'static Aligned; + /// Configuration descriptor bytes (including interfaces and endpoints). + const CONFIG_DESC_BYTES: &'static Aligned; + /// String descriptor 0 bytes (supported languages). + const STRING_DESC_0_BYTES: &'static Aligned; + /// Device status bytes (2 bytes, usually [0, 0]). + const DEVICE_STATUS: Aligned; + + /// Returns a string descriptor by handle and language ID. + fn get_string(&self, handle: StringHandle, lang: u16) -> Option>; + /// Returns the device status bytes. + fn get_device_status(&self) -> &Aligned { + &Self::DEVICE_STATUS + } +} + +/// An empty aligned buffer. +pub const EMPTY: &Aligned = &Aligned([]); + +/// A simple implementation of USB Endpoint 0 (control endpoint). +pub struct SimpleEp0 { + new_address: Option, +} + +/// A trait for modular USB class implementations. +pub trait UsbClass { + /// Attempt to handle a USB event. + /// + /// If the event is handled by this class, it returns `Ok(UsbAction)`. + /// Otherwise, it returns the original event in `Err`. + fn handle_event<'a, P: UsbPacket>( + &'a mut self, + event: UsbEvent

, + ) -> Result, UsbEvent

>; +} + +/// Indicates the result of running a USB action. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum UsbActionRun { + /// No operation was performed. + NoOp, + /// The action has more data to transfer. + HasMoreData, + /// The action is complete. + Done, +} + +/// Actions to be performed on a USB driver. +pub enum UsbAction<'a> { + /// No action. + None, + /// Perform an IN transfer on the specified endpoint. + TransferIn { + /// The endpoint index. + endpoint: u8, + /// The data to transfer. + data: &'a Aligned, + /// If true, send a zero-length packet (ZLP) if the data length + /// is a multiple of the maximum packet size. + zlp: bool, + }, + /// Perform an IN transfer on the specified endpoint using unaligned data. + TransferInUnaligned { + /// The endpoint index. + endpoint: u8, + /// The data to transfer. + data: &'a [u8], + /// If true, send a zero-length packet (ZLP) if the data length + /// is a multiple of the maximum packet size. + zlp: bool, + }, + /// Stall both IN and OUT directions on the specified endpoint. + StallInAndOut { + /// The endpoint index. + endpoint: u8, + }, + /// Set the device address. + SetAddress { + /// The new device address. + new_address: u8, + }, + /// Get the status of an endpoint. + GetEndpointStatus { + /// The endpoint index. + endpoint: u8, + }, + /// Set the stall status of an endpoint. + SetEndpointStatus { + /// The endpoint index. + endpoint: u8, + /// Whether to stall or unstall the endpoint. + stall: bool, + }, +} + +impl PartialEq for UsbAction<'_> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::None, Self::None) => true, + ( + Self::TransferIn { + endpoint: e1, + data: d1, + zlp: z1, + }, + Self::TransferIn { + endpoint: e2, + data: d2, + zlp: z2, + }, + ) => e1 == e2 && core::ptr::eq(*d1, *d2) && z1 == z2, + ( + Self::TransferInUnaligned { + endpoint: e1, + data: d1, + zlp: z1, + }, + Self::TransferInUnaligned { + endpoint: e2, + data: d2, + zlp: z2, + }, + ) => e1 == e2 && core::ptr::eq(*d1, *d2) && z1 == z2, + (Self::StallInAndOut { endpoint: e1 }, Self::StallInAndOut { endpoint: e2 }) => e1 == e2, + (Self::SetAddress { new_address: a1 }, Self::SetAddress { new_address: a2 }) => a1 == a2, + ( + Self::GetEndpointStatus { endpoint: e1 }, + Self::GetEndpointStatus { endpoint: e2 }, + ) => e1 == e2, + ( + Self::SetEndpointStatus { + endpoint: e1, + stall: s1, + }, + Self::SetEndpointStatus { + endpoint: e2, + stall: s2, + }, + ) => e1 == e2 && s1 == s2, + _ => false, + } + } +} +impl Eq for UsbAction<'_> {} + +impl<'a> UsbAction<'a> { + const EP_CLEAR: Aligned = Aligned([0u8, 0]); + const EP_HALTED: Aligned = Aligned([1u8, 0]); + + /// Helper to create a TransferIn action for a control transfer, + /// or a StallInAndOut if the requested length is too small. + #[inline(always)] + #[track_caller] + pub fn control_transfer_in_or_stall( + endpoint: u8, + pkt: &SetupPacket, + data: &'a Aligned, + ) -> Self { + if data.len() > pkt.length().into() { + Self::StallInAndOut { endpoint } + } else { + Self::TransferIn { + endpoint, + data, + // Per USB Specs 5.5.3, we need to send ZLP for control transfers + // if the response is less than requested. + zlp: data.is_empty() || data.len() < pkt.length().into(), + } + } + } + /// Merges another action into this one. + pub fn merge(&mut self, new_action: UsbAction<'a>) { + match new_action { + UsbAction::None => {} + _ => *self = new_action, + } + } + + /// Executes the action on the provided driver. + pub fn run(&mut self, driver: &mut TDriver) -> UsbActionRun { + match self { + Self::None => return UsbActionRun::NoOp, + Self::TransferIn { + endpoint, + data, + zlp, + } => { + let bytes_transferred = driver.transfer_in(*endpoint, data, *zlp); + // Note: bytes_transferred is guaranteed to be a multiple of + // UsbDriver::MAX_PACKET_SIZE, which is guaranteed to be a + // multiple of 4. + if bytes_transferred < data.len() && (bytes_transferred & 3) == 0 { + // We're not done yet... + *data = &data[bytes_transferred..]; + return UsbActionRun::HasMoreData; + } + } + Self::TransferInUnaligned { + endpoint, + data, + zlp, + } => { + let bytes_transferred = driver.transfer_in_unaligned(*endpoint, data, *zlp); + if bytes_transferred < data.len() { + // We're not done yet... + *data = &data[bytes_transferred..]; + return UsbActionRun::HasMoreData; + } + } + Self::SetAddress { new_address } => driver.set_address(*new_address), + Self::StallInAndOut { endpoint } => { + driver.stall((*endpoint) & 0x7f, true); + driver.stall((*endpoint) | 0x80, true); + } + Self::GetEndpointStatus { endpoint } => { + let data = if driver.is_stalled(*endpoint) { + &Self::EP_HALTED + } else { + &Self::EP_CLEAR + }; + let _ = driver.transfer_in(0, data, true); + } + Self::SetEndpointStatus { endpoint, stall } => { + driver.stall(*endpoint, *stall); + let _ = driver.transfer_in(0, EMPTY, true); + } + } + *self = UsbAction::None; + UsbActionRun::Done + } +} + +impl SimpleEp0 { + /// Creates a new `SimpleEp0` handler. + pub fn new() -> Self { + Self { new_address: None } + } + /// A helper function to process a driver UsbEvent. + /// + /// This function returns the action that should be performed on the driver. + pub fn handle_event<'a, P: UsbPacket>( + &mut self, + ev: UsbEvent

, + descriptor_source: &'a impl DescriptorSource, + ) -> Result, UsbEvent

> { + match ev { + UsbEvent::SetupPacket { endpoint, pkt } if endpoint == 0 => { + use hal_usb::RequestType; + if pkt.request().request_type() == RequestType::Standard { + Ok(self.handle_setup(pkt, descriptor_source)) + } else { + Err(ev) + } + } + UsbEvent::PacketSent { endpoint } if endpoint == 0 => Ok(self.handle_packet_sent()), + _ => Err(ev), + } + } + + /// Process a SETUP transfer and return the resulting action. + fn handle_setup<'a, TDescriptorSource: DescriptorSource>( + &mut self, + setup_pkt: SetupPacket, + descriptor_source: &'a TDescriptorSource, + ) -> UsbAction<'a> { + match setup_pkt.request() { + Request::DEVICE_GET_DESCRIPTOR => { + let descriptor = DescriptorInfo::from(&setup_pkt); + #[rustfmt::skip] + let mut response: Option<&Aligned> = match descriptor { + DescriptorInfo { ty: DescriptorType::DEVICE, index: 0, .. } => { + Some(TDescriptorSource::DEVICE_DESC_BYTES) + } + DescriptorInfo { ty: DescriptorType::CONFIGURATION, index: 0, .. } => { + Some(TDescriptorSource::CONFIG_DESC_BYTES) + } + DescriptorInfo { ty: DescriptorType::STRING, index: 0, .. } => { + Some(TDescriptorSource::STRING_DESC_0_BYTES) + } + DescriptorInfo { ty: DescriptorType::STRING, index, .. } => { + descriptor_source + .get_string(StringHandle(index), setup_pkt.index()) + .map(|desc| desc.as_bytes()) + } + _ => None, + }; + if let Some(response) = &mut response { + if response.len() > setup_pkt.length().into() { + *response = &(*response)[..setup_pkt.length().into()]; + } + UsbAction::control_transfer_in_or_stall(0, &setup_pkt, response) + } else { + UsbAction::StallInAndOut { endpoint: 0 } + } + } + Request::DEVICE_GET_STATUS => UsbAction::TransferIn { + endpoint: 0, + data: descriptor_source.get_device_status(), + zlp: true, + }, + Request::ENDPOINT_GET_STATUS => UsbAction::GetEndpointStatus { + endpoint: setup_pkt.index() as u8, + }, + Request::ENDPOINT_SET_FEATURE => UsbAction::SetEndpointStatus { + endpoint: setup_pkt.index() as u8, + stall: true, + }, + Request::ENDPOINT_CLEAR_FEATURE => UsbAction::SetEndpointStatus { + endpoint: setup_pkt.index() as u8, + stall: false, + }, + Request::DEVICE_SET_ADDRESS => { + self.new_address = Some(setup_pkt.value() as u8); + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + } + } + Request::DEVICE_SET_CONFIGURATION => { + if setup_pkt.value() == 1 { + UsbAction::TransferIn { + endpoint: 0, + data: EMPTY, + zlp: true, + } + } else { + UsbAction::StallInAndOut { endpoint: 0 } + } + } + _ => UsbAction::StallInAndOut { endpoint: 0 }, + } + } + fn handle_packet_sent(&mut self) -> UsbAction<'static> { + if let Some(new_address) = self.new_address.take() { + // Now that the transfer is complete it's safe to change the address.. + return UsbAction::SetAddress { new_address }; + } + UsbAction::None + } +} + +impl Default for SimpleEp0 { + fn default() -> Self { + Self::new() + } +} + +/// A helper struct to handle multi-packet USB transfers. +/// +/// It accumulates incoming USB packets into an internal buffer until a short +/// packet or a zero-length packet (ZLP) is received, indicating the end of a transfer. +/// +/// `N` is the number of **words** (`u32`s) in the internal buffer and NOT bytes. +#[derive(Debug, PartialEq, Eq)] +pub struct Transfer { + buffer: [u32; N], + word_offset: usize, +} + +impl Transfer { + /// Maximum packet size supported (fixed at 64 bytes). + pub const MAX_PACKET_SIZE: usize = 64; + + /// Creates a new `Transfer` buffer. + pub fn new() -> Self { + Self { + buffer: [0; N], + word_offset: 0, + } + } + + /// Splices a USB packet into the buffer. + /// + /// Returns `Ok(Some(slice))` if the transfer is complete, `Ok(None)` otherwise. + pub fn splice(&mut self, packet: impl UsbPacket) -> Result>, Error> { + const { + assert!(Self::MAX_PACKET_SIZE % size_of::() == 0); + } + let packet_len = packet.len(); + let dest = { + let start = self.word_offset; + let end = start + packet_len.div_ceil(size_of::()); + self.buffer.get_mut(start..end).ok_or(Error::OutOfRange)? + }; + packet.copy_to(dest); + if packet_len < Self::MAX_PACKET_SIZE { + let result = &self + .buffer + .as_bytes() + .get(..self.word_offset * size_of::() + packet_len) + .ok_or(Error::OutOfRange)?; + self.word_offset = 0; + // This is safe because `self.buffer` is `[u32]` which has alignment of 4. + Ok(Some(unsafe { + core::mem::transmute::<&[u8], &Aligned>(result) + })) + } else { + self.word_offset += Self::MAX_PACKET_SIZE / size_of::(); + Ok(None) + } + } +} + +impl Default for Transfer { + fn default() -> Self { + Self::new() + } +} + +pub mod testing { + use aligned::Aligned; + use aligned::A4; + use hal_usb::driver::UsbPacket; + use zerocopy::IntoBytes; + + #[derive(Debug)] + pub struct FakeUsbPacket<'a> { + pub data: &'a [u8], + pub ep: usize, + } + + impl UsbPacket for FakeUsbPacket<'_> { + fn endpoint_index(&self) -> usize { + self.ep + } + + fn len(&self) -> usize { + self.data.len() + } + + fn copy_to_uninit(self, _dest: &mut [core::mem::MaybeUninit]) -> &[u8] { + unimplemented!() + } + + fn copy_to(self, dest: &mut [u32]) -> &[u8] { + let dest_bytes = dest.as_mut_bytes(); + let copy_len = self.data.len().min(dest_bytes.len()); + dest_bytes[..copy_len].copy_from_slice(&self.data[..copy_len]); + // This is safe because `dest` is a `&mut [u32]`, which is guaranteed to be 4-byte + // aligned. `dest_bytes` is a byte slice view of the same memory, so it's also + // 4-byte aligned. The subslice `&dest_bytes[..copy_len]` maintains this alignment. + unsafe { core::mem::transmute::<&[u8], &Aligned>(&dest_bytes[..copy_len]) } + } + + fn copy_to_unaligned(self, dest: &mut [u8]) -> &[u8] { + let copy_len = self.data.len().min(dest.len()); + dest[..copy_len].copy_from_slice(&self.data[..copy_len]); + &dest[..copy_len] + } + } +} + +#[cfg(test)] +mod splice_tests { + use super::testing::FakeUsbPacket; + use super::*; + + const MAX_PACKET_SIZE: usize = Transfer::<0>::MAX_PACKET_SIZE; + + #[test] + fn test_splice_single_short_packet() { + let packet_data = [1, 2, 3, 4]; + let packet = FakeUsbPacket { + data: &packet_data, + ep: 0, + }; + + let mut transfer = Transfer::<32>::new(); + let result = transfer.splice(packet).unwrap(); + + assert!(result.is_some()); + assert_eq!(result.unwrap().as_ref(), &packet_data[..]); + } + + #[test] + fn test_splice_single_full_packet_then_zlp() { + let packet_data = [42; MAX_PACKET_SIZE]; + let packet = FakeUsbPacket { + data: &packet_data, + ep: 0, + }; + + let mut transfer = Transfer::<32>::new(); + let result = transfer.splice(packet).unwrap(); + + assert!(result.is_none()); + + let zlp = FakeUsbPacket { data: &[], ep: 0 }; + let result = transfer.splice(zlp).unwrap(); + assert!(result.is_some()); + assert_eq!(result.unwrap().as_ref(), &packet_data[..]); + } + + #[test] + fn test_splice_multiple_packets() { + let packet1_data = [1; MAX_PACKET_SIZE]; + let packet2_data = [2; MAX_PACKET_SIZE]; + let packet3_data = [3; 32]; + + // Packet 1 + let packet1 = FakeUsbPacket { + data: &packet1_data, + ep: 0, + }; + let mut transfer = Transfer::<64>::new(); + let result = transfer.splice(packet1).unwrap(); + assert!(result.is_none()); + + // Packet 2 + let packet2 = FakeUsbPacket { + data: &packet2_data, + ep: 0, + }; + let result = transfer.splice(packet2).unwrap(); + assert!(result.is_none()); + + // Packet 3 (short packet) + let packet3 = FakeUsbPacket { + data: &packet3_data, + ep: 0, + }; + let result = transfer.splice(packet3).unwrap(); + assert!(result.is_some()); + + let mut expected_data = [0u8; 2 * MAX_PACKET_SIZE + 32]; + expected_data[..MAX_PACKET_SIZE].copy_from_slice(&packet1_data); + expected_data[MAX_PACKET_SIZE..2 * MAX_PACKET_SIZE].copy_from_slice(&packet2_data); + expected_data[2 * MAX_PACKET_SIZE..].copy_from_slice(&packet3_data); + + assert_eq!(result.unwrap().as_ref(), &expected_data[..]); + } + + #[test] + fn test_splice_buffer_overflow() { + let packet_data = [1; 1]; + let packet = FakeUsbPacket { + data: &packet_data, + ep: 0, + }; + + let mut transfer = Transfer::<16>::new(); + transfer + .splice(FakeUsbPacket { + data: &[0; MAX_PACKET_SIZE], + ep: 0, + }) + .unwrap(); + let result = transfer.splice(packet); + assert_eq!(result.err(), Some(Error::OutOfRange)); + } + + #[test] + fn test_full_capacity_with_full_packets_then_partial_packet() { + const PARTIAL_SIZE: usize = 16; + const FULL1_DATA: &[u8] = &[0xaa; MAX_PACKET_SIZE]; + const FULL2_DATA: &[u8] = &[0xbb; MAX_PACKET_SIZE]; + const PARTIAL_DATA: &[u8] = &[0xcc; PARTIAL_SIZE]; + const RECEIVE_BUFFER_WORDS: usize = + (FULL1_DATA.len() + FULL2_DATA.len() + PARTIAL_DATA.len()) / size_of::(); + let full1 = FakeUsbPacket { + data: FULL1_DATA, + ep: 0, + }; + let full2 = FakeUsbPacket { + data: FULL2_DATA, + ep: 0, + }; + let partial = FakeUsbPacket { + data: PARTIAL_DATA, + ep: 0, + }; + let mut transfer = Transfer::::new(); + assert!(transfer.splice(full1).unwrap().is_none()); + assert!(transfer.splice(full2).unwrap().is_none()); + let buffer = transfer.splice(partial).unwrap().unwrap().as_ref(); + assert_eq!(&buffer[..MAX_PACKET_SIZE], FULL1_DATA); + assert_eq!(&buffer[MAX_PACKET_SIZE..2 * MAX_PACKET_SIZE], FULL2_DATA); + assert_eq!(&buffer[2 * MAX_PACKET_SIZE..], PARTIAL_DATA); + } +} diff --git a/services/flash/BUILD.bazel b/services/flash/BUILD.bazel new file mode 100644 index 00000000..4195c54c --- /dev/null +++ b/services/flash/BUILD.bazel @@ -0,0 +1,57 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "opcode", + srcs = [ + "opcode.rs", + ], + crate_name = "services_flash_opcode", + edition = "2024", + deps = [ + "//util/types", + "@rust_crates//:zerocopy", + ], +) + +rust_library( + name = "client", + srcs = [ + "client.rs", + ], + crate_name = "services_flash_client", + edition = "2024", + deps = [ + ":opcode", + "//hal/blocking/flash", + "//util/error", + "//util/ipc", + "//util/types", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@rust_crates//:zerocopy", + ], +) + +rust_library( + name = "server", + srcs = [ + "server.rs", + ], + crate_name = "services_flash_server", + edition = "2024", + deps = [ + ":opcode", + "//hal/blocking/flash", + "//util/error", + "//util/ipc", + "//util/types", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@rust_crates//:zerocopy", + ], +) diff --git a/services/flash/client.rs b/services/flash/client.rs new file mode 100644 index 00000000..e1a38068 --- /dev/null +++ b/services/flash/client.rs @@ -0,0 +1,88 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +use core::num::NonZero; + +use hal_flash::{Flash, FlashAddress}; +use services_flash_opcode::*; +use userspace::time::Instant; +use util_error::{self as error, ErrorCode}; +use util_ipc::IpcChannel; +use util_types::PowerOf2Usize; +use zerocopy::{FromZeros, IntoBytes}; + +pub struct FlashIpcClient { + ipc: IpcChannel, + page_size: PowerOf2Usize, + total_size: NonZero, +} + +impl FlashIpcClient { + pub fn new(ipc: IpcChannel) -> Result { + let mut info = FlashInfo::new_zeroed(); + let mut result = 0u32; + + ipc.transaction::<12>( + &[IPC_OP_FLASH_GET_INFO.as_bytes()], + &mut [result.as_mut_bytes(), info.as_mut_bytes()], + Instant::MAX, + )?; + IpcChannel::check_status(result)?; + + let Some(page_size) = PowerOf2Usize::new(info.page_size) else { + return Err(error::FLASH_GENERIC_INVALID_PAGE_SIZE); + }; + let Some(total_size) = NonZero::new(info.total_size) else { + return Err(error::FLASH_GENERIC_INVALID_SIZE); + }; + Ok(Self { + ipc, + page_size, + total_size, + }) + } +} + +impl Flash for FlashIpcClient { + fn page_size(&self) -> PowerOf2Usize { + self.page_size + } + fn size(&self) -> core::num::NonZero { + self.total_size + } + fn erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode> { + let mut result = 0u32; + self.ipc.transaction::<12>( + &[IPC_OP_FLASH_ERASE_PAGE.as_bytes(), start_addr.as_bytes()], + &mut [result.as_mut_bytes()], + Instant::MAX, + )?; + IpcChannel::check_status(result) + } + + fn program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), ErrorCode> { + let mut result = 0u32; + self.ipc.transaction::<2056>( + &[IPC_OP_FLASH_PROGRAM.as_bytes(), start_addr.as_bytes(), data], + &mut [result.as_mut_bytes()], + Instant::MAX, + )?; + IpcChannel::check_status(result) + } + + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode> { + let mut result = 0u32; + let length = buf.len(); + self.ipc.transaction::<2056>( + &[ + IPC_OP_FLASH_READ.as_bytes(), + start_addr.as_bytes(), + length.as_bytes(), + ], + &mut [result.as_mut_bytes(), buf], + Instant::MAX, + )?; + IpcChannel::check_status(result) + } +} diff --git a/services/flash/opcode.rs b/services/flash/opcode.rs new file mode 100644 index 00000000..54e107fd --- /dev/null +++ b/services/flash/opcode.rs @@ -0,0 +1,19 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use util_types::Opcode; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +pub const IPC_OP_FLASH_ERASE_PAGE: Opcode = Opcode::new(*b"FLEP"); +pub const IPC_OP_FLASH_PROGRAM: Opcode = Opcode::new(*b"FLWR"); +pub const IPC_OP_FLASH_READ: Opcode = Opcode::new(*b"FLRD"); +pub const IPC_OP_FLASH_GET_INFO: Opcode = Opcode::new(*b"FLIN"); + +#[derive(FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct FlashInfo { + pub page_size: usize, + pub total_size: usize, +} diff --git a/services/flash/server.rs b/services/flash/server.rs new file mode 100644 index 00000000..eee43b7d --- /dev/null +++ b/services/flash/server.rs @@ -0,0 +1,92 @@ +#![no_std] +//use core::num::NonZero; + +use hal_flash::{Flash, FlashAddress}; +use services_flash_opcode::*; +use util_error::{self as error, ErrorCode}; +use util_ipc::IpcChannel; +use util_types::Opcode; +use zerocopy::{FromBytes, IntoBytes}; + +pub struct FlashIpcServer { + flash: TFlash, +} + +impl FlashIpcServer { + pub fn new(flash: TFlash) -> Self { + Self { flash } + } + + fn handle_get_info<'a>(&self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + let (info, _rest) = + FlashInfo::mut_from_prefix(data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + info.page_size = self.flash.page_size().get(); + info.total_size = self.flash.size().get(); + Ok(info.as_bytes()) + } + + fn handle_erase<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + let (addr, data) = + FlashAddress::read_from_prefix(data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + self.flash.erase_page(addr)?; + Ok(&data[0..0]) + } + + fn handle_program<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + let (addr, data) = + FlashAddress::mut_from_prefix(data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + self.flash.program(*addr, data)?; + Ok(&data[0..0]) + } + + fn handle_read<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + let addr = + FlashAddress::read_from_bytes(&data[0..4]).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let length = + usize::read_from_bytes(&data[4..8]).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + self.flash.read(addr, &mut data[..length])?; + Ok(&data[..length]) + } + + fn handle_op<'a>(&mut self, opcode: Opcode, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + match opcode { + IPC_OP_FLASH_GET_INFO => self.handle_get_info(data), + IPC_OP_FLASH_ERASE_PAGE => self.handle_erase(data), + IPC_OP_FLASH_PROGRAM => self.handle_program(data), + IPC_OP_FLASH_READ => self.handle_read(data), + _ => Err(error::IPC_ERROR_UNKNOWN_OP), + } + } + + fn handle_one(&mut self, ipc: &IpcChannel, data: &mut [u8]) -> Result<(), ErrorCode> { + //pw_log::info!("ipc_wait"); + ipc.wait_readable()?; + //pw_log::info!("ipc_read"); + let len = ipc.read(0, data)?; + //pw_log::info!("ipc_exec"); + if len < 4 { + return Err(error::IPC_ERROR_BAD_REQ_LEN); + } + let (op_status, reqrsp) = data.split_at_mut(4); + let opcode = Opcode::read_from_bytes(op_status).unwrap(); + let len = match self.handle_op(opcode, reqrsp) { + Ok(result) => { + op_status.copy_from_slice((0u32).as_bytes()); + result.len() + } + Err(e) => { + op_status.copy_from_slice(e.0.as_bytes()); + 0 + } + }; + //pw_log::info!("ipc_respond: {}", len as usize); + ipc.respond(&data[..4 + len])?; + Ok(()) + } + + pub fn run(&mut self, ipc: &IpcChannel, data: &mut [u8]) -> Result<(), ErrorCode> { + loop { + self.handle_one(ipc, data)?; + } + } +} diff --git a/target/earlgrey/BUILD.bazel b/target/earlgrey/BUILD.bazel index ccad4b87..a0dbc348 100644 --- a/target/earlgrey/BUILD.bazel +++ b/target/earlgrey/BUILD.bazel @@ -29,7 +29,7 @@ platform( "@pigweed//pw_log/rust:pw_log_backend": "@pigweed//pw_kernel:log_backend_basic", }, ), - visibility = [":__subpackages__"], + visibility = ["//visibility:public"], ) string_flag( @@ -60,7 +60,7 @@ config_setting( constraint_value( name = "target_earlgrey", constraint_setting = "@pigweed//pw_kernel/target:target", - visibility = [":__subpackages__"], + visibility = ["//visibility:public"], ) rust_library( diff --git a/target/earlgrey/drivers/BUILD.bazel b/target/earlgrey/drivers/BUILD.bazel new file mode 100644 index 00000000..095ac7c8 --- /dev/null +++ b/target/earlgrey/drivers/BUILD.bazel @@ -0,0 +1,48 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") + +rust_library( + name = "eflash_driver", + srcs = ["eflash_driver.rs"], + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash:driver", + "//target/earlgrey/registers:flash_ctrl_core", + "//target/earlgrey/util", + "//util/error", + "//util/regcpy", + "@rust_crates//:zerocopy", + "@ureg", + ], +) + +rust_library( + name = "usb_driver", + srcs = ["usb_driver.rs"], + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/usb:hal_usb", + "//target/earlgrey/registers:usbdev", + "//util/console", + "//util/regcpy", + "@rust_crates//:aligned", + "@rust_crates//:ufmt", + "@rust_crates//:zerocopy", + "@ureg", + ], +) + +rust_library( + name = "uart_receiver", + srcs = ["uart_receiver.rs"], + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//target/earlgrey/registers:uart", + "@ureg", + ], +) diff --git a/target/earlgrey/drivers/eflash_driver.rs b/target/earlgrey/drivers/eflash_driver.rs new file mode 100644 index 00000000..a15ad9d0 --- /dev/null +++ b/target/earlgrey/drivers/eflash_driver.rs @@ -0,0 +1,256 @@ +#![no_std] + +use core::num::NonZero; + +use earlgrey_util::AsMubi; +use flash_ctrl_core::{self, regs::ControlWriteVal}; +use hal_flash_driver::{FlashAddress, FlashDriver}; +use util_error::{self as error, ErrorCode}; +use util_regcpy::{copy_from_reg_unaligned, copy_to_reg_unaligned}; + +pub struct Permission { + pub read: bool, + pub write: bool, + pub erase: bool, +} + +impl Permission { + pub const FULL_ACCESS: Permission = Permission { + read: true, + write: true, + erase: true, + }; + pub const READ_ONLY: Permission = Permission { + read: true, + write: false, + erase: false, + }; +} + +const FLASH_SIZE: NonZero = NonZero::new(1024 * 1024).unwrap(); + +pub struct EmbeddedFlash { + mmio: flash_ctrl_core::FlashCtrl, + busy: bool, +} +impl EmbeddedFlash { + // TODO: unify these with the trait consts. + const BYTES_PER_BANK: u32 = 0x80000; + const BYTES_PER_PAGE: u32 = 2048; + + pub fn new(mmio: flash_ctrl_core::FlashCtrl) -> Self { + Self { mmio, busy: false } + } + + pub fn new_with_interrupts(mut mmio: flash_ctrl_core::FlashCtrl) -> Self { + mmio.regs_mut().intr_state().write(|w| w.op_done_clear()); + mmio.regs_mut().intr_enable().write(|w| w.op_done(true)); + Self { mmio, busy: false } + } + + pub fn set_default_permission(&mut self, perm: Permission) { + let reg = self.mmio.regs_mut(); + reg.default_region().modify(|v| { + v.rd_en(perm.read.as_mubi()) + .prog_en(perm.write.as_mubi()) + .erase_en(perm.erase.as_mubi()) + }); + } + + pub fn set_info_permission( + &mut self, + address: FlashAddress, + perm: Permission, + ) -> Result<(), ErrorCode> { + if !address.is_info() { + return Err(error::FLASH_GENERIC_ADDR_OUT_OF_BOUNDS); + } + let reg = self.mmio.regs_mut(); + if address.bank() == 0 { + reg.bank0_info0_page_cfg() + .at(address.page() as usize) + .modify(|v| { + v.en(true.as_mubi()) + .rd_en(perm.read.as_mubi()) + .rd_en(perm.read.as_mubi()) + .prog_en(perm.write.as_mubi()) + .erase_en(perm.erase.as_mubi()) + }); + } else { + reg.bank1_info0_page_cfg() + .at(address.page() as usize) + .modify(|v| { + v.en(true.as_mubi()) + .rd_en(perm.read.as_mubi()) + .prog_en(perm.write.as_mubi()) + .erase_en(perm.erase.as_mubi()) + }); + } + Ok(()) + } +} + +//fn u32_from_usize(val: usize) -> u32 { +// u32::try_from(val).unwrap() +//} +impl FlashDriver for EmbeddedFlash { + // BytesPerWord: 8 + // WordsPerPage: 256 + // BytesPerBank: 524288 + // program_resolution: 8 (max flash words to program at one time) + // RegBusPgmResBytes = 64 + const PAGE_SIZE: usize = 2048; + const PROGRAM_WINDOW_SIZE: usize = 64; + const MAX_READ_SIZE: usize = 4096; + const READ_ALIGNMENT: usize = 4; + const PROGRAM_ALIGNMENT: usize = 8; + + fn size(&self) -> core::num::NonZero { + FLASH_SIZE + } + + #[inline(never)] + fn read(&mut self, start_addr: FlashAddress, buf: &mut [u8]) -> Result<(), ErrorCode> { + if buf.is_empty() { + return Ok(()); + } + let start_offset = if start_addr.is_info() { + start_addr.bank() * Self::BYTES_PER_BANK + + start_addr.page() * Self::BYTES_PER_PAGE + + start_addr.offset() + } else { + start_addr.offset() + }; + + if (start_offset & 3) != 0 { + return Err(error::FLASH_GENERIC_BAD_ALIGNMENT); + } + if buf.len() > Self::MAX_READ_SIZE { + return Err(error::FLASH_GENERIC_READ_TOO_LONG); + } + + self.check_busy()?; + self.mmio.regs_mut().addr().write(|w| w.start(start_offset)); + self.start_op(|w| { + w.op(|s| s.read()) + .partition_sel(start_addr.is_info()) + .num((buf.len() as u32 + 3) / 4 - 1) + }); + copy_from_reg_unaligned(buf, &self.mmio.regs_mut().rd_fifo()); + while self.is_busy() {} + self.complete_op() + } + + fn start_erase_page(&mut self, start_addr: FlashAddress) -> Result<(), ErrorCode> { + const { + // Confirm this is a power of two + assert!(Self::PAGE_SIZE.count_ones() == 1); + } + let start_offset = if start_addr.is_info() { + start_addr.bank() * Self::BYTES_PER_BANK + + start_addr.page() * Self::BYTES_PER_PAGE + + start_addr.offset() + } else { + start_addr.offset() + }; + let start_offset = start_offset as usize; + + if start_offset & (Self::PAGE_SIZE - 1) != 0 { + return Err(error::FLASH_GENERIC_ERASE_INVALID_ADDR); + } + self.check_busy()?; + + self.mmio + .regs_mut() + .addr() + .write(|w| w.start(start_offset as u32)); + self.start_op(|w| { + w.op(|s| s.erase()) + .erase_sel(|s| s.page_erase()) + .partition_sel(start_addr.is_info()) + .start(true) + }); + Ok(()) + } + + fn start_program(&mut self, start_addr: FlashAddress, data: &[u8]) -> Result<(), ErrorCode> { + if data.is_empty() { + return Ok(()); + } + if data.len() > Self::PROGRAM_WINDOW_SIZE { + return Err(error::FLASH_GENERIC_PROGRAM_EXCEEDS_WINDOW_SIZE); + } + let start_offset = if start_addr.is_info() { + start_addr.bank() * Self::BYTES_PER_BANK + + start_addr.page() * Self::BYTES_PER_PAGE + + start_addr.offset() + } else { + start_addr.offset() + }; + let start_offset = start_offset as usize; + let end_offset = start_offset.wrapping_add(data.len()); + + if start_offset / Self::PROGRAM_WINDOW_SIZE != (end_offset - 1) / Self::PROGRAM_WINDOW_SIZE + { + return Err(error::FLASH_GENERIC_PROGRAM_SPANS_WINDOW_BOUNDARY); + } + self.check_busy()?; + // reset the op status register + + self.mmio + .regs_mut() + .addr() + .write(|w| w.start(start_offset as u32)); + self.start_op(|w| { + w.op(|s| s.prog()) + .prog_sel(|s| s.normal_program()) + .partition_sel(start_addr.is_info()) + .num(((data.len() + 3) / 4) as u32 - 1) + }); + copy_to_reg_unaligned(&self.mmio.regs_mut().prog_fifo(), data); + Ok(()) + } + + fn is_busy(&mut self) -> bool { + if self.busy && self.mmio.regs_mut().intr_state().read().op_done() { + self.busy = false; + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.op_done_clear()); + } + self.busy + } + + fn complete_op(&mut self) -> Result<(), ErrorCode> { + if self.is_busy() { + return Err(error::FLASH_GENERIC_BUSY); + } + let status = self.mmio.regs().op_status().read(); + if status.err() { + let err_code = u32::from(self.mmio.regs().err_code().read()); + Err(error::FLASH_OPENTITAN.error(err_code as u16)) + } else { + Ok(()) + } + } +} +impl EmbeddedFlash { + fn start_op(&mut self, f: impl FnOnce(ControlWriteVal) -> ControlWriteVal) { + self.busy = true; + self.mmio.regs_mut().err_code().write(|_| 0xff.into()); + self.mmio.regs_mut().op_status().write(|w| w); + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.op_done_clear()); + self.mmio.regs_mut().control().write(|w| f(w).start(true)); + } + fn check_busy(&mut self) -> Result<(), ErrorCode> { + if self.is_busy() { + Err(error::FLASH_GENERIC_BUSY) + } else { + Ok(()) + } + } +} diff --git a/target/earlgrey/drivers/uart_receiver.rs b/target/earlgrey/drivers/uart_receiver.rs new file mode 100644 index 00000000..5df05a02 --- /dev/null +++ b/target/earlgrey/drivers/uart_receiver.rs @@ -0,0 +1,33 @@ +#![no_std] +use uart; + +pub struct UartReceiver { + regs: uart::RegisterBlock>, +} + +impl UartReceiver { + pub unsafe fn new(ptr: *mut u32) -> Self { + Self { + regs: unsafe { uart::RegisterBlock::new(ptr) }, + } + } + + pub fn enable_receiver(&mut self) { + self.regs.ctrl().modify(|ctrl| ctrl.rx(true)); + } + + pub fn enable_interrupt(&mut self) { + self.regs.intr_enable().modify(|en| en.rx_watermark(true)); + self.regs.fifo_ctrl() + .modify(|fifo| fifo.rxilvl(|lvl| lvl.rxlvl1())); + } + + pub fn receive(&mut self) -> Option { + if !self.regs.status().read().rxempty() { + let value = u32::from(self.regs.rdata().read()); + Some(value as u8) + } else { + None + } + } +} diff --git a/target/earlgrey/drivers/usb_driver.rs b/target/earlgrey/drivers/usb_driver.rs new file mode 100644 index 00000000..85cd947b --- /dev/null +++ b/target/earlgrey/drivers/usb_driver.rs @@ -0,0 +1,1131 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use aligned::A4; +use aligned::Aligned; +use core::cmp::min; +use util_regcpy::{copy_to_reg_array, copy_to_reg_array_unaligned, copy_from_reg_array_unaligned}; +use util_console::traceln; +use hal_usb::SetupPacket; +use hal_usb::driver::UsbDriver; +use hal_usb::driver::UsbEvent; +use hal_usb::driver::UsbPacket; +use zerocopy::IntoBytes; + +const MAX_PACKET_SIZE: usize = 64; +const BUFFER_SLOT_SIZE_WORDS: usize = MAX_PACKET_SIZE / 4; +const BUFFER_SLOT_COUNT: usize = 32; + + +use buf_pool::BufId; +use buf_pool::BufPool; +use buf_pool::BuffPoolAllocator; +use core::cmp; +use ureg::RealMmio; + +use crate::transmit_queue::TransmitQueues; + +pub struct PacketHandle { + // A reference to 16 words of packet data in the peripheral MMIO memory. + data: ureg::Array<16, ureg::RegRef, TMmio>>, + // The length of the packet in bytes + packet_len: u16, + ep: u8, +} + +impl UsbPacket for PacketHandle { + fn endpoint_index(&self) -> usize { + self.ep.into() + } + fn len(&self) -> usize { + self.packet_len.into() + } + + fn copy_to_uninit(self, dest: &mut [core::mem::MaybeUninit]) -> &[u8] { + #![allow(clippy::needless_range_loop)] + + // TODO: Are we sure we want to silently truncate if dest isn't big enough? + let word_len = min(min(dest.len(), MAX_PACKET_SIZE / 4), self.len().div_ceil(4)); + for i in 0..word_len { + dest[i].write(self.data.at(i).read()); + } + //let result = unsafe { mutask_subtle::slice_assume_init(&dest[..word_len]) }; + + // This is feature(maybe_uninit_slice). + let result = &dest[..word_len]; + let result = unsafe { &*(result as *const [core::mem::MaybeUninit] as *const [u32]) }; + + &result.as_bytes()[..min(self.len(), word_len * 4)] + } + + fn copy_to(self, dest: &mut [u32]) -> &[u8] { + #![allow(clippy::needless_range_loop)] + + // TODO: Are we sure we want to silently truncate if dest isn't big enough? + let word_len = min(min(dest.len(), MAX_PACKET_SIZE / 4), self.len().div_ceil(4)); + for i in 0..word_len { + dest[i] = self.data.at(i).read(); + } + &dest.as_bytes()[..min(self.len(), dest.as_bytes().len())] + } + + fn copy_to_unaligned(self, dest: &mut [u8]) -> &[u8] { + copy_from_reg_array_unaligned(dest, &self.data); + &dest[..min(self.len(), dest.len())] + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct NextInPacket { + buf_id: u8, + len: u8, +} +impl NextInPacket { + const NONE: Self = Self { + buf_id: 0xff, + len: 0xff, + }; +} + +#[derive(Clone, Copy)] +pub struct EpIn { + pub num: u8, + pub buf_pool_size: u32, +} + +#[derive(Clone, Copy)] +pub struct EpOut { + pub num: u8, + /// If true, hardware will NAK OUT transfers on this endpoint after the first + /// until software re-enables by setting `rxenable_out` + pub set_nak: bool, +} + +const NB_EP: usize = 12; + +pub struct Usb { + mmio: usbdev::Usbdev, + + // buffer pool for SETUP transfers from host, technically common, but only used for EP0. + buf_pool_setup: BufPool, + // buffer pool for OUT transfers from host, common for all EPs. + buf_pool_out: BufPool, + + // buffer pools for IN transfers. + buf_pools_in: [BufPool; NB_EP], + + transmit_queues: TransmitQueues, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UsbConfig { + buf_pool_setup: BufPool, + buf_pool_out: BufPool, + buf_pools_in: [BufPool; NB_EP], + in_mask: u32, + out_mask: u32, + set_nak_mask: u32, +} +impl UsbConfig { + /// Construct a USB config. This should typically be done within a const + /// block so any errors become compile-time panics. + /// + /// # Panic + /// + /// This function will panic if the supplied endpoints are invalid. + #[inline(always)] + pub const fn new(eps_in: &[EpIn], eps_out: &[EpOut]) -> Self { + let mut buf_pool_allocator = BuffPoolAllocator::new(); + let buf_pool_setup = buf_pool_allocator.new_bufpool(4).unwrap(); + let buf_pool_out = buf_pool_allocator.new_bufpool(12).unwrap(); + + let mut buf_pools_in = [BufPool::EMPTY; NB_EP]; + let mut i = 0; + while i < eps_in.len() { + let ep = &eps_in[i]; + if ep.num == 0 || ep.num as usize > NB_EP { + panic!("Invalid endpoint number"); + } + let Some(new_pool) = buf_pool_allocator.new_bufpool(ep.buf_pool_size) else { + panic!("Bufpool allocation overflow"); + }; + buf_pools_in[ep.num as usize] = new_pool; + i += 1; + } + let Some(new_pool) = buf_pool_allocator.remainder_bufpool() else { + panic!("Bufpool allocation overflow"); + }; + buf_pools_in[0] = new_pool; + + let in_mask: u32 = { + let mut v = 1; // always enable EP0 + let mut i = 0; + while i < eps_in.len() { + v |= 1 << eps_in[i].num; + i += 1; + } + v + }; + + let out_mask: u32 = { + let mut v = 1; // always enable EP0 + let mut i = 0; + while i < eps_out.len() { + v |= 1 << eps_out[i].num; + i += 1; + } + v + }; + + let set_nak_mask: u32 = { + let mut v = 0u32; + let mut i = 0; + while i < eps_out.len() { + if eps_out[i].set_nak { + v |= 1 << eps_out[i].num; + } + i += 1; + } + + // Writes to `rxenable_out` are not atomic - therefore we must + // guarantee that `set_nak_out` is only set for up to a single + // endpoint (github.com/lowRISC/opentitan/issues/27434) + assert!( + v.count_ones() <= 1, + "set_nak_out can be enabled on at most one endpoint" + ); + v + }; + + Self { + buf_pool_setup, + buf_pool_out, + buf_pools_in, + in_mask, + out_mask, + set_nak_mask, + } + } +} + +impl Usb { + pub fn new(mmio: usbdev::Usbdev, config: UsbConfig) -> Self { + let mut result = Self { + mmio, + buf_pool_setup: config.buf_pool_setup, + buf_pool_out: config.buf_pool_out, + buf_pools_in: config.buf_pools_in, + transmit_queues: TransmitQueues::new(), + }; + result.init(&config); + result + } + fn init(&mut self, config: &UsbConfig) { + self.fill_setup_buffer_fifo(); + self.fill_out_buffer_fifo(); + + let regs = self.mmio.regs_mut(); + + regs.ep_in_enable0().write(|_| config.in_mask.into()); + regs.ep_out_enable0().write(|_| config.out_mask.into()); + regs.rxenable_out0().write(|_| config.out_mask.into()); + regs.set_nak_out0().write(|_| config.set_nak_mask.into()); + + regs.rxenable_setup0().write(|w| w.setup0(true)); + regs.intr_enable().write(|w| { + w.pkt_received(true) + .pkt_sent(true) + .disconnected(true) + .host_lost(true) + .link_reset(true) + .link_suspend(true) + .link_resume(true) + .av_out_empty(true) + .rx_full(true) + .av_overflow(true) + .link_in_err(false) + .rx_crc_err(false) + .rx_pid_err(false) + .rx_bitstuff_err(false) + .frame(false) + .powered(true) + .link_out_err(false) + .av_setup_empty(true) + }); + regs.usbctrl().modify(|w| w.enable(true)); + + let stat = regs.usbstat().read(); + traceln!( + "Usb out_depth={} setup_depth={}", + stat.av_out_depth(), + stat.av_setup_depth() + ); + } + + fn reset_in(&mut self) { + let regs = self.mmio.regs_mut(); + for (i, pool) in &mut self.buf_pools_in.iter_mut().enumerate() { + pool.reset(); + let configin = regs.configin().at(i); + if configin.read().pend() { + // link reset will cancel any pending transactions. Since we're + // resetting the pool/queue state there's nothing else to do but + // clear the notification. + configin.write(|w| w.pend_clear()); + } + } + self.transmit_queues.reset(); + } + + fn fill_setup_buffer_fifo(&mut self) { + // Setup buffers for incoming SETUP packets from host. + while !self.mmio.regs().usbstat().read().av_setup_full() { + let Some(buf_id) = self.buf_pool_setup.take() else { + break; + }; + self.mmio + .regs_mut() + .avsetupbuffer() + .write(|w| w.buffer(buf_id.into())); + } + } + fn fill_out_buffer_fifo(&mut self) { + // Setup buffers for incoming OUT packets from host. + while !self.mmio.regs().usbstat().read().av_out_full() { + let Some(buf_id) = self.buf_pool_out.take() else { + break; + }; + self.mmio + .regs_mut() + .avoutbuffer() + .write(|w| w.buffer(buf_id.into())); + } + } + + /// Flush packets bufferred to transmit on `endpoint` starting with `buf_id`. + fn clear_ep_tx_queue(&mut self, endpoint: u32, mut buf_id: BufId) { + let buf_pool_in = &mut self.buf_pools_in[usize::try_from(endpoint).unwrap()]; + buf_pool_in.put(buf_id); + while let Some(pkt) = self.transmit_queues.deque_next_packet(endpoint, buf_id) { + buf_id = BufId(pkt.buf_id.into()); + buf_pool_in.put(buf_id); + } + } + + /// Resume accepting OUT transfers on this endpoint. + /// + /// This should be called after processing an OUT transfer on a given endpoint + /// that was configured with `EpOut { set_nak: true }`. The `set_nak` option causes + /// the hardware to automatically NAK subsequent OUT transactions until this function + /// is called to re-enable reception. + pub fn set_rxenable(&mut self, ep_num: u8) { + self.mmio + .regs_mut() + .rxenable_out0() + .modify(|w| bit_setval(u32::from(w), ep_num.into(), true).into()); + } + + /// Common internal implementation for transfer_in and transfer_in_unaligned. + /// + /// # Safety + /// + /// If `aligned` is true, `data` MUST be 4-byte aligned. Failure to ensure + /// alignment will result in undefined behavior when the data is transmuted + /// to an aligned reference. + unsafe fn transfer_in_internal(&mut self, endpoint: u8, mut data: &[u8], zlp: bool, aligned: bool) -> usize { + let mut bytes_queued = 0; + let zlp = zlp && (data.len() % MAX_PACKET_SIZE) == 0; + loop { + if data.is_empty() && !zlp { + break; + } + let regs = self.mmio.regs_mut(); + let Some(configin_reg) = regs.configin().get(endpoint.into()) else { + // Fault? + return 0; + }; + + let pkt = &data[..cmp::min(MAX_PACKET_SIZE, data.len())]; + if pkt.len() == MAX_PACKET_SIZE { + data = &data[pkt.len()..]; + } else { + data = &[]; + } + + let buf_pool = self.buf_pools_in.get_mut(usize::from(endpoint)).unwrap(); + + // Check to see if we have enough buffers to send both + // the last data packet and a ZLP if necessary. If not, leave last + // data packet unsent so caller knows to retry transfer + if zlp && pkt.len() == MAX_PACKET_SIZE && buf_pool.len() < 2 { + traceln!("Couldn't find buf in pool for last IN + ZLP"); + break; + } + + let Some(buf_id) = buf_pool.take() else { + traceln!("Couldn't find buf in pool for next IN"); + break; + }; + + let Some(buffer) = regs + .buffer() + .get_sub_array::(buf_id.offset()) + else { + // Shouldn't fail to get buffer offset + unreachable!(); + }; + if aligned { + // SAFETY: The caller of transfer_in_internal has guaranteed that 'data' + // is 4-byte aligned when the 'aligned' flag is set. + copy_to_reg_array(&buffer, unsafe { core::mem::transmute::<&[u8], &aligned::Aligned>(pkt) }); + } else { + copy_to_reg_array_unaligned(&buffer, pkt); + } + + match self.transmit_queues.queue( + endpoint.into(), + NextInPacket { + buf_id: u32::from(buf_id) as u8, + len: pkt.len() as u8, + }, + ) { + TransmitQueueAction::None => {} + TransmitQueueAction::SendNow => { + if configin_reg.read().rdy() { + traceln!("WARN: Packet already queued in hardware"); + } + configin_reg.write(|w| { + w.buffer(buf_id.into()) + .rdy(true) + .size(u32::try_from(pkt.len()).unwrap()) + }); + } + } + bytes_queued += pkt.len(); + + if pkt.is_empty() { + break; + } + } + bytes_queued + } +} + +#[inline(always)] +fn bit_setval(bits: u32, index: usize, value: bool) -> u32 { + let mask = 1 << index; + if value { bits | mask } else { bits & !mask } +} + +impl UsbDriver for Usb { + const MAX_PACKET_SIZE: usize = 64; + type Packet<'a> = PacketHandle>; + + #[inline(always)] + fn stall(&mut self, endpoint_num: u8, stalled: bool) { + if endpoint_num & 0x80 != 0 { + self.mmio + .regs_mut() + .in_stall0() + .modify(|w| bit_setval(u32::from(w), (endpoint_num & 0x0f).into(), stalled).into()); + } else { + self.mmio + .regs_mut() + .out_stall0() + .modify(|w| bit_setval(u32::from(w), (endpoint_num & 0x0f).into(), stalled).into()); + } + } + + #[inline(always)] + fn is_stalled(&mut self, endpoint_num: u8) -> bool { + if endpoint_num & 0x80 != 0 { + u32::from(self.mmio + .regs() + .in_stall0() + .read()) & (1 << (endpoint_num & 0x0f)) != 0 + + } else { + u32::from(self.mmio + .regs() + .out_stall0() + .read()) & (1 << (endpoint_num & 0x0f)) != 0 + } + } + + /// Store data in peripheral buffer that will be transferred when the host requests it. + #[inline(never)] + fn transfer_in(&mut self, endpoint: u8, data: &Aligned, zlp: bool) -> usize { + // SAFETY: Aligned is guaranteed to be 4-byte aligned. + unsafe { self.transfer_in_internal(endpoint, data.as_ref(), zlp, true) } + } + + #[inline(never)] + fn transfer_in_unaligned(&mut self, endpoint_idx: u8, data: &[u8], zlp: bool) -> usize { + // SAFETY: The 'aligned' flag is set to false, so transfer_in_internal + // will use the unaligned copy helper. + unsafe { self.transfer_in_internal(endpoint_idx, data, zlp, false) } + } + + fn set_address(&mut self, address: u8) { + self.mmio + .regs_mut() + .usbctrl() + .modify(|w| w.device_address(address.into())); + } + + #[inline(never)] + fn poll(&mut self) -> Option>>> { + let intr = self.mmio.regs_mut().intr_state().read(); + + // TODO: use count_leading_zeros() to iterate over the pending interrupts + if intr.pkt_received() { + let fifo_entry = self.mmio.regs_mut().rxfifo().read(); + + if fifo_entry.setup() { + self.fill_setup_buffer_fifo(); + + if let Some(configin_reg) = self + .mmio + .regs_mut() + .configin() + .get(fifo_entry.ep() as usize) + { + let configin = configin_reg.read(); + if configin.pend() { + // Previous transmission was cancelled by an incoming setup packet + configin_reg.write(|w| w.pend_clear()); + self.clear_ep_tx_queue(fifo_entry.ep(), BufId(configin.buffer())); + } + } + } else { + self.fill_out_buffer_fifo(); + } + + let offset = usize::try_from(fifo_entry.buffer()).unwrap() * BUFFER_SLOT_SIZE_WORDS; + let Some(pkt_buffer) = self + .mmio + .regs() + .into_buffer() + .get_sub_array::(offset) + else { + return Some(UsbEvent::ErrorUnexpectedBufId); + }; + + if fifo_entry.setup() { + let buf_id = BufId(fifo_entry.buffer()); + + // Return the buffer back to the pool, but don't call + // self.fill_setup_buffer_fifo() yet, as the caller to poll() + // may look at this data from the returned event, and we don't want + // the peripheral to change it while they're reading the data + // (because the returned Event is exclusively holding self, it won't be possible + // to call fill_setup_buffer_fifo() until after they lose the event). + self.buf_pool_setup.put(buf_id); + + let ep = u8::try_from(fifo_entry.ep()).unwrap(); + let pkt_handle = PacketHandle { + data: pkt_buffer, + // These unwraps will optimize out + ep, + packet_len: u16::try_from(fifo_entry.size()).unwrap(), + }; + let mut pkt_words = [0_u32; 2]; + pkt_handle.copy_to(&mut pkt_words); + return Some(UsbEvent::SetupPacket { + endpoint: ep, + pkt: SetupPacket::new(pkt_words), + }); + } else { + let buf_id = BufId(fifo_entry.buffer()); + self.buf_pool_out.put(buf_id); + return Some(UsbEvent::DataOutPacket(PacketHandle { + data: pkt_buffer, + // These unwraps will optimize out + ep: u8::try_from(fifo_entry.ep()).unwrap(), + packet_len: u16::try_from(fifo_entry.size()).unwrap(), + })); + } + } + if intr.pkt_sent() { + let regs = self.mmio.regs_mut(); + loop { + let endpoint_bits: u32 = regs.in_sent0().read().into(); + if endpoint_bits == 0 { + break; + } + let endpoint_id = endpoint_bits.trailing_zeros(); + + // Ensure we don't get interrupted about this packet again (w1c) + regs.in_sent0().write(|_| (1 << endpoint_id).into()); + + let Some(configin_reg) = regs.configin().get(usize::try_from(endpoint_id).unwrap()) + else { + // TODO: Log weird hardware behavior? + continue; + }; + let configin = configin_reg.read(); + + if configin.rdy() { + // TODO: Log weird hardware behavior... + continue; + } + + let buf_pool = self + .buf_pools_in + .get_mut(usize::try_from(endpoint_id).unwrap()) + .unwrap(); + let sent_buf_id = BufId(configin.buffer()); + buf_pool.put(sent_buf_id); + + if let Some(next_pkt) = self + .transmit_queues + .deque_next_packet(endpoint_id, sent_buf_id) + { + // We have more packets for this endpoint already in the + // peripheral SRAM; let's tell the hardware to prep the next + // one for sending. + configin_reg.write(|w| { + w.buffer(next_pkt.buf_id.into()) + .size(next_pkt.len.into()) + .rdy(true) + }); + } + return Some(UsbEvent::PacketSent { + endpoint: endpoint_id, + }); + } + } + if intr.host_lost() { + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.host_lost_clear()); + return Some(UsbEvent::LinkDown); + } + if intr.powered() { + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.powered_clear()); + return Some(UsbEvent::VBus); + } + if intr.disconnected() { + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.disconnected_clear()); + return Some(UsbEvent::VBusLost); + } + if intr.link_reset() { + self.reset_in(); + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.link_reset_clear()); + return Some(UsbEvent::UsbReset); + } + if intr.av_overflow() { + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.av_overflow_clear()); + traceln!("av_overflow"); + } + if intr.link_suspend() { + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.link_suspend_clear()); + } + if intr.link_resume() { + self.mmio + .regs_mut() + .intr_state() + .write(|w| w.link_resume_clear()); + } + if intr.av_out_empty() { + traceln!("av_out_empty"); + self.fill_out_buffer_fifo(); + } + if intr.av_setup_empty() { + traceln!("av_setup_empty"); + self.fill_setup_buffer_fifo(); + } + if intr.rx_full() { + traceln!("rx_full"); + } + + None + } +} + +#[derive(Eq, PartialEq, Debug)] +pub enum TransmitQueueAction { + None, + SendNow, +} + +pub mod transmit_queue { + use super::*; + + pub struct TransmitQueues { + slots: [NextInPacket; BUFFER_SLOT_COUNT], + + /// Indexed by endpoint num, this is the slot index of the last packet + /// queued for transmission on that endpoint. + last_pkt_idx: [Option; NB_EP], + } + impl TransmitQueues { + pub const fn new() -> Self { + Self { + slots: [NextInPacket::NONE; BUFFER_SLOT_COUNT], + last_pkt_idx: [None; NB_EP], + } + } + pub fn reset(&mut self) { + *self = Self::new() + } + + #[must_use] + #[inline(always)] + pub fn queue(&mut self, ep_id: u32, pkt: NextInPacket) -> TransmitQueueAction { + let ep_id = usize::try_from(ep_id).unwrap(); + let last_pkt_idx = &mut self.last_pkt_idx[ep_id]; + let result = if let Some(last_pkt_idx) = *last_pkt_idx + && let Some(entry) = self.slots.get_mut(usize::from(last_pkt_idx)) + { + *entry = pkt; + TransmitQueueAction::None + } else { + if let Some(entry) = self.slots.get_mut(usize::from(pkt.buf_id)) { + *entry = NextInPacket::NONE; + } + TransmitQueueAction::SendNow + }; + *last_pkt_idx = Some(pkt.buf_id); + result + } + + #[inline(always)] + pub fn deque_next_packet( + &mut self, + ep_id: u32, + sent_buf_id: BufId, + ) -> Option { + let ep_id = usize::try_from(ep_id).unwrap(); + let sent_buf_id = usize::from(sent_buf_id); + let next_pkt = &mut self.slots[sent_buf_id]; + if usize::from(next_pkt.buf_id) >= BUFFER_SLOT_COUNT { + self.last_pkt_idx[ep_id] = None; + return None; + } + Some(core::mem::replace(next_pkt, NextInPacket::NONE)) + } + } + impl Default for TransmitQueues { + fn default() -> Self { + Self::new() + } + } + + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn test_transmit_queues() { + let mut queues = TransmitQueues::new(); + assert_eq!( + queues.queue(0, NextInPacket { buf_id: 4, len: 64 }), + TransmitQueueAction::SendNow + ); + assert_eq!( + queues.queue(0, NextInPacket { buf_id: 5, len: 64 }), + TransmitQueueAction::None + ); + assert_eq!( + queues.queue( + 1, + NextInPacket { + buf_id: 10, + len: 64 + } + ), + TransmitQueueAction::SendNow + ); + assert_eq!( + queues.queue( + 1, + NextInPacket { + buf_id: 11, + len: 64 + } + ), + TransmitQueueAction::None + ); + assert_eq!( + queues.queue(0, NextInPacket { buf_id: 6, len: 0 }), + TransmitQueueAction::None + ); + assert_eq!( + queues.queue(1, NextInPacket { buf_id: 12, len: 3 }), + TransmitQueueAction::None + ); + + assert_eq!( + queues.deque_next_packet(1, BufId(10)), + Some(NextInPacket { + buf_id: 11, + len: 64 + }) + ); + assert_eq!( + queues.deque_next_packet(0, BufId(4)), + Some(NextInPacket { buf_id: 5, len: 64 }) + ); + assert_eq!( + queues.deque_next_packet(0, BufId(5)), + Some(NextInPacket { buf_id: 6, len: 0 }) + ); + assert_eq!(queues.deque_next_packet(0, BufId(6)), None); + + assert_eq!( + queues.queue( + 1, + NextInPacket { + buf_id: 10, + len: 33 + } + ), + TransmitQueueAction::None + ); + assert_eq!( + queues.queue(0, NextInPacket { buf_id: 4, len: 64 }), + TransmitQueueAction::SendNow + ); + assert_eq!( + queues.queue(0, NextInPacket { buf_id: 5, len: 9 }), + TransmitQueueAction::None + ); + + assert_eq!( + queues.deque_next_packet(1, BufId(11)), + Some(NextInPacket { buf_id: 12, len: 3 }) + ); + assert_eq!( + queues.deque_next_packet(1, BufId(12)), + Some(NextInPacket { + buf_id: 10, + len: 33 + }) + ); + assert_eq!( + queues.deque_next_packet(0, BufId(4)), + Some(NextInPacket { buf_id: 5, len: 9 }) + ); + assert_eq!(queues.deque_next_packet(0, BufId(5)), None); + assert_eq!(queues.deque_next_packet(1, BufId(10)), None); + + // Make sure we cleaned up after ourselves... + assert!(queues.slots.iter().all(|s| *s == NextInPacket::NONE)); + assert!(queues.last_pkt_idx.iter().all(|i| i.is_none())); + } + } +} + +pub mod buf_pool { + use super::*; + + #[derive(Clone, Copy, Debug, Eq, PartialEq)] + #[repr(transparent)] + pub struct BufId(pub u32); + impl BufId { + pub const fn offset(&self) -> usize { + self.0 as usize * BUFFER_SLOT_SIZE_WORDS + } + } + impl From for u32 { + fn from(value: BufId) -> Self { + value.0 + } + } + impl From for usize { + fn from(value: BufId) -> Self { + usize::try_from(value.0).unwrap() + } + } + impl From for BufId { + fn from(value: u32) -> Self { + Self(value) + } + } + + pub struct BuffPoolAllocator { + allocated_buf_ids: u32, + } + impl Default for BuffPoolAllocator { + fn default() -> Self { + Self::new() + } + } + impl BuffPoolAllocator { + pub const fn new() -> Self { + Self { + allocated_buf_ids: 0, + } + } + pub const fn new_bufpool(&mut self, len: u32) -> Option { + let start_id = self.allocated_buf_ids.trailing_ones(); + if len == 0 || start_id + len > 32 { + return None; + } + let mask = (((1_u64 << len) - 1) << start_id) as u32; + self.allocated_buf_ids |= mask; + Some(BufPool { + init_value: mask, + available_bufs: mask, + }) + } + pub const fn remainder_bufpool(mut self) -> Option { + let left = self.allocated_buf_ids.leading_zeros(); + self.new_bufpool(left) + } + } + + #[cfg(test)] + mod test_buff_pool_allocator { + use super::*; + + #[test] + fn test_next() { + let mut allocator = BuffPoolAllocator::new(); + assert_eq!( + allocator.new_bufpool(1), + Some(BufPool { + available_bufs: 0b01, + init_value: 0b01 + }) + ); + assert_eq!( + allocator.new_bufpool(1), + Some(BufPool { + available_bufs: 0b10, + init_value: 0b10, + }) + ); + assert_eq!( + allocator.new_bufpool(2), + Some(BufPool { + available_bufs: 0b1100, + init_value: 0b1100, + }) + ); + assert_eq!(allocator.new_bufpool(0), None); + assert_eq!(allocator.new_bufpool(30), None); + assert_eq!( + allocator.new_bufpool(28), + Some(BufPool { + available_bufs: (0xffff_ffffu64 << 4) as u32, + init_value: (0xffff_ffffu64 << 4) as u32, + }) + ); + assert_eq!(allocator.new_bufpool(1), None); + } + #[test] + fn test_remainder() { + let mut allocator = BuffPoolAllocator::new(); + assert_eq!( + allocator.new_bufpool(1), + Some(BufPool { + available_bufs: 0b01, + init_value: 0b01, + }) + ); + assert_eq!( + allocator.new_bufpool(1), + Some(BufPool { + available_bufs: 0b10, + init_value: 0b10, + }) + ); + assert_eq!( + allocator.new_bufpool(2), + Some(BufPool { + available_bufs: 0b1100, + init_value: 0b1100, + }) + ); + assert_eq!( + allocator.remainder_bufpool(), + Some(BufPool { + available_bufs: (0xffff_ffffu64 << 4) as u32, + init_value: (0xffff_ffffu64 << 4) as u32, + }) + ); + } + } + + #[derive(PartialEq, Eq, Debug, Clone, Copy)] + pub struct BufPool { + // bitset of bufs that are currently available for taking with take(). + available_bufs: u32, + init_value: u32, + } + impl BufPool { + pub const EMPTY: Self = Self { + available_bufs: 0, + init_value: 0, + }; + + #[cfg(test)] + pub const fn new(start_id: usize, len: usize) -> Self { + assert!(start_id < 32); + assert!(len > 0); + assert!(start_id + len <= 32); + let available_bufs = (((1_u64 << len) - 1) << start_id) as u32; + Self { + available_bufs, + init_value: available_bufs, + } + } + pub fn reset(&mut self) { + self.available_bufs = self.init_value; + } + pub fn take(&mut self) -> Option { + if self.is_empty() { + return None; + } + let buf_id = self.available_bufs.trailing_zeros(); + let mask = 1 << buf_id; + debug_assert!((self.available_bufs & mask) != 0); + self.available_bufs &= !mask; + Some(BufId(buf_id)) + } + pub fn put(&mut self, buf_id: BufId) { + let mask = 1 << u32::from(buf_id); + debug_assert!((self.available_bufs & mask) == 0); + self.available_bufs |= mask; + } + + pub fn len(&self) -> usize { + usize::try_from(self.available_bufs.count_ones()).unwrap() + } + + pub fn is_empty(&self) -> bool { + self.available_bufs == 0 + } + } + + #[cfg(test)] + mod test { + use super::*; + + #[test] + fn test_full_size() { + let mut pool = BufPool::new(0, 32); + assert_eq!(pool.available_bufs, 0xffff_ffff); + for i in 0..32 { + assert_eq!(Some(BufId::from(i)), pool.take()); + } + assert_eq!(None, pool.take()); + pool.put(5.into()); + pool.put(7.into()); + pool.put(3.into()); + assert_eq!(Some(3.into()), pool.take()); + assert_eq!(Some(5.into()), pool.take()); + assert_eq!(Some(7.into()), pool.take()); + assert_eq!(None, pool.take()); + assert_eq!(None, pool.take()); + } + + #[test] + fn test_5_bits() { + let mut pool = BufPool::new(4, 5); + assert_eq!(pool.available_bufs, 0x0000_01f0); + assert_eq!(Some(BufId::from(4)), pool.take()); + assert_eq!(Some(BufId::from(5)), pool.take()); + assert_eq!(Some(BufId::from(6)), pool.take()); + assert_eq!(Some(BufId::from(7)), pool.take()); + assert_eq!(Some(BufId::from(8)), pool.take()); + assert_eq!(None, pool.take()); + + pool.put(5.into()); + pool.put(6.into()); + assert_eq!(Some(5.into()), pool.take()); + assert_eq!(Some(6.into()), pool.take()); + assert_eq!(None, pool.take()); + } + + #[test] + fn test_config() { + assert_eq!( + UsbConfig::new( + &[ + EpIn { + num: 1, + buf_pool_size: 3, + }, + EpIn { + num: 3, + buf_pool_size: 5, + }, + ], + &[ + EpOut { + num: 2, + set_nak: true + }, + EpOut { + num: 4, + set_nak: false + }, + ] + ), + UsbConfig { + buf_pool_setup: BufPool::new(0, 4), + buf_pool_out: BufPool::new(4, 12), + buf_pools_in: [ + BufPool::new(24, 8), + BufPool::new(16, 3), + BufPool::EMPTY, + BufPool::new(19, 5), + BufPool::EMPTY, + BufPool::EMPTY, + BufPool::EMPTY, + BufPool::EMPTY, + BufPool::EMPTY, + BufPool::EMPTY, + BufPool::EMPTY, + BufPool::EMPTY, + ], + in_mask: 0b01011, + out_mask: 0b10101, + set_nak_mask: 0b100, + }, + ); + } + + #[test] + #[should_panic] + fn test_config_too_many_set_nak() { + UsbConfig::new( + &[EpIn { + num: 1, + buf_pool_size: 3, + }], + &[ + EpOut { + num: 2, + set_nak: true, + }, + EpOut { + num: 4, + set_nak: true, + }, + ], + ); + } + } +} diff --git a/target/earlgrey/firmware/hwe/BUILD.bazel b/target/earlgrey/firmware/hwe/BUILD.bazel new file mode 100644 index 00000000..520d3d36 --- /dev/null +++ b/target/earlgrey/firmware/hwe/BUILD.bazel @@ -0,0 +1,212 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_app( + name = "usbdfu", + srcs = [ + "usbdfu.rs", + ], + codegen_crate_name = "usbdfu_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash", + "//hal/blocking/usb:hal_usb", + "//protocol/usb/dfu", + "//protocol/usb/stack", + "//services/flash:client", + "//target/earlgrey/drivers:usb_driver", + "//target/earlgrey/registers:pinmux", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/registers:usbdev", + "//target/earlgrey/services/sysmgr:client", + "//target/earlgrey/util", + "//util/console", + "//util/error", + "//util/ipc", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:aligned", + "@rust_crates//:zerocopy", + ], +) + +rust_app( + name = "flash_server", + srcs = [ + "flash_server.rs", + ], + codegen_crate_name = "flash_server_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash", + "//services/flash:server", + "//target/earlgrey/drivers:eflash_driver", + "//target/earlgrey/registers:flash_ctrl_core", + "//util/error", + "//util/ipc", + "//util/types", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_app( + name = "sysmgr", + srcs = [ + "sysmgr.rs", + ], + codegen_crate_name = "sysmgr_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//target/earlgrey/services/sysmgr:server", + "//target/earlgrey/util", + "//util/error", + "//util/ipc", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_app( + name = "platform", + srcs = [ + "platform.rs", + ], + codegen_crate_name = "platform_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//target/earlgrey/drivers:uart_receiver", + "//target/earlgrey/registers:pinmux", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/registers:uart", + "//target/earlgrey/services/sysmgr:client", + "//target/earlgrey/util", + "//util/console", + "//util/error", + "//util/ipc", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +system_image( + name = "hwe", + apps = [ + ":sysmgr", + ":platform", + ":flash_server", + ":usbdfu", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "hwe_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":hwe", +) + +opentitan_test( + name = "hwe_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":hwe", +) + +opentitan_test( + name = "hwe_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":hwe", +) + +opentitan_test( + name = "hwe_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":hwe", +) diff --git a/target/earlgrey/firmware/hwe/flash_server.rs b/target/earlgrey/firmware/hwe/flash_server.rs new file mode 100644 index 00000000..8a4e37cb --- /dev/null +++ b/target/earlgrey/firmware/hwe/flash_server.rs @@ -0,0 +1,75 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use flash_server_codegen::{handle, signals}; +use pw_status::{Error}; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use hal_flash::{BlockingFlash, FlashAddress}; +use services_flash_server::FlashIpcServer; +use util_types::Blocking; +use util_ipc::IpcChannel; +use util_error::ErrorCode; +use eflash_driver::{EmbeddedFlash, Permission}; + +struct FlashCtrlInterrupt; + +impl Blocking for FlashCtrlInterrupt { + fn wait_for_notification(&self) { + loop { + let w = syscall::object_wait( + handle::FLASH_INTERRUPTS, + signals::FLASH_CTRL_OP_DONE, + Instant::MAX, + ) + .unwrap(); + if w.pending_signals.contains(signals::FLASH_CTRL_OP_DONE) { + break; + } + } + let _ = syscall::interrupt_ack(handle::FLASH_INTERRUPTS, signals::FLASH_CTRL_OP_DONE); + } +} + +fn flash_server() -> Result<(), ErrorCode> { + let mut driver = EmbeddedFlash::new_with_interrupts( + unsafe { flash_ctrl_core::FlashCtrl::new() }); + driver.set_default_permission(Permission::FULL_ACCESS); + for i in 5..9 { + driver.set_info_permission(FlashAddress::info(0, i, 0), Permission::FULL_ACCESS)?; + driver.set_info_permission(FlashAddress::info(1, i, 0), Permission::FULL_ACCESS)?; + } + let flash = BlockingFlash { + driver, + blocking: FlashCtrlInterrupt, + }; + let mut flash_server = FlashIpcServer::new(flash); + let mut buf = [0u8; 2056]; + let ipc = IpcChannel::new(handle::FLASH_SERVICE); + flash_server.run(&ipc, &mut buf) +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + let ret = flash_server(); + + // Log that an error occurred so that the app that caused the shutdown is logged. + let ret = match ret { + Ok(()) => { pw_log::info!("✅ PASSED"); Ok(()) } + Err(e) => { pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); Err(Error::Unknown) } + }; + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/firmware/hwe/platform.rs b/target/earlgrey/firmware/hwe/platform.rs new file mode 100644 index 00000000..73220bb5 --- /dev/null +++ b/target/earlgrey/firmware/hwe/platform.rs @@ -0,0 +1,181 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +#![allow(unused_imports)] +use platform_codegen::{handle, signals}; +use pw_status::Error; +use userspace::{entry, syscall}; +use userspace::time::Instant; + + +use earlgrey_sysmgr_client::{SysmgrClient, BootInfo}; +use uart_receiver::UartReceiver; +use uart::Uart0; +use util_error::ErrorCode; +use util_ipc::IpcChannel; + +struct CommandProcessor { + line: [u8; 160], + pos: usize, +} + +impl Default for CommandProcessor { + fn default() -> Self { + Self { + line: [0u8; 160], + pos: 0, + } + } +} + +impl CommandProcessor { + + fn push(&mut self, byte: u8) -> bool { + match byte { + 8 | 127 => { + // Backspace + if self.pos > 0 { + self.pos -= 1; + util_console::print!("\x08 \x08"); + } + false + } + 21 => { + // Ctrl-U: Kill line + while self.pos > 0 { + self.pos -= 1; + util_console::print!("\x08 \x08"); + } + false + } + 23 => { + // Ctrl-W: Kill word + if self.pos > 0 && self.line[self.pos-1] == b' ' { + self.pos -= 1; + util_console::print!("\x08 \x08"); + } + while self.pos > 0 && self.line[self.pos-1] != b' ' { + self.pos -= 1; + util_console::print!("\x08 \x08"); + } + false + } + 13 => { + // Enter: accept line + util_console::println!(""); + true + } + _ => { + // Any other ASCII character: append to line. + if self.pos < self.line.len() && byte < 127 { + self.line[self.pos] = byte; + self.pos += 1; + util_console::print!("{}", byte as char); + } + false + } + } + } + + fn clear(&mut self) { + self.pos = 0; + } + + fn execute(&self) -> Result<(), ErrorCode> { + let mut n = 0; + let mut cmd = [""; 20]; + // SAFETY: this is safe because only ascii input is permitted. + let line = unsafe { core::str::from_utf8_unchecked(&self.line[..self.pos]) }; + for part in line.split(' ').filter(|x| !x.is_empty()) { + if n < cmd.len() { + cmd[n] = part; + n += 1; + } else { + util_console::println!("ERROR: too many arguments"); + } + } + + RootCommandHierarchy::exec(&cmd[..n]) + } +} + +struct RootCommandHierarchy; +impl RootCommandHierarchy { + fn exec(cmd: &[&str]) -> Result<(), ErrorCode> { + match cmd { + ["hello"] => util_console::println!("Hello world!"), + ["info"] => Self::handle_info()?, + ["reboot"] => Self::handle_reboot()?, + [_, ..] => util_console::println!("Unknown command: {}", cmd[0]), + [] => {}, + } + Ok(()) + } + + fn handle_info() -> Result<(), ErrorCode> { + let sysmgr = SysmgrClient::new(IpcChannel::new(handle::SYSMGR_PLATFORM)); + let info = sysmgr.get_boot_info()?; + util_console::println!("{:#?}", info); + Ok(()) + } + fn handle_reboot() -> Result<(), ErrorCode> { + let sysmgr = SysmgrClient::new(IpcChannel::new(handle::SYSMGR_PLATFORM)); + sysmgr.request_reboot()?; + Ok(()) + } + +} + +fn platform_server() -> Result<(), ErrorCode> { + let mut uart = unsafe { UartReceiver::new(Uart0::PTR) }; + + let mut cmd = CommandProcessor::default(); + + uart.enable_receiver(); + uart.enable_interrupt(); + loop { + let w= syscall::object_wait( + handle::UART_INTERRUPTS, + signals::UART0_RX_WATERMARK, + Instant::MAX, + ).map_err(ErrorCode::kernel_error)?; + if w.pending_signals.contains(signals::UART0_RX_WATERMARK) { + if let Some(byte) = uart.receive() { + if cmd.push(byte) { + let _ = cmd.execute(); + cmd.clear(); + } + } + let _ = syscall::interrupt_ack(handle::UART_INTERRUPTS, signals::UART0_RX_WATERMARK); + } + } +} + +fn uart_setup_pinmux() { + use top_earlgrey::{PinmuxInsel, PinmuxPeripheralIn}; + let mut pinmux = unsafe { pinmux::PinmuxAon::new() }; + pinmux + .regs_mut() + .mio_periph_insel() + .at(PinmuxPeripheralIn::Uart0Rx as usize) + .modify(|_| (PinmuxInsel::Ioc3 as u32).into()); +} + +#[entry] +fn entry() -> ! { + uart_setup_pinmux(); + let ret = platform_server().map_err(|e| { + pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); + Error::Unknown + }); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/firmware/hwe/sysmgr.rs b/target/earlgrey/firmware/hwe/sysmgr.rs new file mode 100644 index 00000000..fe5d1bd0 --- /dev/null +++ b/target/earlgrey/firmware/hwe/sysmgr.rs @@ -0,0 +1,37 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use sysmgr_codegen::{handle}; +use pw_status::Error; +use userspace::{entry, syscall}; + +use earlgrey_sysmgr_server::SysmgrServer; +use util_error::ErrorCode; +use util_ipc::IpcChannel; + +fn sysmgr_server() -> Result<(), ErrorCode> { + let mut sysmgr = SysmgrServer::new()?; + let mut buf = [0u8; 512]; + sysmgr.run(handle::SYSMGR_WAIT_GROUP, &[ + &IpcChannel::new(handle::SYSMGR_USB), + &IpcChannel::new(handle::SYSMGR_PLATFORM), + ], &mut buf) +} + +#[entry] +fn entry() -> ! { + let ret = sysmgr_server().map_err(|e| { + pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); + Error::Unknown + }); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/firmware/hwe/system.json5 b/target/earlgrey/firmware/hwe/system.json5 new file mode 100644 index 00000000..b41a6e68 --- /dev/null +++ b/target/earlgrey/firmware/hwe/system.json5 @@ -0,0 +1,236 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "sysmgr", + flash_size_bytes: 16384, + processes: [{ + name: "sysmgr_process", + ram_size_bytes: 4096, + objects: [ + { + name: "sysmgr_wait_group", + type: "wait_group", + }, + { + name: "sysmgr_platform", + type: "channel_handler", + }, + { + name: "sysmgr_usb", + type: "channel_handler", + }, + ], + memory_mappings: [ + { + name: "retram", + type: "device", + start_address: 0x40600000, + size_bytes: 0x1000, + }, + { + name: "rstmgr", + type: "device", + start_address: 0x40410000, + size_bytes: 0x80, + }, + { + name: "lc_ctrl", + type: "device", + start_address: 0x40140000, + size_bytes: 0x100, + }, + + ], + threads: [ + { + name: "sysmgr_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + + { + name: "platform", + flash_size_bytes: 16384, + processes: [ + { + name: "platform_server", + ram_size_bytes: 4096, + objects: [ + { + name: "sysmgr_platform", + type: "channel_initiator", + handler_process: "sysmgr_process", + handler_object_name: "sysmgr_platform", + }, + { + name: "uart_interrupts", + type: "interrupt", + irqs: [ + //{ name: "uart0_tx_watermark", number: 1 }, + { name: "uart0_rx_watermark", number: 2 }, + //{ name: "uart0_tx_done", number: 3 }, + //{ name: "uart0_rx_overflow", number: 4 }, + //{ name: "uart0_rx_frame_err", number: 5 }, + //{ name: "uart0_rx_break_err", number: 6 }, + //{ name: "uart0_rx_timeout", number: 7 }, + //{ name: "uart0_rx_parity_err", number: 8 }, + //{ name: "uart0_tx_empty", number: 9 }, + ], + }, + ], + memory_mappings: [ + { + name: "uart0", + type: "device", + start_address: 0x40000000, + size_bytes: 0x40, + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000, + } + + ], + threads: [ + { + name: "platform_server_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }, + ], + }, + + { + name: "flash_server", + flash_size_bytes: 16384, + processes: [ + { + name: "flash_server", + ram_size_bytes: 4096, + objects: [ + { + name: "flash_service", + type: "channel_handler", + }, + { + name: "flash_interrupts", + type: "interrupt", + irqs: [ + //{ name: "flash_ctrl_prog_empty", number: 160 }, + //{ name: "flash_ctrl_prog_lvl", number: 161 }, + //{ name: "flash_ctrl_rd_full", number: 162 }, + //{ name: "flash_ctrl_rd_lvl", number: 163 }, + { name: "flash_ctrl_op_done", number: 164 }, + //{ name: "flash_ctrl_corr_err", number: 165 }, + ], + } + ], + memory_mappings: [ + { + name: "flash_ctrl_core", + type: "device", + start_address: 0x41000000, + size_bytes: 0x200, + } + ], + threads: [ + { + name: "flash_server_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }, + ], + }, + { + name: "usbdfu", + flash_size_bytes: 16384, + processes: [{ + name: "usbdfu_process", + ram_size_bytes: 6144, + objects: [ + { + name: "flash_service", + type: "channel_initiator", + handler_process: "flash_server", + handler_object_name: "flash_service", + }, + { + name: "sysmgr_usb", + type: "channel_initiator", + handler_process: "sysmgr_process", + handler_object_name: "sysmgr_usb", + }, + { + name: "usbdev_interrupts", + type: "interrupt", + irqs: [ + { name: "usbdev_pkt_received", number: 135 }, + { name: "usbdev_pkt_sent", number: 136 }, + { name: "usbdev_disconnected", number: 137 }, + { name: "usbdev_host_lost", number: 138 }, + + { name: "usbdev_link_reset", number: 139 }, + { name: "usbdev_link_suspend", number: 140 }, + { name: "usbdev_link_resume", number: 141 }, + { name: "usbdev_av_out_empty", number: 142 }, + + { name: "usbdev_rx_full", number: 143 }, + { name: "usbdev_av_overflow", number: 144 }, + //{ name: "usbdev_link_in_err", number: 145 }, + { name: "usbdev_rx_crc_err", number: 146 }, + + { name: "usbdev_rx_pid_err", number: 147 }, + { name: "usbdev_rx_bitstuff_err", number: 148 }, + { name: "usbdev_frame", number: 149 }, + //{ name: "usbdev_powered", number: 150 }, + + //{ name: "usbdev_link_out_err", number: 151 }, + { name: "usbdev_av_setup_empty", number: 152 }, + ], + }, + ], + memory_mappings: [ + { + name: "usbdev", + type: "device", + start_address: 0x40320000, + size_bytes: 0x1000, + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000, + } + + ], + threads: [ + { + name: "usbdev_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + ] +} diff --git a/target/earlgrey/firmware/hwe/target.rs b/target/earlgrey/firmware/hwe/target.rs new file mode 100644 index 00000000..2e253d37 --- /dev/null +++ b/target/earlgrey/firmware/hwe/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/firmware/hwe/usbdfu.rs b/target/earlgrey/firmware/hwe/usbdfu.rs new file mode 100644 index 00000000..68bd3e9a --- /dev/null +++ b/target/earlgrey/firmware/hwe/usbdfu.rs @@ -0,0 +1,396 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +#![allow(dead_code)] + +use pw_status::Error; +use usbdfu_codegen::{handle, signals}; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use aligned::{Aligned, A4}; +use hal_usb::{ConfigDescriptor, DeviceDescriptor, StringDescriptorRef}; + +use hal_usb::driver::UsbDriver; +use usb_driver::UsbConfig; +use usb_stack::{DescriptorSource, UsbAction, UsbClass}; + +use earlgrey_util::error as eg_error; +use earlgrey_util::PersoCertificate; +use earlgrey_util::tags::{BootSlot}; +use earlgrey_util::tags::ManifestIdentifier; +use earlgrey_sysmgr_client::{SysmgrClient, BootInfo}; +use hal_flash::{Flash, FlashAddress}; +use services_flash_client::FlashIpcClient; +use util_error::{self as error, ErrorCode}; +use util_ipc::IpcChannel; +use zerocopy::{FromBytes}; + +use protocol_usb_dfu::{DfuBuilder, DfuClass, DfuHandler, DfuResult, DfuStatus}; + +const USB_VENDOR_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(1); +const USB_PRODUCT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(2); +const USB_SERIAL_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(3); +const DFU_FIRMWARE_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(4); +const DFU_UDS_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(5); +const DFU_CDI0_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(6); +const DFU_CDI1_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(7); + +const DFU_BUILDER: DfuBuilder = DfuBuilder::new( + 0, // interface_num + 1, // alt_settings (1 for now) + 2048, // transfer_size +); + +const DEVICE_DESC: DeviceDescriptor = DeviceDescriptor { + device_class: hal_usb::DeviceClass::SPECIFIED_BY_INTERFACE, + device_sub_class: 0x00, + device_protocol: 0x00, + max_packet_size: 64, + vendor_id: 0x18d1, // Google, Inc. + product_id: 0x503a, // STWG USB Fullspeed IP. + device_release_num: 0x0100, + manufacturer: USB_VENDOR_HANDLE, + product: USB_PRODUCT_HANDLE, + serial_num: USB_SERIAL_HANDLE, +}; + +const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { + configuration_value: 1, + max_power: 250, + self_powered: false, + remote_wakeup: false, + interfaces: &[ + DFU_BUILDER.interface( 0, DFU_FIRMWARE_HANDLE, &[]), + DFU_BUILDER.interface( 1, DFU_UDS_CERT_HANDLE, &[]), + DFU_BUILDER.interface( 2, DFU_CDI0_CERT_HANDLE, &[]), + DFU_BUILDER.interface( 3, DFU_CDI1_CERT_HANDLE, + &[DFU_BUILDER.functional_descriptor()]), + ], +}; + +const STRING_DESC_0: hal_usb::StringDescriptor0 = hal_usb::StringDescriptor0 { + langs: &[ + // English - United States + 0x0409, + ], +}; + +const VENDOR_ID: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("Google Inc.").as_ref(); +const PRODUCT_ID_DEFAULT: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("Earlgrey DFU").as_ref(); +const DFU_FIRMWARE: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("Firmware").as_ref(); +const DFU_UDS_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("UDS Certificate").as_ref(); +const DFU_CDI0_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDI0 Certificate").as_ref(); +const DFU_CDI1_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDI1 Certificate").as_ref(); + +struct MyDescriptors<'a> { + serial_desc_bytes: StringDescriptorRef<'a>, + product_desc_bytes: StringDescriptorRef<'a>, +} + +impl DescriptorSource for MyDescriptors<'_> { + const DEVICE_DESC_BYTES: &'static Aligned = &Aligned(DEVICE_DESC.serialize()); + const CONFIG_DESC_BYTES: &'static Aligned = + &Aligned(CONFIG_DESC.serialize::<{ CONFIG_DESC.total_size() }>()); + const STRING_DESC_0_BYTES: &'static Aligned = + &Aligned(STRING_DESC_0.serialize::<{ STRING_DESC_0.total_size() }>()); + const DEVICE_STATUS: Aligned = Aligned([1u8, 0]); + + fn get_string( + &self, + handle: hal_usb::StringHandle, + _lang: u16, + ) -> Option> { + match handle { + USB_VENDOR_HANDLE => Some(VENDOR_ID), + USB_PRODUCT_HANDLE => Some(self.product_desc_bytes), + USB_SERIAL_HANDLE => Some(self.serial_desc_bytes), + DFU_FIRMWARE_HANDLE => Some(DFU_FIRMWARE), + DFU_UDS_CERT_HANDLE => Some(DFU_UDS_CERT), + DFU_CDI0_CERT_HANDLE => Some(DFU_CDI0_CERT), + DFU_CDI1_CERT_HANDLE => Some(DFU_CDI1_CERT), + _ => None, + } + } +} + +fn get_certificate(flash: &mut FlashIpcClient, n: u8, data: &mut [u8]) -> Result { + pw_log::info!("Reading certificate {}", n as usize); + let (partition, mut n) = match n { + 0 => (0, 0), // The UDS (dice) cert is located in bank=0, page=9. + 1 => (1, 0), // The CDI (dice) certs are located in bank=1, page=9. + 2 => (1, 1), + _ => return Err(DfuStatus::ErrFile), + }; + let mut offset = 0usize; + let mut buf = [0u8; 1024]; + loop { + let sz = core::cmp::min(2048 - offset, buf.len()); + flash.read(FlashAddress::info(partition, 9, offset as u32), &mut buf[..sz]).map_err(|_| DfuStatus::ErrUnknown)?; + match PersoCertificate::from_bytes(&buf) { + Ok((cert, _)) => { + if n == 0 { + let len = cert.certificate.len(); + pw_log::info!("Found cert: {} bytes", len as usize); + data[..len].copy_from_slice(cert.certificate); + return Ok(len); + } + offset += (cert.obj_size + 7) & !7; + n -= 1; + } + Err(_) => break, + } + } + Err(DfuStatus::ErrUnknown) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum FwUpdateState { + Idle, + RomExt, + Application, + Done, +} + +struct FwUpdate { + state: FwUpdateState, + next_erase: u32, + start_block: u32, + rom_ext: BootSlot, + rom_ext_start: u32, + rom_ext_end: u32, + app: BootSlot, + app_start: u32, + app_end: u32, +} + +impl FwUpdate { + fn new(info: &BootInfo) -> Result { + let rom_ext= info.rom_ext.boot_slot.opposite().ok_or(eg_error::EG_ERROR_BOOT_SLOT_UNKNOWN)?; + let rom_ext_start= FwUpdate::addr(rom_ext); + let app = info.app.boot_slot.opposite().ok_or(eg_error::EG_ERROR_BOOT_SLOT_UNKNOWN)?; + let app_start= FwUpdate::addr(app) + info.rom_ext.size; + + Ok(FwUpdate { + state: FwUpdateState::Idle, + next_erase: 0, + start_block: 0, + rom_ext, + rom_ext_start, + rom_ext_end: rom_ext_start + info.rom_ext.size, + app, + app_start, + app_end: app_start + info.app.size, + }) + } + + fn addr(slot: BootSlot) -> u32 { + match slot { + BootSlot::SlotA => 0, + BootSlot::SlotB => 0x80000, + _ => unreachable!(), + } + } +} + +struct MyDfuHandler { + flash: FlashIpcClient, + sysmgr: SysmgrClient, + update: FwUpdate, +} + +impl MyDfuHandler { + fn flash_erase(&mut self, mut start: u32, end: u32) -> Result<(), ErrorCode> { + while start < end { + self.flash.erase_page(FlashAddress::data(start))?; + start += 2048; + } + Ok(()) + } + + fn flash_fw_block(&mut self, block_num: u32, data: &[u8]) -> Result<(), DfuStatus> { + if block_num == self.update.next_erase { + let id = ManifestIdentifier::read_from_bytes(&data[0x334..0x338]).unwrap(); + match id { + ManifestIdentifier::ROM_EXT => { + pw_log::info!("Flashing ROM_EXT region at {}-{}", + self.update.rom_ext_start as u32, + self.update.rom_ext_end as u32, + ); + self.flash_erase(self.update.rom_ext_start, self.update.rom_ext_end).map_err(|_| DfuStatus::ErrErase)?; + self.update.state = FwUpdateState::RomExt; + self.update.next_erase = 32; + self.update.start_block = block_num; + } + ManifestIdentifier::APPLICATION => { + pw_log::info!("Flashing Application region at {}-{}", + self.update.app_start as u32, + self.update.app_end as u32, + ); + self.flash_erase(self.update.app_start, self.update.app_end).map_err(|_| DfuStatus::ErrErase)?; + self.update.state = FwUpdateState::Application; + self.update.start_block = block_num; + } + _ => { + pw_log::error!("Unknown manifest ID: {:08x}", id.0 as u32); + return Err(DfuStatus::ErrUnknown); + } + } + } + + let block = block_num - self.update.start_block; + match self.update.state { + FwUpdateState::RomExt => { + self.flash.program(FlashAddress::data(self.update.rom_ext_start+block*2048), data).map_err(|_| DfuStatus::ErrProg)?; + } + FwUpdateState::Application => { + self.flash.program(FlashAddress::data(self.update.app_start+block*2048), data).map_err(|_| DfuStatus::ErrProg)?; + } + _ => { return Err(DfuStatus::ErrUnknown); } + } + + if data.len() < 2048 { + self.update.state = FwUpdateState::Done; + } + Ok(()) + } +} + +impl DfuHandler for MyDfuHandler { + fn dnload(&mut self, alt: u8, block_num: u16, data: &[u8]) -> DfuResult { + pw_log::info!( + "DNLOAD: alt={}, block={}, len={}", + alt, + block_num, + data.len() + ); + if alt == 0 { + match self.flash_fw_block(block_num as u32, data) { + Ok(()) => DfuResult::Ok, + Err(e) => DfuResult::Err(e), + } + } else { + DfuResult::Err(DfuStatus::ErrFile) + } + } + + fn upload(&mut self, alt: u8, block_num: u16, data: &mut [u8]) -> Result { + pw_log::info!( + "UPLOAD: alt={}, block={}, len={}", + alt, + block_num, + data.len() + ); + match alt { + 1|2|3 => get_certificate(&mut self.flash, alt-1, data), + _ => Err(DfuStatus::ErrFile), + } + } + + fn manifest(&mut self) -> DfuResult { + pw_log::info!("MANIFEST"); + if self.update.state == FwUpdateState::Done { + // TODO: check for errors. + let _ = self.sysmgr.set_boot_policy(self.update.app, BootSlot::Unspecified); + let _ = self.sysmgr.request_reboot(); + } + DfuResult::Ok + } + + fn abort(&mut self) { + pw_log::info!("ABORT"); + } +} + +fn handle_usb() -> Result<(), ErrorCode> { + let mut serial_num_buffer = Aligned::([0_u8; 130]); + let descriptors = MyDescriptors { + serial_desc_bytes: hal_usb::hex_utf16_descriptor_aligned( + &mut serial_num_buffer, + b"DFU-12345", + ) + .unwrap(), + product_desc_bytes: PRODUCT_ID_DEFAULT, + }; + + const USB_CONFIG: UsbConfig = UsbConfig::new(&[], &[]); + + let mut usb = usb_driver::Usb::new(unsafe { usbdev::Usbdev::new() }, USB_CONFIG); + let mut ep0 = usb_stack::SimpleEp0::new(); + let sysmgr = SysmgrClient::new(IpcChannel::new(handle::SYSMGR_USB)); + let info = sysmgr.get_boot_info()?; + let mut dfu = DfuClass::<_, 2048>::new(DFU_BUILDER, MyDfuHandler { + flash: FlashIpcClient::new(IpcChannel::new(handle::FLASH_SERVICE))?, + sysmgr, + update: FwUpdate::new(&info)?, + }); + + loop { + let wait_return = syscall::object_wait( + handle::USBDEV_INTERRUPTS, + signals::USBDEV_PKT_RECEIVED + | signals::USBDEV_PKT_SENT + | signals::USBDEV_DISCONNECTED + | signals::USBDEV_HOST_LOST + | signals::USBDEV_LINK_RESET + | signals::USBDEV_LINK_SUSPEND + | signals::USBDEV_LINK_RESUME + | signals::USBDEV_AV_OUT_EMPTY + | signals::USBDEV_RX_FULL + | signals::USBDEV_AV_OVERFLOW + | signals::USBDEV_RX_CRC_ERR + | signals::USBDEV_RX_PID_ERR + | signals::USBDEV_RX_BITSTUFF_ERR + | signals::USBDEV_FRAME + | signals::USBDEV_AV_SETUP_EMPTY, + Instant::MAX, + ).map_err(ErrorCode::kernel_error)?; + + if wait_return.user_data != 0 { + pw_log::error!("Incorrect WaitReturn values"); + return Err(error::KERNEL_ERROR_UNKNOWN); + } + + while let Some(event) = usb.poll() { + let mut action = match dfu.handle_event(event) { + Ok(a) => a, + Err(e) => ep0.handle_event(e, &descriptors).unwrap_or(UsbAction::None), + }; + action.run(&mut usb); + } + + // Initiate any pending transmissions (e.g. UPLOAD blocks) + dfu.poll(&mut usb); + } +} + +fn usb_setup_pinmux() { + use top_earlgrey::{PinmuxInsel, PinmuxPeripheralIn}; + let mut pinmux = unsafe { pinmux::PinmuxAon::new() }; + + pinmux + .regs_mut() + .mio_periph_insel() + .at(PinmuxPeripheralIn::UsbdevSense as usize) + .modify(|_| (PinmuxInsel::ConstantOne as u32).into()); +} + +#[entry] +fn entry() -> ! { + usb_setup_pinmux(); + let ret = handle_usb().map_err(|e| { + pw_log::error!("usbdfu failed: {:08x}", u32::from(e) as u32); + Error::Unknown + }); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/services/sysmgr/BUILD.bazel b/target/earlgrey/services/sysmgr/BUILD.bazel new file mode 100644 index 00000000..ddb07568 --- /dev/null +++ b/target/earlgrey/services/sysmgr/BUILD.bazel @@ -0,0 +1,47 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "client", + srcs = [ + "client.rs", + ], + crate_name = "earlgrey_sysmgr_client", + edition = "2024", + deps = [ + "//util/error", + "//util/ipc", + "//util/types", + "//target/earlgrey/util", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@rust_crates//:ufmt", + "@rust_crates//:zerocopy", + ], +) + +rust_library( + name = "server", + srcs = [ + "server.rs", + ], + crate_name = "earlgrey_sysmgr_server", + edition = "2024", + deps = [ + ":client", + "//util/error", + "//util/ipc", + "//util/types", + "//target/earlgrey/util", + "//target/earlgrey/registers:rstmgr", + "//target/earlgrey/registers:lc_ctrl", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@rust_crates//:sha2", + "@rust_crates//:zerocopy", + ], +) diff --git a/target/earlgrey/services/sysmgr/client.rs b/target/earlgrey/services/sysmgr/client.rs new file mode 100644 index 00000000..4aa286ec --- /dev/null +++ b/target/earlgrey/services/sysmgr/client.rs @@ -0,0 +1,130 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +#![no_std] +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, FromZeros}; + +use earlgrey_util::tags::{BootSlot, OwnershipState, HardenedBool}; +use userspace::time::Instant; +use util_error::ErrorCode; +use util_ipc::IpcChannel; +use ufmt::derive::uDebug; + +pub mod op { +use util_types::Opcode; +pub const SYSMGR_OP_GET_BOOT_INFO: Opcode = Opcode::new(*b"MGBI"); +pub const SYSMGR_OP_SET_BOOT_POLICY: Opcode = Opcode::new(*b"MGBP"); +pub const SYSMGR_OP_REQ_REBOOT: Opcode = Opcode::new(*b"MGRB"); +} + +pub struct SysmgrClient { + ipc: IpcChannel, +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct ChipInfo { + pub git_hash: u64, + pub lc_state: u32, + pub device_id: [u32; 8], + pub creator_id: u16, + pub product_id: u16, + pub revision: u8, + pub _pad: [u8; 7], +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct RomExtInfo { + pub boot_slot: BootSlot, + pub major: u32, + pub minor: u32, + pub min_sec_ver: u32, + pub size: u32, +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct ApplicationInfo { + pub boot_slot: BootSlot, + pub pref_slot: BootSlot, + pub min_sec_ver: u32, + pub size: u32, + pub firmware_domain: u32, +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct OwnershipInfo { + pub nonce: u64, + pub state: OwnershipState, + pub transfers: u32, +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct ResetInfo { + pub reason: u32, + pub retram_init: HardenedBool, + pub hardware_straps: u32, + pub software_straps: u32, +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct BootInfo { + pub chip: ChipInfo, + pub rom_ext: RomExtInfo, + pub app: ApplicationInfo, + pub ownership: OwnershipInfo, + pub reset: ResetInfo, +} + +#[derive(Clone, FromBytes, IntoBytes, Immutable, KnownLayout, uDebug)] +#[repr(C)] +pub struct BootPolicy { + pub pref_slot: BootSlot, + pub next_slot: BootSlot, +} + +impl SysmgrClient { + pub const fn new(ipc: IpcChannel) -> Self { + SysmgrClient { ipc } + } + + pub fn get_boot_info(&self) -> Result { + let mut result = 0u32; + let mut info = BootInfo::new_zeroed(); + self.ipc.transaction::<256>( + &[op::SYSMGR_OP_GET_BOOT_INFO.as_bytes()], + &mut [result.as_mut_bytes(), info.as_mut_bytes()], + Instant::MAX, + )?; + IpcChannel::check_status(result)?; + Ok(info) + } + + pub fn set_boot_policy(&self, pref: BootSlot, next: BootSlot) -> Result<(), ErrorCode> { + let mut result = 0u32; + self.ipc.transaction::<256>( + &[op::SYSMGR_OP_SET_BOOT_POLICY.as_bytes(), + pref.as_bytes(), + next.as_bytes(), + ], + &mut [result.as_mut_bytes()], + Instant::MAX, + )?; + IpcChannel::check_status(result) + } + + pub fn request_reboot(&self) -> Result<(), ErrorCode> { + let mut result = 0u32; + self.ipc.transaction::<256>( + &[op::SYSMGR_OP_REQ_REBOOT.as_bytes(), + ], + &mut [result.as_mut_bytes()], + Instant::MAX, + )?; + IpcChannel::check_status(result) + } + +} diff --git a/target/earlgrey/services/sysmgr/server.rs b/target/earlgrey/services/sysmgr/server.rs new file mode 100644 index 00000000..d9877a1a --- /dev/null +++ b/target/earlgrey/services/sysmgr/server.rs @@ -0,0 +1,180 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +use zerocopy::{FromBytes, IntoBytes}; + +use userspace::syscall::Signals; +use userspace::time::Instant; +use userspace::{syscall}; + +use earlgrey_sysmgr_client::*; +use earlgrey_util::boot_svc::NextBl0SlotRequest; +use earlgrey_util::error as eg_error; +use earlgrey_util::ret_ram::RetRam; +use earlgrey_util::tags::BootSlot; +use earlgrey_util::CheckDigest; +use earlgrey_util::GetData; +use util_error::{self as error, ErrorCode}; +use util_ipc::IpcChannel; +use util_types::fourcc::FourCC; +use util_types::Opcode; + +use lc_ctrl::LcCtrl; +use rstmgr::RstmgrAon; +use sha2::{Digest, Sha256}; + +#[allow(dead_code)] +pub struct SysmgrServer { + info: BootInfo, + retram: &'static mut RetRam, +} + +impl SysmgrServer { + pub fn new() -> Result { + let lc_ctrl = unsafe { LcCtrl::new() }; + let lcreg = lc_ctrl.regs(); + + let retram = unsafe { RetRam::mut_ref() }; + if !retram + .boot_log + .check_digest(|data| Sha256::digest(data).into()) + { + return Err(eg_error::EG_ERROR_BAD_BOOT_LOG); + } + + let info = BootInfo { + chip: ChipInfo { + git_hash: retram.boot_log.chip_version.get(), + lc_state: u32::from(lcreg.lc_state().read()), + device_id: lcreg.device_id().read().into(), + creator_id: lcreg.hw_revision0().read().silicon_creator_id() as u16, + product_id: lcreg.hw_revision0().read().product_id() as u16, + revision: lcreg.hw_revision1().read().revision_id() as u8, + _pad: Default::default(), + }, + rom_ext: RomExtInfo { + boot_slot: retram.boot_log.rom_ext_slot, + major: retram.boot_log.rom_ext_major, + minor: retram.boot_log.rom_ext_minor, + min_sec_ver: retram.boot_log.rom_ext_min_sec_ver, + size: retram.boot_log.rom_ext_size, + }, + app: ApplicationInfo { + boot_slot: retram.boot_log.bl0_slot, + pref_slot: retram.boot_log.primary_bl0_slot, + min_sec_ver: retram.boot_log.bl0_min_sec_ver, + // TODO: get from config? + size: 400 * 1024, + // TODO: read from keymgr. + firmware_domain: 0, + }, + ownership: OwnershipInfo { + nonce: retram.boot_log.rom_ext_nonce.get(), + state: retram.boot_log.ownership_state, + transfers: retram.boot_log.ownership_transfers, + }, + reset: ResetInfo { + reason: retram.reset_reasons, + retram_init: retram.boot_log.retention_ram_initialized, + // TODO: read gpio strapping value. + hardware_straps: 0, + // TODO: get from config? + software_straps: 0, + }, + }; + + pw_log::info!("Earlgrey System Manager"); + pw_log::info!( + "chip: {:04x}-{:04x}-{:02x} / {:016x}", + info.chip.creator_id as u16, + info.chip.product_id as u16, + info.chip.revision as u8, + info.chip.git_hash as u64, + ); + pw_log::info!( + "ROM_EXT: {}.{} in {}", + info.rom_ext.major as u32, + info.rom_ext.minor as u32, + info.rom_ext.boot_slot.as_str() as &str, + ); + pw_log::info!( + "Application in {} (prefer {})", + info.app.boot_slot.as_str() as &str, + info.app.pref_slot.as_str() as &str, + ); + pw_log::info!("Reset reasons: {:08x}", info.reset.reason as u32); + + Ok(Self { info, retram }) + } + + fn handle_get_boot_info<'a>(&self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + let info = self.info.as_bytes(); + data[..info.len()].copy_from_slice(info); + Ok(&data[..info.len()]) + } + + fn handle_req_reboot<'a>(&self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + pw_log::info!("RESET REQUESTED!"); + let mut rstmgr = unsafe { RstmgrAon::new() }; + rstmgr.regs_mut().reset_req().write(|_| 6u32.into()); + Ok(&data[0..0]) + } + + fn handle_set_boot_policy<'a>(&mut self, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + let (pref, rest) = + BootSlot::ref_from_prefix(data).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let (next, _rest) = + BootSlot::ref_from_prefix(rest).map_err(|_| error::IPC_ERROR_BAD_REQ_LEN)?; + let request: &mut NextBl0SlotRequest = self.retram.boot_svc.get_mut(); + request.next_bl0_slot = *next; + request.primary_bl0_slot = *pref; + self.retram + .boot_svc + .set_digest(|data| Sha256::digest(data).into()); + Ok(&data[0..0]) + } + + fn handle_op<'a>(&mut self, opcode: Opcode, data: &'a mut [u8]) -> Result<&'a [u8], ErrorCode> { + match opcode { + op::SYSMGR_OP_GET_BOOT_INFO => self.handle_get_boot_info(data), + op::SYSMGR_OP_REQ_REBOOT => self.handle_req_reboot(data), + op::SYSMGR_OP_SET_BOOT_POLICY => self.handle_set_boot_policy(data), + _ => Err(error::IPC_ERROR_UNKNOWN_OP), + } + } + + fn handle_one(&mut self, ipc: &IpcChannel, data: &mut [u8]) -> Result<(), ErrorCode> { + //ipc.wait_readable()?; + let len = ipc.read(0, data)?; + if len < 4 { + return Err(error::IPC_ERROR_BAD_REQ_LEN); + } + let (op_status, reqrsp) = data.split_at_mut(4); + let opcode = Opcode::read_from_bytes(op_status).unwrap(); + let len = match self.handle_op(opcode, reqrsp) { + Ok(result) => { + op_status.copy_from_slice((0u32).as_bytes()); + result.len() + } + Err(e) => { + op_status.copy_from_slice(e.0.as_bytes()); + 0 + } + }; + ipc.respond(&data[..4 + len])?; + Ok(()) + } + + pub fn run(&mut self, wg: u32, ipc: &[&IpcChannel], data: &mut [u8]) -> Result<(), ErrorCode> { + for (i, chan) in ipc.iter().enumerate() { + syscall::wait_group_add(wg, chan.handle(), Signals::READABLE, i).map_err(ErrorCode::kernel_error)?; + } + loop { + let w = syscall::object_wait(wg, Signals::READABLE, Instant::MAX).map_err(ErrorCode::kernel_error)?; + if w.pending_signals.contains(Signals::READABLE) { + self.handle_one(ipc[w.user_data], data)?; + } + } + } +} diff --git a/target/earlgrey/tests/eflash/BUILD.bazel b/target/earlgrey/tests/eflash/BUILD.bazel new file mode 100644 index 00000000..b284b49b --- /dev/null +++ b/target/earlgrey/tests/eflash/BUILD.bazel @@ -0,0 +1,156 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_app( + name = "flash_server", + srcs = [ + "flash_server.rs", + ], + codegen_crate_name = "flash_server_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash", + "//services/flash:server", + "//target/earlgrey/drivers:eflash_driver", + "//target/earlgrey/registers:flash_ctrl_core", + "//util/error", + "//util/ipc", + "//util/types", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_app( + name = "flash_test", + srcs = [ + "flash_test.rs", + ], + codegen_crate_name = "flash_test_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash", + "//services/flash:client", + "//target/earlgrey/util", + "//util/console", + "//util/error", + "//util/ipc", + "@pigweed//pw_base64/rust:pw_base64", + "@pigweed//pw_kernel/lib/pw_assert", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +system_image( + name = "flash", + apps = [ + ":flash_server", + ":flash_test", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "flash_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":flash", +) + +opentitan_test( + name = "flash_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":flash", +) + +opentitan_test( + name = "flash_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":flash", +) + +opentitan_test( + name = "flash_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":flash", +) diff --git a/target/earlgrey/tests/eflash/flash_server.rs b/target/earlgrey/tests/eflash/flash_server.rs new file mode 100644 index 00000000..8a4e37cb --- /dev/null +++ b/target/earlgrey/tests/eflash/flash_server.rs @@ -0,0 +1,75 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use flash_server_codegen::{handle, signals}; +use pw_status::{Error}; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use hal_flash::{BlockingFlash, FlashAddress}; +use services_flash_server::FlashIpcServer; +use util_types::Blocking; +use util_ipc::IpcChannel; +use util_error::ErrorCode; +use eflash_driver::{EmbeddedFlash, Permission}; + +struct FlashCtrlInterrupt; + +impl Blocking for FlashCtrlInterrupt { + fn wait_for_notification(&self) { + loop { + let w = syscall::object_wait( + handle::FLASH_INTERRUPTS, + signals::FLASH_CTRL_OP_DONE, + Instant::MAX, + ) + .unwrap(); + if w.pending_signals.contains(signals::FLASH_CTRL_OP_DONE) { + break; + } + } + let _ = syscall::interrupt_ack(handle::FLASH_INTERRUPTS, signals::FLASH_CTRL_OP_DONE); + } +} + +fn flash_server() -> Result<(), ErrorCode> { + let mut driver = EmbeddedFlash::new_with_interrupts( + unsafe { flash_ctrl_core::FlashCtrl::new() }); + driver.set_default_permission(Permission::FULL_ACCESS); + for i in 5..9 { + driver.set_info_permission(FlashAddress::info(0, i, 0), Permission::FULL_ACCESS)?; + driver.set_info_permission(FlashAddress::info(1, i, 0), Permission::FULL_ACCESS)?; + } + let flash = BlockingFlash { + driver, + blocking: FlashCtrlInterrupt, + }; + let mut flash_server = FlashIpcServer::new(flash); + let mut buf = [0u8; 2056]; + let ipc = IpcChannel::new(handle::FLASH_SERVICE); + flash_server.run(&ipc, &mut buf) +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + let ret = flash_server(); + + // Log that an error occurred so that the app that caused the shutdown is logged. + let ret = match ret { + Ok(()) => { pw_log::info!("✅ PASSED"); Ok(()) } + Err(e) => { pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); Err(Error::Unknown) } + }; + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/eflash/flash_test.rs b/target/earlgrey/tests/eflash/flash_test.rs new file mode 100644 index 00000000..6dbe358c --- /dev/null +++ b/target/earlgrey/tests/eflash/flash_test.rs @@ -0,0 +1,132 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use flash_test_codegen::handle; +use pw_status::Error; +use userspace::{entry, syscall}; + +use earlgrey_util::PersoCertificate; +use hal_flash::{Flash, FlashAddress}; +use services_flash_client::FlashIpcClient; +use util_error::ErrorCode; +use util_ipc::IpcChannel; + +fn get_manifest(flash: &mut FlashIpcClient) -> Result<(), ErrorCode> { + let mut buf = [0u8; 1024]; + flash.read(FlashAddress::data(0), &mut buf)?; + pw_log::info!("ROM_EXT manifest header:"); + util_console::hexdump::hexdump(&buf); + Ok(()) +} + +fn get_certificates(flash: &mut FlashIpcClient) -> Result<(), ErrorCode> { + const BEGIN_CERT: &'static str = "-----BEGIN CERTIFICATE-----"; + const END_CERT: &'static str = "-----END CERTIFICATE-----"; + + let mut buf = [0u8; 1024]; + let mut output = [0u8; 1200]; + + pw_log::info!("Reading UDS cert"); + // Read out the UDS and print it if it exists. + // The UDS (factory) cert is located in bank=0, page=9. + if flash.read(FlashAddress::info(0, 9, 0), &mut buf).is_ok() { + let cert = PersoCertificate::from_bytes(&buf); + if let Ok((uds, _rest)) = cert { + pw_log::info!( + "Certificate: {}\n{}\n{}\n{}", + uds.name, + BEGIN_CERT as &str, + pw_base64::encode_str(uds.certificate, &mut output) + .map_err(ErrorCode::kernel_error)? as &str, + END_CERT as &str, + ); + } + } + + pw_log::info!("Reading CDI certs"); + // Read out the CDI certificates and print them. + // The CDI (dice) certs are located in bank=1, page=9. + let mut offset = 0usize; + loop { + let sz = core::cmp::min(2048 - offset, buf.len()); + flash.read(FlashAddress::info(1, 9, offset as u32), &mut buf[..sz])?; + match PersoCertificate::from_bytes(&buf) { + Ok((cdi, _)) => { + pw_log::info!( + "Certificate: {}\n{}\n{}\n{}", + cdi.name, + BEGIN_CERT as &str, + pw_base64::encode_str(cdi.certificate, &mut output) + .map_err(ErrorCode::kernel_error)? as &str, + END_CERT as &str, + ); + offset += (cdi.obj_size + 7) & !7; + } + Err(_) => break, + } + } + Ok(()) +} + +fn erase_program_test(flash: &mut FlashIpcClient, addr: FlashAddress) -> Result<(), ErrorCode> { + pw_log::info!("Erasing {:08x}", addr.offset()); + flash.erase_page(addr)?; + pw_log::info!("Reading {:08x}", addr.offset()); + let mut buf = [0u8; 32]; + flash.read(addr, &mut buf)?; + util_console::hexdump::hexdump(&buf); + + pw_log::info!("Programming {:08x}", addr.offset()); + flash.program(addr, b"This is a test.")?; + + pw_log::info!("Reading {:08x}", addr.offset()); + flash.read(addr, &mut buf)?; + util_console::hexdump::hexdump(&buf); + + Ok(()) +} + +fn flash_test() -> Result<(), ErrorCode> { + let mut flash = FlashIpcClient::new(IpcChannel::new(handle::FLASH_SERVICE))?; + + pw_log::info!("Flash size: {}", flash.size().get() as usize); + pw_log::info!("Flash page size: {}", flash.page_size().get() as usize); + + get_manifest(&mut flash)?; + get_certificates(&mut flash)?; + + // We're currently executing in SlotA, so we should be able to access SlotB. + erase_program_test(&mut flash, FlashAddress::data(0x90000))?; + erase_program_test(&mut flash, FlashAddress::info(0, 5, 0))?; + Ok(()) +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + let ret = flash_test(); + + // Log that an error occurred so that the app that caused the shutdown is logged. + let ret = match ret { + Ok(()) => { + pw_log::info!("✅ PASSED"); + Ok(()) + } + Err(e) => { + pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); + Err(Error::Unknown) + } + }; + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/eflash/system.json5 b/target/earlgrey/tests/eflash/system.json5 new file mode 100644 index 00000000..244068e6 --- /dev/null +++ b/target/earlgrey/tests/eflash/system.json5 @@ -0,0 +1,86 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "flash_server", + flash_size_bytes: 16384, + processes: [ + { + name: "flash_server", + ram_size_bytes: 4096, + objects: [ + { + name: "flash_service", + type: "channel_handler", + }, + { + name: "flash_interrupts", + type: "interrupt", + irqs: [ + //{ name: "flash_ctrl_prog_empty", number: 160 }, + //{ name: "flash_ctrl_prog_lvl", number: 161 }, + //{ name: "flash_ctrl_rd_full", number: 162 }, + //{ name: "flash_ctrl_rd_lvl", number: 163 }, + { name: "flash_ctrl_op_done", number: 164 }, + //{ name: "flash_ctrl_corr_err", number: 165 }, + ], + } + ], + memory_mappings: [ + { + name: "flash_ctrl_core", + type: "device", + start_address: 0x41000000, + size_bytes: 0x200, + } + ], + threads: [ + { + name: "flash_server_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }, + ], + }, + + { + name: "flash_test", + flash_size_bytes: 16384, + processes: [ + { + name: "flash_test", + ram_size_bytes: 6144, + objects: [ + { + name: "flash_service", + type: "channel_initiator", + handler_process: "flash_server", + handler_object_name: "flash_service", + }, + ], + threads: [ + { + name: "flash_test_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }, + ], + }, + + ], +} diff --git a/target/earlgrey/tests/eflash/target.rs b/target/earlgrey/tests/eflash/target.rs new file mode 100644 index 00000000..2e253d37 --- /dev/null +++ b/target/earlgrey/tests/eflash/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/retram/BUILD.bazel b/target/earlgrey/tests/retram/BUILD.bazel new file mode 100644 index 00000000..4f45c07a --- /dev/null +++ b/target/earlgrey/tests/retram/BUILD.bazel @@ -0,0 +1,149 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_app( + name = "test_retram", + srcs = [ + "test_retram.rs", + ], + codegen_crate_name = "test_retram_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//target/earlgrey/services/sysmgr:client", + "//target/earlgrey/util", + "//util/console", + "//util/error", + "//util/ipc", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_app( + name = "sysmgr", + srcs = [ + "sysmgr.rs", + ], + codegen_crate_name = "sysmgr_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//target/earlgrey/services/sysmgr:server", + "//target/earlgrey/util", + "//util/error", + "//util/ipc", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +system_image( + name = "retram", + apps = [ + ":test_retram", + ":sysmgr", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "retram_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":retram", +) + +opentitan_test( + name = "retram_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":retram", +) + +opentitan_test( + name = "retram_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":retram", +) + +opentitan_test( + name = "retram_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":retram", +) diff --git a/target/earlgrey/tests/retram/sysmgr.rs b/target/earlgrey/tests/retram/sysmgr.rs new file mode 100644 index 00000000..4545fe4d --- /dev/null +++ b/target/earlgrey/tests/retram/sysmgr.rs @@ -0,0 +1,38 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use pw_status::Error; +use sysmgr_codegen::handle; +use userspace::{entry, syscall}; + +use earlgrey_sysmgr_server::SysmgrServer; +use util_error::ErrorCode; +use util_ipc::IpcChannel; + +fn sysmgr_server() -> Result<(), ErrorCode> { + let mut sysmgr = SysmgrServer::new()?; + let mut buf = [0u8; 512]; + sysmgr.run( + handle::SYSMGR_WAIT_GROUP, + &[&IpcChannel::new(handle::SYSMGR_SERVICE)], + &mut buf, + ) +} + +#[entry] +fn entry() -> ! { + let ret = sysmgr_server().map_err(|e| { + pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); + Error::Unknown + }); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/retram/system.json5 b/target/earlgrey/tests/retram/system.json5 new file mode 100644 index 00000000..386f53d1 --- /dev/null +++ b/target/earlgrey/tests/retram/system.json5 @@ -0,0 +1,87 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "sysmgr", + flash_size_bytes: 16384, + processes: [{ + name: "sysmgr_process", + ram_size_bytes: 4096, + objects: [ + { + name: "sysmgr_wait_group", + type: "wait_group", + }, + { + name: "sysmgr_service", + type: "channel_handler", + }, + ], + memory_mappings: [ + { + name: "retram", + type: "device", + start_address: 0x40600000, + size_bytes: 0x1000, + }, + { + name: "rstmgr", + type: "device", + start_address: 0x40410000, + size_bytes: 0x80, + }, + { + name: "lc_ctrl", + type: "device", + start_address: 0x40140000, + size_bytes: 0x100, + }, + + ], + threads: [ + { + name: "sysmgr_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + { + name: "test_retram", + flash_size_bytes: 16384, + processes: [{ + name: "test_retram_process", + ram_size_bytes: 4096, + objects: [ + { + name: "sysmgr_service", + type: "channel_initiator", + handler_process: "sysmgr_process", + handler_object_name: "sysmgr_service", + }, + ], + memory_mappings: [ + ], + threads: [ + { + name: "retram_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + ], +} diff --git a/target/earlgrey/tests/retram/target.rs b/target/earlgrey/tests/retram/target.rs new file mode 100644 index 00000000..2e253d37 --- /dev/null +++ b/target/earlgrey/tests/retram/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/retram/test_retram.rs b/target/earlgrey/tests/retram/test_retram.rs new file mode 100644 index 00000000..3f07bbd3 --- /dev/null +++ b/target/earlgrey/tests/retram/test_retram.rs @@ -0,0 +1,58 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use test_retram_codegen::{handle}; +use userspace::{entry, syscall }; +use pw_status::{ Error}; +use earlgrey_sysmgr_client::SysmgrClient; +use earlgrey_util::tags::BootSlot; +use util_error::ErrorCode; +use util_ipc::IpcChannel; +use util_console::println; + + +fn test_retram() -> Result<(),ErrorCode> { + let sysmgr = SysmgrClient::new(IpcChannel::new(handle::SYSMGR_SERVICE)); + + let boot_info = sysmgr.get_boot_info()?; + println!("BootInfo = {:#?}", boot_info); + + if boot_info.reset.reason == 1 { + // If power on reset, reset once more. + println!("Preparing to boot slot B"); + sysmgr.set_boot_policy(BootSlot::SlotB, BootSlot::Unspecified)?; + + println!("Reboot"); + sysmgr.request_reboot()?; + } + Ok(()) +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + + // Log that an error occurred so that the app that caused the shutdown is logged. + let ret = match test_retram() { + Ok(()) => { + pw_log::info!("✅ PASSED"); + Ok(()) + } + Err(e) => { + pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); + Err(Error::Unknown) + } + }; + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/usbdev/BUILD.bazel b/target/earlgrey/tests/usbdev/BUILD.bazel new file mode 100644 index 00000000..5f9d7bd8 --- /dev/null +++ b/target/earlgrey/tests/usbdev/BUILD.bazel @@ -0,0 +1,130 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_app( + name = "test_usb", + srcs = [ + "test_usb.rs", + ], + codegen_crate_name = "test_usb_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/usb:hal_usb", + "//protocol/usb/stack", + "//target/earlgrey/drivers:usb_driver", + "//target/earlgrey/registers:pinmux", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/registers:usbdev", + "//util/console", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:aligned", + ], +) + +system_image( + name = "usb", + apps = [ + ":test_usb", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "usb_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":usb", +) diff --git a/target/earlgrey/tests/usbdev/system.json5 b/target/earlgrey/tests/usbdev/system.json5 new file mode 100644 index 00000000..87cfd0ad --- /dev/null +++ b/target/earlgrey/tests/usbdev/system.json5 @@ -0,0 +1,77 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "test_usb", + flash_size_bytes: 16384, + processes: [{ + name: "test_usb_process", + ram_size_bytes: 4096, + objects: [ + { + name: "usbdev_interrupts", + type: "interrupt", + irqs: [ + { name: "usbdev_pkt_received", number: 135 }, + { name: "usbdev_pkt_sent", number: 136 }, + { name: "usbdev_disconnected", number: 137 }, + { name: "usbdev_host_lost", number: 138 }, + + { name: "usbdev_link_reset", number: 139 }, + { name: "usbdev_link_suspend", number: 140 }, + { name: "usbdev_link_resume", number: 141 }, + { name: "usbdev_av_out_empty", number: 142 }, + + { name: "usbdev_rx_full", number: 143 }, + { name: "usbdev_av_overflow", number: 144 }, + //{ name: "usbdev_link_in_err", number: 145 }, + { name: "usbdev_rx_crc_err", number: 146 }, + + { name: "usbdev_rx_pid_err", number: 147 }, + { name: "usbdev_rx_bitstuff_err", number: 148 }, + { name: "usbdev_frame", number: 149 }, + //{ name: "usbdev_powered", number: 150 }, + + //{ name: "usbdev_link_out_err", number: 151 }, + { name: "usbdev_av_setup_empty", number: 152 }, + ], + }, + ], + memory_mappings: [ + { + name: "usbdev", + type: "device", + start_address: 0x40320000, + size_bytes: 0x1000, + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000, + } + + ], + threads: [ + { + name: "usb_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + ], +} diff --git a/target/earlgrey/tests/usbdev/target.rs b/target/earlgrey/tests/usbdev/target.rs new file mode 100644 index 00000000..2e253d37 --- /dev/null +++ b/target/earlgrey/tests/usbdev/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/usbdev/test_usb.rs b/target/earlgrey/tests/usbdev/test_usb.rs new file mode 100644 index 00000000..29ba26ca --- /dev/null +++ b/target/earlgrey/tests/usbdev/test_usb.rs @@ -0,0 +1,220 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use test_usb_codegen::{handle, signals}; +use userspace::time::Instant; +use userspace::{entry, syscall}; +use pw_status::Error; + +use aligned::{Aligned, A4}; +use hal_usb::driver::{UsbDriver, UsbEvent, UsbPacket}; +use hal_usb::{Direction, StringDescriptorRef, USB_CLASS_VENDOR}; +use usb_driver::{EpIn, EpOut, UsbConfig}; +use usb_stack::{ + //UsbActionRun, + DescriptorSource, + UsbAction, +}; + +const USB_VENDOR_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(1); +const USB_PRODUCT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(2); +const USB_SERIAL_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(3); +const USB_TEST_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(4); + +static DEVICE_DESC: hal_usb::DeviceDescriptor = hal_usb::DeviceDescriptor { + device_class: hal_usb::DeviceClass::SPECIFIED_BY_INTERFACE, + device_sub_class: 0x00, + device_protocol: 0x00, + max_packet_size: 64, + vendor_id: 0x18d1, // Google, Inc. + product_id: 0x503a, // STWG USB Fullspeed IP. + device_release_num: 0x0100, + manufacturer: USB_VENDOR_HANDLE, + product: USB_PRODUCT_HANDLE, + serial_num: USB_SERIAL_HANDLE, +}; +const CONFIG_DESC: hal_usb::ConfigDescriptor = hal_usb::ConfigDescriptor { + configuration_value: 1, + max_power: 250, + self_powered: false, + remote_wakeup: false, + interfaces: &[hal_usb::InterfaceDescriptor { + name: USB_TEST_HANDLE, + interface_number: 1, + alternate_setting: 0, + interface_class: USB_CLASS_VENDOR, + interface_sub_class: 0xFF, + interface_protocol: 1, + func_descs: &[], + endpoints: &[ + hal_usb::EndpointDescriptor { + direction: Direction::DeviceToHost, + endpoint_num: 1, + interval: 0, + max_packet_size: 64, + transfer_type: hal_usb::TransferType::Bulk, + }, + hal_usb::EndpointDescriptor { + direction: Direction::HostToDevice, + endpoint_num: 2, + interval: 0, + max_packet_size: 64, + transfer_type: hal_usb::TransferType::Bulk, + }, + ], + }], +}; + +const STRING_DESC_0: hal_usb::StringDescriptor0 = hal_usb::StringDescriptor0 { + langs: &[ + // English - United States + 0x0409, + ], +}; + +const VENDOR_ID: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("Google Inc.").as_ref(); +const PRODUCT_ID_DEFAULT: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("Earlgrey").as_ref(); +const USB_TEST: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("USB Test Interface").as_ref(); + +struct MyDescriptors<'a> { + serial_desc_bytes: StringDescriptorRef<'a>, + product_desc_bytes: StringDescriptorRef<'a>, +} + +impl DescriptorSource for MyDescriptors<'_> { + const DEVICE_DESC_BYTES: &'static Aligned = &Aligned(DEVICE_DESC.serialize()); + const CONFIG_DESC_BYTES: &'static Aligned = + &Aligned(CONFIG_DESC.serialize::<{ CONFIG_DESC.total_size() }>()); + const STRING_DESC_0_BYTES: &'static Aligned = + &Aligned(STRING_DESC_0.serialize::<{ STRING_DESC_0.total_size() }>()); + // We advertise that we are self-powered and do not support remote wakeup. + const DEVICE_STATUS: Aligned = Aligned([1u8, 0]); + + fn get_string( + &self, + handle: hal_usb::StringHandle, + _lang: u16, + ) -> Option> { + match handle { + USB_VENDOR_HANDLE => Some(VENDOR_ID), + USB_PRODUCT_HANDLE => Some(self.product_desc_bytes), + USB_SERIAL_HANDLE => Some(self.serial_desc_bytes), + USB_TEST_HANDLE => Some(USB_TEST), + _ => None, + } + } +} + +const CONTROL_EP_OUT_NUM: u8 = 0; + +fn handle_usb() -> Result<(), Error> { + let mut serial_num_buffer = Aligned::([0_u8; 130]); + // TODO + //let mut product_desc_buffer = Aligned::([0_u8; 100]); + let descriptors = MyDescriptors { + serial_desc_bytes: hal_usb::hex_utf16_descriptor_aligned(&mut serial_num_buffer, b"12345") + .unwrap(), + product_desc_bytes: PRODUCT_ID_DEFAULT, + }; + const USB_EP_IN: EpIn = EpIn { + num: 1, + buf_pool_size: 8, + }; + const USB_EP_OUT: EpOut = EpOut { + num: 2, + set_nak: true, + }; + + const USB_CONFIG: UsbConfig = UsbConfig::new(&[USB_EP_IN], &[USB_EP_OUT]); + let mut usb = usb_driver::Usb::new(unsafe { usbdev::Usbdev::new() }, USB_CONFIG); + let mut ep0 = usb_stack::SimpleEp0::new(); + let mut ep0_action: UsbAction<'_> = UsbAction::None; + + loop { + let wait_return = syscall::object_wait( + handle::USBDEV_INTERRUPTS, + signals::USBDEV_PKT_RECEIVED + | signals::USBDEV_PKT_SENT + | signals::USBDEV_DISCONNECTED + | signals::USBDEV_HOST_LOST + | signals::USBDEV_LINK_RESET + | signals::USBDEV_LINK_SUSPEND + | signals::USBDEV_LINK_RESUME + | signals::USBDEV_AV_OUT_EMPTY + | signals::USBDEV_RX_FULL + | signals::USBDEV_AV_OVERFLOW + //| signals::USBDEV_LINK_IN_ERR + | signals::USBDEV_RX_CRC_ERR + | signals::USBDEV_RX_PID_ERR + | signals::USBDEV_RX_BITSTUFF_ERR + | signals::USBDEV_FRAME + //| signals::USBDEV_POWERED + //| signals::USBDEV_LINK_OUT_ERR + | signals::USBDEV_AV_SETUP_EMPTY, + Instant::MAX, + )?; + + if wait_return.user_data != 0 { + pw_log::error!("Incorrect WaitReturn values"); + return Err(Error::Unknown); + } + while let Some(event) = usb.poll() { + match event { + UsbEvent::SetupPacket { pkt, endpoint } => { + if endpoint == 0 { + console::println!("SETUP: {:?}", pkt); + ep0_action = ep0.handle_event(event, &descriptors); + } else { + console::println!("Setup on bad EP {:?}", endpoint); + } + } + + UsbEvent::DataOutPacket(pkt) => match u8::try_from(pkt.endpoint_index()).unwrap() { + CONTROL_EP_OUT_NUM => { + console::println!("OUT on control ep"); + } + ep => { + console::println!("Unhandled OUT on EP {} len={}", ep, pkt.len()); + } + }, + UsbEvent::UsbReset => { + console::println!("USB reset"); + } + _ => { + ep0_action.merge(ep0.handle_event(event, &descriptors)); + } + } + ep0_action.run(&mut usb); + } + } +} + +fn usb_setup_pinmux() { + use top_earlgrey::{PinmuxInsel, PinmuxPeripheralIn}; + let mut pinmux = unsafe { pinmux::PinmuxAon::new() }; + + pinmux + .regs_mut() + .mio_periph_insel() + .at(PinmuxPeripheralIn::UsbdevSense as usize) + .modify(|_| (PinmuxInsel::ConstantOne as u32).into()); +} + +#[entry] +fn entry() -> ! { + // Since this is written as a test, shut down with the return status from `main()`. + usb_setup_pinmux(); + let ret = handle_usb(); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/usbdfu/BUILD.bazel b/target/earlgrey/tests/usbdfu/BUILD.bazel new file mode 100644 index 00000000..f895b54c --- /dev/null +++ b/target/earlgrey/tests/usbdfu/BUILD.bazel @@ -0,0 +1,161 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_app( + name = "test_usb_dfu", + srcs = [ + "test_usb.rs", + ], + codegen_crate_name = "test_usb_dfu_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash", + "//hal/blocking/usb:hal_usb", + "//protocol/usb/dfu", + "//protocol/usb/stack", + "//services/flash:client", + "//target/earlgrey/drivers:usb_driver", + "//target/earlgrey/registers:pinmux", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/registers:usbdev", + "//target/earlgrey/util", + "//util/console", + "//util/error", + "//util/ipc", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:aligned", + ], +) + +rust_app( + name = "flash_server", + srcs = [ + "flash_server.rs", + ], + codegen_crate_name = "flash_server_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/flash", + "//services/flash:server", + "//target/earlgrey/drivers:eflash_driver", + "//target/earlgrey/registers:flash_ctrl_core", + "//util/error", + "//util/ipc", + "//util/types", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +system_image( + name = "usb", + apps = [ + ":test_usb_dfu", + ":flash_server", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "usb_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":usb", +) diff --git a/target/earlgrey/tests/usbdfu/flash_server.rs b/target/earlgrey/tests/usbdfu/flash_server.rs new file mode 100644 index 00000000..8a4e37cb --- /dev/null +++ b/target/earlgrey/tests/usbdfu/flash_server.rs @@ -0,0 +1,75 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use flash_server_codegen::{handle, signals}; +use pw_status::{Error}; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use hal_flash::{BlockingFlash, FlashAddress}; +use services_flash_server::FlashIpcServer; +use util_types::Blocking; +use util_ipc::IpcChannel; +use util_error::ErrorCode; +use eflash_driver::{EmbeddedFlash, Permission}; + +struct FlashCtrlInterrupt; + +impl Blocking for FlashCtrlInterrupt { + fn wait_for_notification(&self) { + loop { + let w = syscall::object_wait( + handle::FLASH_INTERRUPTS, + signals::FLASH_CTRL_OP_DONE, + Instant::MAX, + ) + .unwrap(); + if w.pending_signals.contains(signals::FLASH_CTRL_OP_DONE) { + break; + } + } + let _ = syscall::interrupt_ack(handle::FLASH_INTERRUPTS, signals::FLASH_CTRL_OP_DONE); + } +} + +fn flash_server() -> Result<(), ErrorCode> { + let mut driver = EmbeddedFlash::new_with_interrupts( + unsafe { flash_ctrl_core::FlashCtrl::new() }); + driver.set_default_permission(Permission::FULL_ACCESS); + for i in 5..9 { + driver.set_info_permission(FlashAddress::info(0, i, 0), Permission::FULL_ACCESS)?; + driver.set_info_permission(FlashAddress::info(1, i, 0), Permission::FULL_ACCESS)?; + } + let flash = BlockingFlash { + driver, + blocking: FlashCtrlInterrupt, + }; + let mut flash_server = FlashIpcServer::new(flash); + let mut buf = [0u8; 2056]; + let ipc = IpcChannel::new(handle::FLASH_SERVICE); + flash_server.run(&ipc, &mut buf) +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + let ret = flash_server(); + + // Log that an error occurred so that the app that caused the shutdown is logged. + let ret = match ret { + Ok(()) => { pw_log::info!("✅ PASSED"); Ok(()) } + Err(e) => { pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); Err(Error::Unknown) } + }; + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/usbdfu/system.json5 b/target/earlgrey/tests/usbdfu/system.json5 new file mode 100644 index 00000000..c85aeeaa --- /dev/null +++ b/target/earlgrey/tests/usbdfu/system.json5 @@ -0,0 +1,126 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + + { + name: "flash_server", + flash_size_bytes: 16384, + processes: [ + { + name: "flash_server", + ram_size_bytes: 4096, + objects: [ + { + name: "flash_service", + type: "channel_handler", + }, + { + name: "flash_interrupts", + type: "interrupt", + irqs: [ + //{ name: "flash_ctrl_prog_empty", number: 160 }, + //{ name: "flash_ctrl_prog_lvl", number: 161 }, + //{ name: "flash_ctrl_rd_full", number: 162 }, + //{ name: "flash_ctrl_rd_lvl", number: 163 }, + { name: "flash_ctrl_op_done", number: 164 }, + //{ name: "flash_ctrl_corr_err", number: 165 }, + ], + } + ], + memory_mappings: [ + { + name: "flash_ctrl_core", + type: "device", + start_address: 0x41000000, + size_bytes: 0x200, + } + ], + threads: [ + { + name: "flash_server_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }, + ], + }, + { + name: "test_usb_dfu", + flash_size_bytes: 16384, + processes: [{ + name: "test_usb_dfu_process", + ram_size_bytes: 6144, + objects: [ + { + name: "flash_service", + type: "channel_initiator", + handler_process: "flash_server", + handler_object_name: "flash_service", + }, + { + name: "usbdev_interrupts", + type: "interrupt", + irqs: [ + { name: "usbdev_pkt_received", number: 135 }, + { name: "usbdev_pkt_sent", number: 136 }, + { name: "usbdev_disconnected", number: 137 }, + { name: "usbdev_host_lost", number: 138 }, + + { name: "usbdev_link_reset", number: 139 }, + { name: "usbdev_link_suspend", number: 140 }, + { name: "usbdev_link_resume", number: 141 }, + { name: "usbdev_av_out_empty", number: 142 }, + + { name: "usbdev_rx_full", number: 143 }, + { name: "usbdev_av_overflow", number: 144 }, + //{ name: "usbdev_link_in_err", number: 145 }, + { name: "usbdev_rx_crc_err", number: 146 }, + + { name: "usbdev_rx_pid_err", number: 147 }, + { name: "usbdev_rx_bitstuff_err", number: 148 }, + { name: "usbdev_frame", number: 149 }, + //{ name: "usbdev_powered", number: 150 }, + + //{ name: "usbdev_link_out_err", number: 151 }, + { name: "usbdev_av_setup_empty", number: 152 }, + ], + }, + ], + memory_mappings: [ + { + name: "usbdev", + type: "device", + start_address: 0x40320000, + size_bytes: 0x1000, + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000, + } + + ], + threads: [ + { + name: "usb_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + ], +} diff --git a/target/earlgrey/tests/usbdfu/target.rs b/target/earlgrey/tests/usbdfu/target.rs new file mode 100644 index 00000000..2e253d37 --- /dev/null +++ b/target/earlgrey/tests/usbdfu/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/usbdfu/test_usb.rs b/target/earlgrey/tests/usbdfu/test_usb.rs new file mode 100644 index 00000000..e5f2ac08 --- /dev/null +++ b/target/earlgrey/tests/usbdfu/test_usb.rs @@ -0,0 +1,280 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +#![allow(dead_code)] + +use pw_status::Error; +use test_usb_dfu_codegen::{handle, signals}; +use userspace::time::Instant; +use userspace::{entry, syscall}; + +use aligned::{Aligned, A4}; +use hal_usb::{ConfigDescriptor, DeviceDescriptor, StringDescriptorRef}; + +use hal_usb::driver::UsbDriver; +use usb_driver::UsbConfig; +use usb_stack::{DescriptorSource, UsbAction, UsbClass}; + +use earlgrey_util::PersoCertificate; +use hal_flash::{Flash, FlashAddress}; +use services_flash_client::FlashIpcClient; +use util_error::{self as error, ErrorCode}; +use util_ipc::IpcChannel; + +use protocol_usb_dfu::{DfuBuilder, DfuClass, DfuHandler, DfuResult, DfuStatus}; + +const USB_VENDOR_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(1); +const USB_PRODUCT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(2); +const USB_SERIAL_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(3); +const DFU_FIRMWARE_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(4); +const DFU_UDS_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(5); +const DFU_CDI0_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(6); +const DFU_CDI1_CERT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(7); + +const DFU_BUILDER: DfuBuilder = DfuBuilder::new( + 0, // interface_num + 1, // alt_settings (1 for now) + 2048, // transfer_size +); + +const DEVICE_DESC: DeviceDescriptor = DeviceDescriptor { + device_class: hal_usb::DeviceClass::SPECIFIED_BY_INTERFACE, + device_sub_class: 0x00, + device_protocol: 0x00, + max_packet_size: 64, + vendor_id: 0x18d1, // Google, Inc. + product_id: 0x503a, // STWG USB Fullspeed IP. + device_release_num: 0x0100, + manufacturer: USB_VENDOR_HANDLE, + product: USB_PRODUCT_HANDLE, + serial_num: USB_SERIAL_HANDLE, +}; + +const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { + configuration_value: 1, + max_power: 250, + self_powered: false, + remote_wakeup: false, + interfaces: &[ + DFU_BUILDER.interface( 0, DFU_FIRMWARE_HANDLE, &[]), + DFU_BUILDER.interface( 1, DFU_UDS_CERT_HANDLE, &[]), + DFU_BUILDER.interface( 2, DFU_CDI0_CERT_HANDLE, &[]), + DFU_BUILDER.interface( 3, DFU_CDI1_CERT_HANDLE, + &[DFU_BUILDER.functional_descriptor()]), + ], +}; + +const STRING_DESC_0: hal_usb::StringDescriptor0 = hal_usb::StringDescriptor0 { + langs: &[ + // English - United States + 0x0409, + ], +}; + +const VENDOR_ID: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("Google Inc.").as_ref(); +const PRODUCT_ID_DEFAULT: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("Earlgrey DFU").as_ref(); +const DFU_FIRMWARE: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("Firmware").as_ref(); +const DFU_UDS_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("UDS Certificate").as_ref(); +const DFU_CDI0_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDI0 Certificate").as_ref(); +const DFU_CDI1_CERT: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("CDI1 Certificate").as_ref(); + +struct MyDescriptors<'a> { + serial_desc_bytes: StringDescriptorRef<'a>, + product_desc_bytes: StringDescriptorRef<'a>, +} + +impl DescriptorSource for MyDescriptors<'_> { + const DEVICE_DESC_BYTES: &'static Aligned = &Aligned(DEVICE_DESC.serialize()); + const CONFIG_DESC_BYTES: &'static Aligned = + &Aligned(CONFIG_DESC.serialize::<{ CONFIG_DESC.total_size() }>()); + const STRING_DESC_0_BYTES: &'static Aligned = + &Aligned(STRING_DESC_0.serialize::<{ STRING_DESC_0.total_size() }>()); + const DEVICE_STATUS: Aligned = Aligned([1u8, 0]); + + fn get_string( + &self, + handle: hal_usb::StringHandle, + _lang: u16, + ) -> Option> { + match handle { + USB_VENDOR_HANDLE => Some(VENDOR_ID), + USB_PRODUCT_HANDLE => Some(self.product_desc_bytes), + USB_SERIAL_HANDLE => Some(self.serial_desc_bytes), + DFU_FIRMWARE_HANDLE => Some(DFU_FIRMWARE), + DFU_UDS_CERT_HANDLE => Some(DFU_UDS_CERT), + DFU_CDI0_CERT_HANDLE => Some(DFU_CDI0_CERT), + DFU_CDI1_CERT_HANDLE => Some(DFU_CDI1_CERT), + _ => None, + } + } +} + +fn get_certificate(flash: &mut FlashIpcClient, n: u8, data: &mut [u8]) -> Result { + pw_log::info!("Reading certificate {}", n as usize); + let (partition, mut n) = match n { + 0 => (0, 0), // The UDS (dice) cert is located in bank=0, page=9. + 1 => (1, 0), // The CDI (dice) certs are located in bank=1, page=9. + 2 => (1, 1), + _ => return Err(DfuStatus::ErrFile), + }; + let mut offset = 0usize; + let mut buf = [0u8; 1024]; + loop { + let sz = core::cmp::min(2048 - offset, buf.len()); + flash.read(FlashAddress::info(partition, 9, offset as u32), &mut buf[..sz]).map_err(|_| DfuStatus::ErrUnknown)?; + match PersoCertificate::from_bytes(&buf) { + Ok((cert, _)) => { + if n == 0 { + let len = cert.certificate.len(); + pw_log::info!("Found cert: {} bytes", len as usize); + data[..len].copy_from_slice(cert.certificate); + return Ok(len); + } + offset += (cert.obj_size + 7) & !7; + n -= 1; + } + Err(_) => break, + } + } + Err(DfuStatus::ErrUnknown) +} + + +struct MyDfuHandler { + flash: FlashIpcClient, +} + +impl DfuHandler for MyDfuHandler { + fn dnload(&mut self, alt: u8, block_num: u16, data: &[u8]) -> DfuResult { + pw_log::info!( + "DNLOAD: alt={}, block={}, len={}", + alt, + block_num, + data.len() + ); + DfuResult::Ok + } + + fn upload(&mut self, alt: u8, block_num: u16, data: &mut [u8]) -> Result { + pw_log::info!( + "UPLOAD: alt={}, block={}, len={}", + alt, + block_num, + data.len() + ); + match alt { + 1|2|3 => get_certificate(&mut self.flash, alt-1, data), + _ => Err(DfuStatus::ErrFile), + } + } + + fn manifest(&mut self) -> DfuResult { + pw_log::info!("MANIFEST"); + DfuResult::Ok + } + + fn abort(&mut self) { + pw_log::info!("ABORT"); + } +} + +fn handle_usb() -> Result<(), ErrorCode> { + let mut serial_num_buffer = Aligned::([0_u8; 130]); + let descriptors = MyDescriptors { + serial_desc_bytes: hal_usb::hex_utf16_descriptor_aligned( + &mut serial_num_buffer, + b"DFU-12345", + ) + .unwrap(), + product_desc_bytes: PRODUCT_ID_DEFAULT, + }; + + const USB_CONFIG: UsbConfig = UsbConfig::new(&[], &[]); + + let mut usb = usb_driver::Usb::new(unsafe { usbdev::Usbdev::new() }, USB_CONFIG); + let mut ep0 = usb_stack::SimpleEp0::new(); + let mut dfu = DfuClass::<_, 2048>::new(DFU_BUILDER, MyDfuHandler { + flash: FlashIpcClient::new(IpcChannel::new(handle::FLASH_SERVICE))?, + }); + + loop { + let wait_return = syscall::object_wait( + handle::USBDEV_INTERRUPTS, + signals::USBDEV_PKT_RECEIVED + | signals::USBDEV_PKT_SENT + | signals::USBDEV_DISCONNECTED + | signals::USBDEV_HOST_LOST + | signals::USBDEV_LINK_RESET + | signals::USBDEV_LINK_SUSPEND + | signals::USBDEV_LINK_RESUME + | signals::USBDEV_AV_OUT_EMPTY + | signals::USBDEV_RX_FULL + | signals::USBDEV_AV_OVERFLOW + | signals::USBDEV_RX_CRC_ERR + | signals::USBDEV_RX_PID_ERR + | signals::USBDEV_RX_BITSTUFF_ERR + | signals::USBDEV_FRAME + | signals::USBDEV_AV_SETUP_EMPTY, + Instant::MAX, + ).map_err(ErrorCode::kernel_error)?; + + if wait_return.user_data != 0 { + pw_log::error!("Incorrect WaitReturn values"); + return Err(error::KERNEL_ERROR_UNKNOWN); + } + + while let Some(event) = usb.poll() { + let mut action = match dfu.handle_event(event) { + Ok(a) => a, + Err(e) => ep0.handle_event(e, &descriptors).unwrap_or(UsbAction::None), + }; + action.run(&mut usb); + } + + // Initiate any pending transmissions (e.g. UPLOAD blocks) + dfu.poll(&mut usb); + } +} + +fn usb_setup_pinmux() { + use top_earlgrey::{PinmuxInsel, PinmuxPeripheralIn}; + let mut pinmux = unsafe { pinmux::PinmuxAon::new() }; + + pinmux + .regs_mut() + .mio_periph_insel() + .at(PinmuxPeripheralIn::UsbdevSense as usize) + .modify(|_| (PinmuxInsel::ConstantOne as u32).into()); +} + +#[entry] +fn entry() -> ! { + pw_log::info!("🔄 RUNNING"); + usb_setup_pinmux(); + let ret = handle_usb(); + + // Log that an error occurred so that the app that caused the shutdown is logged. + let ret = match ret { + Ok(()) => { + pw_log::info!("✅ PASSED"); + Ok(()) + } + Err(e) => { + pw_log::error!("❌ FAILED: {:08x}", u32::from(e) as u32); + Err(Error::Unknown) + } + }; + + // Since this is written as a test, shut down with the return status from `main()`. + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tests/usbserial/BUILD.bazel b/target/earlgrey/tests/usbserial/BUILD.bazel new file mode 100644 index 00000000..e0abea33 --- /dev/null +++ b/target/earlgrey/tests/usbserial/BUILD.bazel @@ -0,0 +1,134 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@pigweed//pw_kernel/tooling:rust_app.bzl", "rust_app") +load("@pigweed//pw_kernel/tooling:system_image.bzl", "system_image") +load("@pigweed//pw_kernel/tooling:target_codegen.bzl", "target_codegen") +load("@pigweed//pw_kernel/tooling:target_linker_script.bzl", "target_linker_script") +load("@rules_rust//rust:defs.bzl", "rust_binary") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target/earlgrey/signing/keys:defs.bzl", "FPGA_ECDSA_KEY", "SILICON_ECDSA_KEY") +load("//target/earlgrey/tooling:opentitan_runner.bzl", "opentitan_test") + +rust_app( + name = "test_usb_serial", + srcs = [ + "test_usb.rs", + ], + codegen_crate_name = "test_usb_serial_codegen", + edition = "2024", + system_config = "@pigweed//pw_kernel/target:system_config_file", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + "//hal/blocking/usb:hal_usb", + "//protocol/usb/cdc_acm", + "//protocol/usb/stack", + "//target/earlgrey/drivers:usb_driver", + "//target/earlgrey/registers:pinmux", + "//target/earlgrey/registers:top_earlgrey", + "//target/earlgrey/registers:usbdev", + "//util/console", + "//util/ringbuffer", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:aligned", + #"@rust_crates//:ufmt", + #"@rust_crates//:zerocopy", + ], +) + +system_image( + name = "usb", + apps = [ + ":test_usb_serial", + ], + kernel = ":target", + platform = "//target/earlgrey", + system_config = ":system_config", + tags = ["kernel"], +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + template = "//target/earlgrey:linker_script_template", +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/riscv:arch_riscv", + system_config = ":system_config", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/earlgrey:entry", + "@pigweed//pw_kernel/arch/riscv:arch_riscv", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/subsys/console:console_backend", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +) + +opentitan_test( + name = "usb_verilator_test", + timeout = "eternal", + interface = "verilator", + tags = [ + "nightly_test", + "verilator", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_hyper310_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper310", + tags = [ + "hardware", + "hyper310", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_hyper340_test", + ecdsa_key = FPGA_ECDSA_KEY, + interface = "hyper340", + tags = [ + "hardware", + "hyper340", + ], + target = ":usb", +) + +opentitan_test( + name = "usb_silicon_test", + ecdsa_key = SILICON_ECDSA_KEY, + interface = "teacup", + tags = [ + "earlgrey_silicon", + "hardware", + ], + target = ":usb", +) diff --git a/target/earlgrey/tests/usbserial/system.json5 b/target/earlgrey/tests/usbserial/system.json5 new file mode 100644 index 00000000..b6b8bcd2 --- /dev/null +++ b/target/earlgrey/tests/usbserial/system.json5 @@ -0,0 +1,77 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +{ + arch: { + type: "riscv", + }, + kernel: { + flash_start_address: 0xA0010000, + flash_size_bytes: 65536, + ram_start_address: 0x10000000, + ram_size_bytes: 32768, + interrupt_table: { + table: {} + }, + }, + apps: [ + { + name: "test_usb_serial", + flash_size_bytes: 16384, + processes: [{ + name: "test_usb_serial_process", + ram_size_bytes: 4096, + objects: [ + { + name: "usbdev_interrupts", + type: "interrupt", + irqs: [ + { name: "usbdev_pkt_received", number: 135 }, + { name: "usbdev_pkt_sent", number: 136 }, + { name: "usbdev_disconnected", number: 137 }, + { name: "usbdev_host_lost", number: 138 }, + + { name: "usbdev_link_reset", number: 139 }, + { name: "usbdev_link_suspend", number: 140 }, + { name: "usbdev_link_resume", number: 141 }, + { name: "usbdev_av_out_empty", number: 142 }, + + { name: "usbdev_rx_full", number: 143 }, + { name: "usbdev_av_overflow", number: 144 }, + //{ name: "usbdev_link_in_err", number: 145 }, + { name: "usbdev_rx_crc_err", number: 146 }, + + { name: "usbdev_rx_pid_err", number: 147 }, + { name: "usbdev_rx_bitstuff_err", number: 148 }, + { name: "usbdev_frame", number: 149 }, + //{ name: "usbdev_powered", number: 150 }, + + //{ name: "usbdev_link_out_err", number: 151 }, + { name: "usbdev_av_setup_empty", number: 152 }, + ], + }, + ], + memory_mappings: [ + { + name: "usbdev", + type: "device", + start_address: 0x40320000, + size_bytes: 0x1000, + }, + { + name: "pinmux", + type: "device", + start_address: 0x40460000, + size_bytes: 0x1000, + } + + ], + threads: [ + { + name: "usb_thread", + kernel_stack_size_bytes: 2048, + }, + ], + }], + }, + ], +} diff --git a/target/earlgrey/tests/usbserial/target.rs b/target/earlgrey/tests/usbserial/target.rs new file mode 100644 index 00000000..2e253d37 --- /dev/null +++ b/target/earlgrey/tests/usbserial/target.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +use target_common::{declare_target, TargetInterface}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "Earlgrey Userspace UART"; + + fn main() -> ! { + codegen::start(); + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + match code { + 0 => pw_log::info!("PASS"), + _ => pw_log::info!("FAIL: {}", code as u32), + }; + loop {} + } +} + +declare_target!(Target); diff --git a/target/earlgrey/tests/usbserial/test_usb.rs b/target/earlgrey/tests/usbserial/test_usb.rs new file mode 100644 index 00000000..1b0f0b8d --- /dev/null +++ b/target/earlgrey/tests/usbserial/test_usb.rs @@ -0,0 +1,191 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +#![no_main] +#![allow(dead_code)] + +use test_usb_serial_codegen::{handle, signals}; +use userspace::time::Instant; +use userspace::{entry, syscall}; +use pw_status::Error; + +use aligned::{Aligned, A4}; +use hal_usb::{ + ConfigDescriptor, DeviceDescriptor, StringDescriptorRef, +}; + +use hal_usb::driver::UsbDriver; +use usb_driver::UsbConfig; +use usb_stack::{DescriptorSource, UsbAction, UsbClass}; + +use protocol_usb_cdc_acm::{CdcAcm, CdcAcmBuilder}; + +const USB_VENDOR_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(1); +const USB_PRODUCT_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(2); +const USB_SERIAL_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(3); +const USB_CDC_COMM_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(4); +const USB_CDC_DATA_HANDLE: hal_usb::StringHandle = hal_usb::StringHandle(5); + +const CDC_BUILDER: CdcAcmBuilder = CdcAcmBuilder::new( + 0, // comm_if: Communication Interface index + 1, // data_if: Data Interface index + 1, // comm_ep: Communication IN endpoint (Interrupt) + 2, // data_out_ep: Data OUT endpoint (Bulk) + 3, // data_in_ep: Data IN endpoint (Bulk) +); + +const DEVICE_DESC: DeviceDescriptor = DeviceDescriptor { + device_class: hal_usb::DeviceClass::SPECIFIED_BY_INTERFACE, + device_sub_class: 0x00, + device_protocol: 0x00, + max_packet_size: 64, + vendor_id: 0x18d1, // Google, Inc. + product_id: 0x503a, // STWG USB Fullspeed IP. + device_release_num: 0x0100, + manufacturer: USB_VENDOR_HANDLE, + product: USB_PRODUCT_HANDLE, + serial_num: USB_SERIAL_HANDLE, +}; + +const CONFIG_DESC: ConfigDescriptor = ConfigDescriptor { + configuration_value: 1, + max_power: 250, + self_powered: false, + remote_wakeup: false, + interfaces: &[ + CDC_BUILDER.comm_interface( + USB_CDC_COMM_HANDLE, + &CDC_BUILDER.comm_func_descs(), + &CDC_BUILDER.comm_endpoints(), + ), + CDC_BUILDER.data_interface(USB_CDC_DATA_HANDLE, &CDC_BUILDER.data_endpoints()), + ], +}; + +const STRING_DESC_0: hal_usb::StringDescriptor0 = hal_usb::StringDescriptor0 { + langs: &[ + // English - United States + 0x0409, + ], +}; + +const VENDOR_ID: hal_usb::StringDescriptorRef = hal_usb::string_descriptor!("Google Inc.").as_ref(); +const PRODUCT_ID_DEFAULT: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("Earlgrey").as_ref(); +const USB_COMM: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("CDC Comm Interface").as_ref(); +const USB_DATA: hal_usb::StringDescriptorRef = + hal_usb::string_descriptor!("CDC Data Interface").as_ref(); + +struct MyDescriptors<'a> { + serial_desc_bytes: StringDescriptorRef<'a>, + product_desc_bytes: StringDescriptorRef<'a>, +} + +impl DescriptorSource for MyDescriptors<'_> { + const DEVICE_DESC_BYTES: &'static Aligned = &Aligned(DEVICE_DESC.serialize()); + const CONFIG_DESC_BYTES: &'static Aligned = + &Aligned(CONFIG_DESC.serialize::<{ CONFIG_DESC.total_size() }>()); + const STRING_DESC_0_BYTES: &'static Aligned = + &Aligned(STRING_DESC_0.serialize::<{ STRING_DESC_0.total_size() }>()); + const DEVICE_STATUS: Aligned = Aligned([1u8, 0]); + + fn get_string( + &self, + handle: hal_usb::StringHandle, + _lang: u16, + ) -> Option> { + match handle { + USB_VENDOR_HANDLE => Some(VENDOR_ID), + USB_PRODUCT_HANDLE => Some(self.product_desc_bytes), + USB_SERIAL_HANDLE => Some(self.serial_desc_bytes), + USB_CDC_COMM_HANDLE => Some(USB_COMM), + USB_CDC_DATA_HANDLE => Some(USB_DATA), + _ => None, + } + } +} + +fn handle_usb() -> Result<(), Error> { + let mut serial_num_buffer = Aligned::([0_u8; 130]); + let descriptors = MyDescriptors { + serial_desc_bytes: hal_usb::hex_utf16_descriptor_aligned(&mut serial_num_buffer, b"12345") + .unwrap(), + product_desc_bytes: PRODUCT_ID_DEFAULT, + }; + + const USB_CONFIG: UsbConfig = UsbConfig::new(&CDC_BUILDER.eps().0, &CDC_BUILDER.eps().1); + + let mut usb = usb_driver::Usb::new(unsafe { usbdev::Usbdev::new() }, USB_CONFIG); + let mut ep0 = usb_stack::SimpleEp0::new(); + let mut cdc_acm = CdcAcm::<1024, 1024>::new(CDC_BUILDER); + + loop { + let wait_return = syscall::object_wait( + handle::USBDEV_INTERRUPTS, + signals::USBDEV_PKT_RECEIVED + | signals::USBDEV_PKT_SENT + | signals::USBDEV_DISCONNECTED + | signals::USBDEV_HOST_LOST + | signals::USBDEV_LINK_RESET + | signals::USBDEV_LINK_SUSPEND + | signals::USBDEV_LINK_RESUME + | signals::USBDEV_AV_OUT_EMPTY + | signals::USBDEV_RX_FULL + | signals::USBDEV_AV_OVERFLOW + | signals::USBDEV_RX_CRC_ERR + | signals::USBDEV_RX_PID_ERR + | signals::USBDEV_RX_BITSTUFF_ERR + | signals::USBDEV_FRAME + | signals::USBDEV_AV_SETUP_EMPTY, + Instant::MAX, + )?; + + if wait_return.user_data != 0 { + pw_log::error!("Incorrect WaitReturn values"); + return Err(Error::Unknown); + } + + while let Some(event) = usb.poll() { + let mut action = match cdc_acm.handle_event(event) { + Ok(a) => a, + Err(e) => ep0.handle_event(e, &descriptors).unwrap_or(UsbAction::None), + }; + action.run(&mut usb); + } + + // Loopback received data to send buffer + while let Some(byte) = cdc_acm.rx_queue.pop() { + let _ = cdc_acm.tx_queue.push(byte); + } + + // Initiate any pending transmissions + cdc_acm.poll_transmit(&mut usb); + } +} + +fn usb_setup_pinmux() { + use top_earlgrey::{PinmuxInsel, PinmuxPeripheralIn}; + let mut pinmux = unsafe { pinmux::PinmuxAon::new() }; + + pinmux + .regs_mut() + .mio_periph_insel() + .at(PinmuxPeripheralIn::UsbdevSense as usize) + .modify(|_| (PinmuxInsel::ConstantOne as u32).into()); +} + +#[entry] +fn entry() -> ! { + usb_setup_pinmux(); + let ret = handle_usb(); + let _ = syscall::debug_shutdown(ret); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + pw_log::error!("FAIL: panic in {}", module_path!() as &str); + loop {} +} diff --git a/target/earlgrey/tooling/opentitan_runner.bzl b/target/earlgrey/tooling/opentitan_runner.bzl index fcde8db6..4c9dac21 100644 --- a/target/earlgrey/tooling/opentitan_runner.bzl +++ b/target/earlgrey/tooling/opentitan_runner.bzl @@ -54,7 +54,7 @@ def _opentitan_runner_impl(ctx): output = run_script, is_executable = True, content = """#!/bin/bash -{runner} --interface {interface} {load_bitstream} {mechanism} --elf {elf} --bin {bin} {optional_args} +{runner} --interface {interface} {load_bitstream} {mechanism} --elf {elf} --bin {bin} {optional_args} "$@" """.format( runner = runner.short_path, interface = ctx.attr.interface, diff --git a/target/earlgrey/tooling/opentitan_runner.py b/target/earlgrey/tooling/opentitan_runner.py index cb40f8bc..f1eecdac 100755 --- a/target/earlgrey/tooling/opentitan_runner.py +++ b/target/earlgrey/tooling/opentitan_runner.py @@ -64,6 +64,7 @@ def _parse_args(): action=argparse.BooleanOptionalAction, help="load a bitstream into the FPGA board", ) + parser.add_argument( "--mechanism", type=str, @@ -81,6 +82,13 @@ def _parse_args(): type=pathlib.Path, help="bin file", ) + parser.add_argument( + "--timestamp", + type=bool, + default=True, + action=argparse.BooleanOptionalAction, + help="Display a timestamp per line of console output", + ) parser.add_argument( "--exit-success", type=str, @@ -172,7 +180,12 @@ def load_bitstream(interface: str): def load_and_run( - image: Path, interface: str, mechanism: str, exit_success: str, exit_failure: str + image: Path, + interface: str, + mechanism: str, + exit_success: str, + exit_failure: str, + timestamp: bool, ) -> list[str]: """Prepare opentitantool arguments to load an image into a board and spawn a console.""" if interface == "verilator": @@ -209,7 +222,9 @@ def load_and_run( else: raise Exception("unknown mechanism", mechanism) - console_command = ["console", "--timestamp"] + console_command = ["console"] + if timestamp: + console_command.append("--timestamp") if exit_success: console_command.append(f"--exit-success={exit_success}") if exit_failure: @@ -280,7 +295,12 @@ def _main(args) -> int: load_bitstream(args.interface) cmd = load_and_run( - args.bin, args.interface, args.mechanism, args.exit_success, args.exit_failure + args.bin, + args.interface, + args.mechanism, + args.exit_success, + args.exit_failure, + args.timestamp, ) # TODO(cfrantz): add support for the tokenized console. return_code = simple_console(cmd) diff --git a/target/earlgrey/util/BUILD.bazel b/target/earlgrey/util/BUILD.bazel new file mode 100644 index 00000000..091f9244 --- /dev/null +++ b/target/earlgrey/util/BUILD.bazel @@ -0,0 +1,29 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "util", + srcs = [ + "boot_log.rs", + "boot_svc.rs", + "error.rs", + "lib.rs", + "misc.rs", + "mubi.rs", + "perso_tlv.rs", + "ret_ram.rs", + "rom_error.rs", + "tags.rs", + ], + crate_name = "earlgrey_util", + edition = "2024", + deps = [ + "//util/error", + "//util/types", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:ufmt", + "@rust_crates//:zerocopy", + ], +) diff --git a/target/earlgrey/util/boot_log.rs b/target/earlgrey/util/boot_log.rs new file mode 100644 index 00000000..652ab63b --- /dev/null +++ b/target/earlgrey/util/boot_log.rs @@ -0,0 +1,70 @@ +use ufmt::derive::uDebug; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +use crate::misc::UnalignedU64; +use crate::tags::{BootSlot, HardenedBool, OwnershipState}; +use crate::CheckDigest; + +/// The BootLog provides information about how the ROM and ROM_EXT +/// booted the chip. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout, uDebug)] +#[repr(C)] +pub struct BootLog { + /// A SHA256 digest over all other fields in this struct. + pub digest: [u8; 32], + /// A tag that identifies this struct as the boot log ('BLOG'). + pub identifier: u32, + /// The chip version (a git hash prefix from the ROM). + pub chip_version: UnalignedU64, + /// The boot slot the ROM chose to boot the ROM_EXT. + pub rom_ext_slot: BootSlot, + /// The ROM_EXT major version number. + pub rom_ext_major: u32, + /// The ROM_EXT minor version number. + pub rom_ext_minor: u32, + /// The ROM_EXT size in bytes. + pub rom_ext_size: u32, + /// The ROM_EXT nonce (a value used to prevent replay of signed commands). + pub rom_ext_nonce: UnalignedU64, + /// The boot slot the ROM_EXT chose to boot the owner firmware. + pub bl0_slot: BootSlot, + /// The chip's ownership state. + pub ownership_state: OwnershipState, + /// The number of ownership transfers performed on this chip. + pub ownership_transfers: u32, + /// Minimum security version permitted for ROM_EXT payloads. + pub rom_ext_min_sec_ver: u32, + /// Minimum security version permitted for application payloads. + pub bl0_min_sec_ver: u32, + /// The primary BL0 boot slot. + pub primary_bl0_slot: BootSlot, + /// Whether the retention RAM was initialized on this boot. + pub retention_ram_initialized: HardenedBool, + /// Reserved for future use. + pub reserved: [u32; 8], +} + +impl CheckDigest for BootLog { + fn check_digest(&self, f: F) -> bool + where + F: Fn(&[u8]) -> [u8; 32], + { + let digest = f(&self.as_bytes()[32..]); + for (a, b) in self.digest.iter().zip(digest.iter().rev()) { + if *a != *b { + return false; + } + } + return true; + } + + fn set_digest(&mut self, f: F) + where + F: Fn(&[u8]) -> [u8; 32], + { + let digest = f(&self.as_bytes()[32..]); + for (a, b) in self.digest.iter_mut().zip(digest.iter().rev()) { + *a = *b; + } + } +} diff --git a/target/earlgrey/util/boot_svc.rs b/target/earlgrey/util/boot_svc.rs new file mode 100644 index 00000000..fc17ae64 --- /dev/null +++ b/target/earlgrey/util/boot_svc.rs @@ -0,0 +1,187 @@ +use core::mem::size_of; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unalign}; + +use crate::rom_error::RomError; +use crate::tags::{BootSlot, BootSvcKind, HardenedBool, OwnershipKeyAlg, UnlockMode}; +use crate::{CheckDigest, GetData}; + +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +/// The Boot Services header common to all boot services commands and responses. +pub struct BootSvc { + /// A SHA256 digest over the rest of the boot services message. + pub digest: [u8; 32], + /// A tag that identifies this struct as a boot services message ('BSVC'). + pub identifier: u32, + /// The type of boot services message that follows this header. + pub kind: BootSvcKind, + /// The length of the boot services message in bytes (including the header). + pub length: u32, + /// The message data. + pub data: [u8; 212], +} + +impl BootSvc { + const HEADER_LEN: usize = 44; + const TAG: u32 = u32::from_le_bytes(*b"BSVC"); +} + +impl CheckDigest for BootSvc { + fn check_digest(&self, f: F) -> bool + where + F: Fn(&[u8]) -> [u8; 32], + { + let digest = f(&self.as_bytes()[32..self.length as usize]); + for (a, b) in self.digest.iter().zip(digest.iter().rev()) { + if *a != *b { + return false; + } + } + return true; + } + + fn set_digest(&mut self, f: F) + where + F: Fn(&[u8]) -> [u8; 32], + { + let digest = f(&self.as_bytes()[32..self.length as usize]); + for (a, b) in self.digest.iter_mut().zip(digest.iter().rev()) { + *a = *b; + } + } +} + +macro_rules! impl_getdata { + ($t:ident, $tag:ident) => { + impl GetData<$t> for BootSvc { + fn get(&self) -> Option<&$t> { + if self.identifier != BootSvc::TAG + || self.length as usize != BootSvc::HEADER_LEN + size_of::<$t>() + || self.kind != BootSvcKind::$tag + { + return None; + } + let (result, _) = <$t>::ref_from_prefix(&self.data).unwrap(); + Some(result) + } + fn get_mut(&mut self) -> &mut $t { + self.identifier = BootSvc::TAG; + self.length = (BootSvc::HEADER_LEN + size_of::<$t>()) as u32; + self.kind = BootSvcKind::$tag; + let (result, _) = <$t>::mut_from_prefix(&mut self.data).unwrap(); + result + } + } + }; + ($t:ident) => { + impl_getdata!($t, $t); + }; +} + +/// An empty boot services message. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct Empty { + pub payload: [u8; 212], +} +impl_getdata!(Empty, EmptyRequest); + +/// Request to set the minimum owner stage firmware version. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct MinBl0SecVerRequest { + /// The desired minimum BL0 version. + pub ver: u32, +} +impl_getdata!(MinBl0SecVerRequest); + +/// Response to the minimum version request. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct MinBl0SecVerResponse { + /// The current minimum BL0 version. + pub ver: u32, + /// The status response to the request. + pub status: RomError, +} +impl_getdata!(MinBl0SecVerResponse); + +/// Request to set the next (one-time) owner stage boot slot. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct NextBl0SlotRequest { + /// The slot to boot. + pub next_bl0_slot: BootSlot, + /// The slot to configure as primary. + pub primary_bl0_slot: BootSlot, +} +impl_getdata!(NextBl0SlotRequest); + +/// Response to the set next boot slot request. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct NextBl0SlotResponse { + /// The status response to the request. + pub status: RomError, + /// The current primary slot. + pub primary_bl0_slot: BootSlot, +} +impl_getdata!(NextBl0SlotResponse); + +/// Request to unlock ownership of the chip. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct OwnershipUnlockRequest { + /// The desired unlock mode. + pub unlock_mode: UnlockMode, + /// The Device Identification Number of the chip. + pub din: Unalign, + /// Reserved for future use. + pub reserved: [u32; 7], + /// The algorithm of next owner's key (for unlock Endorsed mode). + pub next_owner_alg: OwnershipKeyAlg, + /// The ROM_EXT nonce. + pub nonce: Unalign, + /// The next owner's key (for unlock Endorsed mode). + pub next_owner_key: [u8; 96], + /// A signature over [unlock_mode..next_owner_key] with the current owner unlock key. + pub signature: [u8; 64], +} +impl_getdata!(OwnershipUnlockRequest); + +/// Response to the ownership unlock command. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct OwnershipUnlockResponse { + /// The status response to the request. + pub status: RomError, +} +impl_getdata!(OwnershipUnlockResponse); + +/// Request to activate ownership of the chip. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct OwnershipActivateRequest { + /// The new primary boot slot after activating ownership. + pub primary_bl0_slot: BootSlot, + /// The Device Identification Number of the chip. + pub din: Unalign, + /// Whether to erase the previous owner's data during activation. + pub erase_previous: HardenedBool, + /// Reserved for future use. + pub reserved: [u32; 31], + /// The ROM_EXT nonce. + pub nonce: Unalign, + /// A signature over [primary_bl0_slot..nonce] with the next owner's activate key. + pub signature: [u8; 64], +} +impl_getdata!(OwnershipActivateRequest); + +/// Response to the ownership activate command. +#[derive(Clone, FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct OwnershipActivateResponse { + /// The status response to the request. + pub status: RomError, +} +impl_getdata!(OwnershipActivateResponse); diff --git a/target/earlgrey/util/error.rs b/target/earlgrey/util/error.rs new file mode 100644 index 00000000..035f1372 --- /dev/null +++ b/target/earlgrey/util/error.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use pw_status::Error; +use util_error::{ErrorCode, ErrorModule}; + +// TODO: review the pw_status error codes. + +pub const EG_ERROR: ErrorModule = ErrorModule::new(0x464c); //ascii `FL`. +pub const EG_ERROR_CERT_NOT_FOUND: ErrorCode = EG_ERROR.from_pw(1, Error::NotFound); +pub const EG_ERROR_CERT_BAD_NAME: ErrorCode = EG_ERROR.from_pw(2, Error::InvalidArgument); +pub const EG_ERROR_BAD_BOOT_LOG: ErrorCode = EG_ERROR.from_pw(3, Error::Unknown); +pub const EG_ERROR_BOOT_SLOT_UNKNOWN: ErrorCode = EG_ERROR.from_pw(4, Error::Unknown); diff --git a/target/earlgrey/util/lib.rs b/target/earlgrey/util/lib.rs new file mode 100644 index 00000000..2e8399c9 --- /dev/null +++ b/target/earlgrey/util/lib.rs @@ -0,0 +1,31 @@ +#![no_std] + +pub mod boot_log; +pub mod boot_svc; +pub mod error; +mod misc; +mod mubi; +mod perso_tlv; +pub mod ret_ram; +pub mod rom_error; +pub mod tags; + +pub use mubi::AsMubi; +pub use perso_tlv::{PersoCertificate, PersoTlvType}; + +pub trait CheckDigest { + /// Check the digest on a data structure by calling `f` to generate a digest. + fn check_digest(&self, f: F) -> bool + where + F: Fn(&[u8]) -> [u8; 32]; + + /// Set the digest on a data structure by calling `f` to generate the digest. + fn set_digest(&mut self, f: F) + where + F: Fn(&[u8]) -> [u8; 32]; +} + +pub trait GetData { + fn get(&self) -> Option<&T>; + fn get_mut(&mut self) -> &mut T; +} diff --git a/target/earlgrey/util/misc.rs b/target/earlgrey/util/misc.rs new file mode 100644 index 00000000..b15733b1 --- /dev/null +++ b/target/earlgrey/util/misc.rs @@ -0,0 +1,32 @@ +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unalign}; + +#[derive(Clone, Copy, FromBytes, Immutable, IntoBytes, KnownLayout)] +pub struct UnalignedU64(Unalign); + +impl UnalignedU64 { + pub fn get(&self) -> u64 { + self.0.get() + } + pub fn set(&mut self, v: u64) { + self.0.set(v) + } +} + +impl ufmt::uDisplay for UnalignedU64 { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + let v = self.get(); + ufmt::uwrite!(f, "{:016x}", v) + } +} + +impl ufmt::uDebug for UnalignedU64 { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uDisplay::fmt(self, f) + } +} diff --git a/target/earlgrey/util/mubi.rs b/target/earlgrey/util/mubi.rs new file mode 100644 index 00000000..51fa77a5 --- /dev/null +++ b/target/earlgrey/util/mubi.rs @@ -0,0 +1,13 @@ +pub trait AsMubi { + fn as_mubi(&self) -> u32; +} + +impl AsMubi for bool { + fn as_mubi(&self) -> u32 { + if *self { + 6 + } else { + 9 + } + } +} diff --git a/target/earlgrey/util/perso_tlv.rs b/target/earlgrey/util/perso_tlv.rs new file mode 100644 index 00000000..8a31bf32 --- /dev/null +++ b/target/earlgrey/util/perso_tlv.rs @@ -0,0 +1,55 @@ +use crate::error::*; +use util_error::ErrorCode; + +#[derive(Clone, Copy, Default)] +#[repr(transparent)] +pub struct PersoTlvType(u8); + +#[allow(non_upper_case_globals)] +impl PersoTlvType { + pub const X509Tbs: PersoTlvType = PersoTlvType(0); + pub const X509Cert: PersoTlvType = PersoTlvType(1); + pub const DevSeed: PersoTlvType = PersoTlvType(2); + pub const CwtCert: PersoTlvType = PersoTlvType(3); + pub const WasTbsHmac: PersoTlvType = PersoTlvType(4); + pub const DeviceId: PersoTlvType = PersoTlvType(5); + pub const GenericSeed: PersoTlvType = PersoTlvType(6); + pub const PersoSha256Hash: PersoTlvType = PersoTlvType(7); +} + +pub struct PersoCertificate<'a> { + pub obj_type: PersoTlvType, + pub obj_size: usize, + pub name: &'a str, + pub certificate: &'a [u8], +} + +impl PersoCertificate<'_> { + pub fn from_bytes<'a>(data: &'a [u8]) -> Result<(PersoCertificate<'a>, &'a [u8]), ErrorCode> { + let type_size = u16::from_be_bytes(data[0..2].try_into().unwrap()); + let namelen_certsz = u16::from_be_bytes(data[2..4].try_into().unwrap()); + let rest = &data[4..]; + + if type_size == 0xFFFF || type_size == 0 { + return Err(EG_ERROR_CERT_NOT_FOUND); + } + // Really should check the object type and return an error if not a certificate. + let obj_type = PersoTlvType((type_size >> 12) as u8); + let obj_size = (type_size & 0x0FFF) as usize; + let namelen = (namelen_certsz >> 12) as usize; + let certsz = (namelen_certsz & 0x0FFF) as usize; + + let name = core::str::from_utf8(&rest[..namelen]).map_err(|_| EG_ERROR_CERT_BAD_NAME)?; + let certificate = &rest[namelen..namelen + certsz]; + let end = (obj_size + 7) & !7; + Ok(( + PersoCertificate { + obj_type, + obj_size, + name, + certificate, + }, + &rest[end..], + )) + } +} diff --git a/target/earlgrey/util/ret_ram.rs b/target/earlgrey/util/ret_ram.rs new file mode 100644 index 00000000..48787186 --- /dev/null +++ b/target/earlgrey/util/ret_ram.rs @@ -0,0 +1,26 @@ +use crate::boot_log::BootLog; +use crate::boot_svc::BootSvc; +use crate::rom_error::RomError; +use crate::tags::RetRamVersion; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(FromBytes, Immutable, IntoBytes, KnownLayout)] +#[repr(C)] +pub struct RetRam { + pub version: RetRamVersion, + pub reset_reasons: u32, + pub boot_svc: BootSvc, + pub reserved: [u8; 1652], + pub boot_log: BootLog, + pub last_shutdown_reason: RomError, + pub owner: [u8; 2048], +} + +impl RetRam { + pub unsafe fn mut_ref() -> &'static mut RetRam { + unsafe { + let rr = core::slice::from_raw_parts_mut(0x4060_0000 as *mut u8, 4096); + RetRam::mut_from_bytes(rr).unwrap() + } + } +} diff --git a/target/earlgrey/util/rom_error.rs b/target/earlgrey/util/rom_error.rs new file mode 100644 index 00000000..c8f5a0e6 --- /dev/null +++ b/target/earlgrey/util/rom_error.rs @@ -0,0 +1,152 @@ +#![allow(non_upper_case_globals)] + +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct RomError(pub u32); +impl RomError { + pub const Ok: Self = Self(1849); + pub const WriteBootdataThenReboot: Self = Self(746); + pub const NoData: Self = Self(1239); + pub const Unknown: Self = Self(4294967295); + pub const SigverifyBadRsaSignature: Self = Self(22238723); + pub const SigverifyBadSpxSignature: Self = Self(39015939); + pub const SigverifyBadKey: Self = Self(55793155); + pub const SigverifyBadRsaKey: Self = Self(72570371); + pub const SigverifyBadSpxKey: Self = Self(89347587); + pub const SigverifyLargeRsaSignature: Self = Self(106124803); + pub const SigverifyBadEcdsaSignature: Self = Self(122902019); + pub const SigverifyBadAuthPartition: Self = Self(139679235); + pub const SigverifyBadEcdsaKey: Self = Self(156456451); + pub const SigverifyBadSpxConfig: Self = Self(173233667); + pub const SigverifyEcdsaNotFound: Self = Self(190010885); + pub const SigverifySpxNotFound: Self = Self(206788101); + pub const KeymgrInternal: Self = Self(21712141); + pub const ManifestBadEntryPoint: Self = Self(21840141); + pub const ManifestBadCodeRegion: Self = Self(38617357); + pub const ManifestBadSignedRegion: Self = Self(55394573); + pub const ManifestBadExtension: Self = Self(72171789); + pub const ManifestBadVersionMajor: Self = Self(88949005); + pub const AlertBadIndex: Self = Self(21055491); + pub const AlertBadClass: Self = Self(37832707); + pub const AlertBadEnable: Self = Self(54609923); + pub const AlertBadEscalation: Self = Self(71387139); + pub const AlertBadCrc32: Self = Self(88164355); + pub const RomBootFailed: Self = Self(21844489); + pub const RomResetReasonFault: Self = Self(38621698); + pub const RomImmSection: Self = Self(55398915); + pub const Interrupt: Self = Self(4805122); + pub const EpmpBadCheck: Self = Self(21319693); + pub const KmacInvalidStatus: Self = Self(21709581); + pub const KmacInvalidKeySize: Self = Self(38486787); + pub const OtbnInvalidArgument: Self = Self(21122563); + pub const OtbnBadOffsetLen: Self = Self(37899779); + pub const OtbnExecutionFailed: Self = Self(54677005); + pub const OtbnSecWipeImemFailed: Self = Self(71454221); + pub const OtbnSecWipeDmemFailed: Self = Self(88231437); + pub const OtbnBadInsnCount: Self = Self(105008653); + pub const OtbnUnavailable: Self = Self(121785869); + pub const FlashCtrlDataRead: Self = Self(21381901); + pub const FlashCtrlInfoRead: Self = Self(38159117); + pub const FlashCtrlDataWrite: Self = Self(54936333); + pub const FlashCtrlInfoWrite: Self = Self(71713549); + pub const FlashCtrlDataErase: Self = Self(88490765); + pub const FlashCtrlInfoErase: Self = Self(105267981); + pub const FlashCtrlDataEraseVerify: Self = Self(122045197); + pub const BootPolicyBadIdentifier: Self = Self(21123085); + pub const BootPolicyBadLength: Self = Self(37900301); + pub const BootPolicyRollback: Self = Self(54677517); + pub const BootstrapEraseAddress: Self = Self(21123843); + pub const BootstrapProgramAddress: Self = Self(37901059); + pub const BootstrapInvalidState: Self = Self(54678275); + pub const BootstrapNotRequested: Self = Self(71455501); + pub const BootstrapDisabledRomExt: Self = Self(88232717); + pub const LogBadFormatSpecifier: Self = Self(21776141); + pub const BootDataNotFound: Self = Self(21120013); + pub const BootDataWriteCheck: Self = Self(37897229); + pub const BootDataInvalid: Self = Self(54674445); + pub const SpiDevicePayloadOverflow: Self = Self(22237197); + pub const AstInitNotDone: Self = Self(21058317); + pub const RstmgrBadInit: Self = Self(22172429); + pub const RndBadCrc32: Self = Self(22171139); + pub const BootSvcBadHeader: Self = Self(21119757); + pub const BootSvcBadSlot: Self = Self(37896963); + pub const BootSvcBadSecVer: Self = Self(54674179); + pub const RomExtBootFailed: Self = Self(22168841); + pub const XModemTimeoutStart: Self = Self(22564100); + pub const XModemTimeoutPacket: Self = Self(39341316); + pub const XModemTimeoutData: Self = Self(56118532); + pub const XModemTimeoutCrc: Self = Self(72895748); + pub const XModemTimeoutAck: Self = Self(89672964); + pub const XModemCrc: Self = Self(106450191); + pub const XModemEndOfFile: Self = Self(123227403); + pub const XModemCancel: Self = Self(140004609); + pub const XModemUnknown: Self = Self(156781826); + pub const XModemProtocol: Self = Self(173559043); + pub const XModemTooManyErrors: Self = Self(190336265); + pub const XModemBadLength: Self = Self(207113475); + pub const RomExtInterrupt: Self = Self(5392642); + pub const BootLogInvalid: Self = Self(21122061); + pub const Asn1Internal: Self = Self(21049613); + pub const Asn1StartInvalidArgument: Self = Self(37826819); + pub const Asn1PushBytesInvalidArgument: Self = Self(54604035); + pub const Asn1PushIntegerPadInvalidArgument: Self = Self(71381251); + pub const Asn1PushIntegerInvalidArgument: Self = Self(88158467); + pub const Asn1FinishBitstringInvalidArgument: Self = Self(104935683); + pub const Asn1BufferExhausted: Self = Self(121712904); + pub const RetRamBadVersion: Self = Self(22172162); + pub const RescueReboot: Self = Self(5395213); + pub const RescueBadMode: Self = Self(22172419); + pub const RescueImageTooBig: Self = Self(38949641); + pub const RescueSendStart: Self = Self(72504077); + pub const RescueInactivity: Self = Self(89281284); + pub const CertInternal: Self = Self(4408589); + pub const CertInvalidArgument: Self = Self(21185795); + pub const CertInvalidSize: Self = Self(37963023); + pub const OwnershipInvalidNonce: Self = Self(5199619); + pub const OwnershipInvalidMode: Self = Self(21976835); + pub const OwnershipInvalidSignature: Self = Self(38754051); + pub const OwnershipInvalidState: Self = Self(55531267); + pub const OwnershipInvalidRequest: Self = Self(72308483); + pub const OwnershipInvalidTag: Self = Self(89085699); + pub const OwnershipInvalidTagLength: Self = Self(105862915); + pub const OwnershipDuplicateItem: Self = Self(122640134); + pub const OwnershipFlashConfigLength: Self = Self(139417355); + pub const OwnershipInvalidInfoPage: Self = Self(156194563); + pub const OwnershipBadInfoPage: Self = Self(172971789); + pub const OwnershipNoOwner: Self = Self(189749005); + pub const OwnershipKeyNotFound: Self = Self(206526213); + pub const OwnershipInvalidDin: Self = Self(223303427); + pub const OwnershipUnlockDenied: Self = Self(240080647); + pub const OwnershipFlashConfigRomExt: Self = Self(256857859); + pub const OwnershipFlashConfigBounds: Self = Self(273635075); + pub const OwnershipInvalidAlgorithm: Self = Self(290412291); + pub const OwnershipFlashConfigSlots: Self = Self(307189507); + pub const OwnershipInvalidRescueBounds: Self = Self(323966723); + pub const OwnershipSignatureNotFound: Self = Self(340743941); + pub const OwnershipISFBNotPresent: Self = Self(1615812357); + pub const OwnershipISFBProductExpCnt: Self = Self(1632589579); + pub const OwnershipISFBStrikeMask: Self = Self(1649366791); + pub const OwnershipISFBProductExp: Self = Self(1666144007); + pub const OwnershipISFBFailed: Self = Self(1682921229); + pub const OwnershipISFBPage: Self = Self(1699698435); + pub const OwnershipISFBSize: Self = Self(1716475651); + pub const OwnershipOWNRVersion: Self = Self(1884247811); + pub const OwnershipAPPKVersion: Self = Self(1901025027); + pub const OwnershipFLSHVersion: Self = Self(1917802243); + pub const OwnershipINFOVersion: Self = Self(1934579459); + pub const OwnershipRESQVersion: Self = Self(1951356675); + pub const OwnershipISFBVersion: Self = Self(1968133891); + pub const PersoTlvInternal: Self = Self(5264397); + pub const PersoTlvCertObjNotFound: Self = Self(22041605); + pub const PersoTlvCertNameTooLong: Self = Self(38818827); + pub const PersoTlvOutputBufTooSmall: Self = Self(55596043); + pub const DiceInternal: Self = Self(4473613); + pub const DiceCwtCoseKeyNotFound: Self = Self(21250821); + pub const DiceCwtCoseKeyBadSize: Self = Self(21250829); + pub const DiceCwtKeyCoordsNotFound: Self = Self(38028037); + pub const DicePageCorrupted: Self = Self(54805251); + pub const UsbBadSetup: Self = Self(5591811); + pub const UsbBadEndpointNumber: Self = Self(22369027); +} diff --git a/target/earlgrey/util/tags.rs b/target/earlgrey/util/tags.rs new file mode 100644 index 00000000..a9325637 --- /dev/null +++ b/target/earlgrey/util/tags.rs @@ -0,0 +1,130 @@ +#![allow(non_upper_case_globals)] + +use ufmt::{uDebug, uDisplay}; +use util_types::impl_fourcc; +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct ManifestIdentifier(pub u32); +impl ManifestIdentifier { + pub const ROM_EXT: Self = Self(u32::from_le_bytes(*b"OTRE")); + pub const APPLICATION: Self = Self(u32::from_le_bytes(*b"OTB0")); +} + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct HardenedBool(pub u32); +impl HardenedBool { + pub const True: Self = Self(0x739); + pub const False: Self = Self(0x1d4); +} + +impl uDisplay for HardenedBool { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + match *self { + HardenedBool::True => ufmt::uwrite!(f, "True"), + HardenedBool::False => ufmt::uwrite!(f, "False"), + unk => ufmt::uwrite!(f, "HardenedBool({:08x})", unk.0), + } + } +} + +impl uDebug for HardenedBool { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uDisplay::fmt(self, f) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct OwnershipState(pub u32); +impl OwnershipState { + pub const Recovery: Self = Self(0); + pub const LockedOwner: Self = Self(0x444e574f); + pub const UnlockedSelf: Self = Self(0x464c5355); + pub const UnlockedAny: Self = Self(0x594e4155); + pub const UnlockedEndorsed: Self = Self(0x444e4555); +} +impl_fourcc!(OwnershipState); + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct BootSlot(pub u32); +impl BootSlot { + pub const SlotA: Self = Self(u32::from_le_bytes(*b"AA__")); + pub const SlotB: Self = Self(u32::from_le_bytes(*b"__BB")); + pub const Unspecified: Self = Self(u32::from_le_bytes(*b"UUUU")); +} +impl_fourcc!(BootSlot); + +impl BootSlot { + pub fn opposite(self) -> Option { + match self { + BootSlot::SlotA => Some(BootSlot::SlotB), + BootSlot::SlotB => Some(BootSlot::SlotA), + _ => None, + } + } +} + +/// The unlock mode for the OwnershipUnlock command. +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct UnlockMode(pub u32); +impl UnlockMode { + /// Unlock the chip to accept any next owner. + pub const Any: Self = Self(0x00594e41); + /// Unlock the chip to accept only the endorsed next owner. + pub const Endorsed: Self = Self(0x4f444e45); + /// Unlock the chip to update the current owner configuration. + pub const Update: Self = Self(0x00445055); + /// Abort the unlock operation. + pub const Abort: Self = Self(0x54524241); +} +impl_fourcc!(UnlockMode); + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct BootSvcKind(pub u32); +impl BootSvcKind { + pub const EmptyRequest: Self = Self(u32::from_le_bytes(*b"EMPT")); + pub const EmptyResponse: Self = Self(u32::from_le_bytes(*b"TPME")); + pub const MinBl0SecVerRequest: Self = Self(u32::from_le_bytes(*b"MSEC")); + pub const MinBl0SecVerResponse: Self = Self(u32::from_le_bytes(*b"CESM")); + pub const NextBl0SlotRequest: Self = Self(u32::from_le_bytes(*b"NEXT")); + pub const NextBl0SlotResponse: Self = Self(u32::from_le_bytes(*b"TXEN")); + pub const OwnershipUnlockRequest: Self = Self(u32::from_le_bytes(*b"UNLK")); + pub const OwnershipUnlockResponse: Self = Self(u32::from_le_bytes(*b"KLNU")); + pub const OwnershipActivateRequest: Self = Self(u32::from_le_bytes(*b"ACTV")); + pub const OwnershipActivateResponse: Self = Self(u32::from_le_bytes(*b"VTCA")); +} +impl_fourcc!(BootSvcKind); + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct OwnershipKeyAlg(pub u32); +impl OwnershipKeyAlg { + pub const Rsa: Self = Self(u32::from_le_bytes(*b"RSA3")); + pub const EcdsaP256: Self = Self(u32::from_le_bytes(*b"P256")); + pub const SpxPure: Self = Self(u32::from_le_bytes(*b"S+Pu")); + pub const SpxPrehash: Self = Self(u32::from_le_bytes(*b"S+S2")); + pub const HybridSpxPure: Self = Self(u32::from_le_bytes(*b"H+Pu")); + pub const HybridSpxPrehash: Self = Self(u32::from_le_bytes(*b"H+S2")); +} +impl_fourcc!(OwnershipKeyAlg); + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, KnownLayout, Immutable)] +#[repr(C)] +pub struct RetRamVersion(pub u32); +impl RetRamVersion { + pub const Version3: Self = Self(u32::from_le_bytes(*b"RR03")); + pub const Version4: Self = Self(u32::from_le_bytes(*b"RR04")); +} +impl_fourcc!(RetRamVersion); diff --git a/third_party/crates_io/Cargo.lock b/third_party/crates_io/Cargo.lock index 609665a8..90b7ab7b 100644 --- a/third_party/crates_io/Cargo.lock +++ b/third_party/crates_io/Cargo.lock @@ -1187,6 +1187,7 @@ dependencies = [ "tock-registers", "tokio", "tokio-util", + "ufmt", "zerocopy", "zeroize", ] @@ -1533,6 +1534,33 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "ufmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a64846ec02b57e9108d6469d98d1648782ad6bb150a95a9baac26900bbeab9d" +dependencies = [ + "ufmt-macros", + "ufmt-write", +] + +[[package]] +name = "ufmt-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d337d3be617449165cb4633c8dece429afd83f84051024079f97ad32a9663716" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ufmt-write" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" + [[package]] name = "unicode-ident" version = "1.0.24" diff --git a/third_party/crates_io/Cargo.toml b/third_party/crates_io/Cargo.toml index 4984d1a2..ef76dc05 100644 --- a/third_party/crates_io/Cargo.toml +++ b/third_party/crates_io/Cargo.toml @@ -34,6 +34,7 @@ smlang = { version = "0.8.0", default-features = false } syn = { version = "2.0.104", features = ["full", "extra-traits"] } syn1 = { package = "syn", version = "1.0.109", features = ["full", "extra-traits"] } tock-registers = "0.9.0" +ufmt = "0.2.0" zerocopy = { version = "0.8.48", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false, features = ["derive"] } diff --git a/util/console/BUILD.bazel b/util/console/BUILD.bazel new file mode 100644 index 00000000..ef6d4b89 --- /dev/null +++ b/util/console/BUILD.bazel @@ -0,0 +1,65 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_library", "rust_static_library", "rust_test") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "console", + srcs = [ + "hexdump.rs", + "lib.rs", + ], + crate_name = "util_console", + edition = "2024", + deps = [ + ":dbg_print", + "@rust_crates//:ufmt", + "@rust_crates//:zerocopy", + ] + select({ + "@platforms//os:none": [ + ":pigweed", + ], + "//conditions:default": [":stdout"], + }), +) + +rust_doc( + name = "console_doc", + crate = ":console", +) + +rust_static_library( + name = "stdout", + srcs = [ + "stdout.rs", + ], + crate_name = "console_stdout", + edition = "2024", +) + +rust_static_library( + name = "pigweed", + srcs = [ + "pigweed.rs", + ], + crate_name = "console_pigweed", + edition = "2024", + rustc_flags = [ + "-C", + "panic=abort", + ], +) + +cc_library( + name = "dbg_print", + srcs = ["dbg_print.c"], + hdrs = ["dbg_print.h"], + alwayslink = 1, +) + +rust_test( + name = "console_test", + crate = ":console", +) diff --git a/util/console/dbg_print.c b/util/console/dbg_print.c new file mode 100644 index 00000000..c492577b --- /dev/null +++ b/util/console/dbg_print.c @@ -0,0 +1,131 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#include "util/console/dbg_print.h" + +#include +#include +#include +#include + +extern void system_lowlevel_console_write(const char *buf, size_t len); + +static const char kHexTable[17] = "0123456789abcdef"; + +static size_t print_integer(char *dest, unsigned value, bool is_signed) { + char buf[12]; + char *b = buf + sizeof(buf); + size_t len = 0; + if (is_signed && (int)value < 0) { + *dest++ = ('-'); + len++; + value = (unsigned)(-(int)value); + } + *--b = '\0'; + do { + *--b = '0' + value % 10; + value /= 10; + } while (value); + while (*b) { + *dest++ = (*b++); + len++; + } + return len; +} + +void dbg_printf(const char *format, ...) { + char buffer[256]; + char *buf = buffer; + + va_list args; + va_start(args, format); + + for (; *format != '\0'; ++format) { + if (*format != '%') { + *buf++ = *format; + continue; + } + + ++format; // Skip over the '%'. + switch (*format) { + case '%': + *buf++ = *format; + break; + case 'c': { + int ch = va_arg(args, int); + *buf++ = (char)ch; + break; + } + case 'C': { + uint32_t val = va_arg(args, uint32_t); + for (size_t i = 0; i < sizeof(uint32_t); ++i, val >>= 8) { + uint8_t ch = (uint8_t)val; + if (ch >= 32 && ch < 127) { + *buf++ = (char)ch; + } else { + *buf++ = ('\\'); + *buf++ = ('x'); + *buf++ = (kHexTable[ch >> 4]); + *buf++ = (kHexTable[ch & 15]); + } + } + break; + } + case 's': { + // Print a null-terminated string. + const char *str = va_arg(args, const char *); + while (*str != '\0') { + *buf++ = (*str++); + } + break; + } + case 'd': + // `print_integer` will handle the sign bit of the value. + buf += print_integer(buf, va_arg(args, unsigned), true); + break; + case 'u': + buf += print_integer(buf, va_arg(args, unsigned), false); + break; + case 'p': + case 'x': { + // Print an `unsigned int` in hexadecimal. + unsigned int v = va_arg(args, unsigned int); + for (size_t i = 0; i < sizeof(v) * 2; ++i) { + int shift = sizeof(v) * 8 - 4; + *buf++ = (kHexTable[v >> shift]); + v <<= 4; + } + break; + } + default: + // For an invalid format specifier, back up one char and allow + // the output via the normal mechanism. + *buf++ = ('%'); + --format; + } + } + va_end(args); + system_lowlevel_console_write(buffer, buf - buffer); +} + +void dbg_hexdump(const void *data, size_t len) { + const uint8_t *p = (const uint8_t *)data; + size_t j = 0; + + while (j < len) { + // hexbuf is initialized as 48 spaces followed by a nul byte. + char hexbuf[] = " "; + // ascii is initialized as 17 nul bytes. + char ascii[17] = { + 0, + }; + dbg_printf("%p: ", p); + for (size_t i = 0; i < 16 && j < len; ++p, ++i, ++j) { + uint8_t val = *p; + hexbuf[i * 3 + 0] = kHexTable[val >> 4]; + hexbuf[i * 3 + 1] = kHexTable[val & 15]; + ascii[i] = (val >= 32 && val < 127) ? (char)val : '.'; + } + dbg_printf("%s %s\r\n", hexbuf, ascii); + } +} diff --git a/util/console/dbg_print.h b/util/console/dbg_print.h new file mode 100644 index 00000000..57c540bb --- /dev/null +++ b/util/console/dbg_print.h @@ -0,0 +1,53 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#ifndef OPENPROT_UTIL_CONSOLE_DBG_PRINT_H_ +#define OPENPROT_UTIL_CONSOLE_DBG_PRINT_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * An intentionally pared-down implementation of `printf()` that writes + * to UART0. + * + * This function only supports the format specifiers required by the + * ROM: + * - %c prints a single character. + * - %C prints a 'FourCC' style uint32_t (ASCII bytes in little-endian order). + * - %d prints a signed int in decimal. + * - %u prints an unsigned int in decimal. + * - %s prints a nul-terminated string. + * - %p prints pointer in hexadecimal. + * - %x prints an `unsigned int` in hexadecimal using lowercase characters. + * + * No modifiers are supported and the leading zeros in hexidecimal + * values are always printed. + * + * Note: unfortunately `uint32_t` is not necessarily an alias for + * `unsigned int`. An explicit cast is therefore necessary when printing + * `uint32_t` values using the `%x` format specifier in order to satisfy + * the `printf` format checker (`-Wformat`). + * + * @param format The format specifier. + * @param ... The values to interpolate in the format. + * @return The result of the operation. + */ +void dbg_printf(const char *format, ...) __attribute__((format(printf, 1, 2))); + +/** + * Hexdump a region of memory. + * + * @param data The memory to dump. + * @param len The length of the region to dump. + */ +void dbg_hexdump(const void *data, size_t len); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif // OPENPROT_UTIL_CONSOLE_DBG_PRINT_H_ diff --git a/util/console/hexdump.rs b/util/console/hexdump.rs new file mode 100644 index 00000000..e57888fe --- /dev/null +++ b/util/console/hexdump.rs @@ -0,0 +1,151 @@ +use ufmt::uWrite; +use zerocopy::{Immutable, IntoBytes}; + +use crate::Console; + +const HEX: [u8; 16] = *b"0123456789ABCDEF"; + +pub fn hexdump_write(writer: &mut W, data: &T) +where + W: uWrite, + T: IntoBytes + Immutable + ?Sized, +{ + let data = data.as_bytes(); + for (i, d) in data.chunks(16).enumerate() { + let mut buf = [b' '; 80]; + let mut offset = i * 16; + for j in 0..8 { + buf[7 - j] = HEX[offset & 15]; + offset = offset >> 4; + } + for (j, &byte) in d.iter().enumerate() { + buf[10 + j * 3] = HEX[(byte >> 4) as usize]; + buf[11 + j * 3] = HEX[(byte & 15) as usize]; + buf[60 + j] = if byte >= 0x20 && byte < 0x7f { + byte + } else { + b'.' + }; + } + let end = 60 + d.len(); + buf[end] = b'\n'; + let line = unsafe { core::str::from_utf8_unchecked(&buf[..end + 1]) }; + let _ = writer.write_str(line); + } +} + +pub fn hexdump(data: &T) +where + T: IntoBytes + Immutable + ?Sized, +{ + hexdump_write(&mut Console, data); +} + +pub fn hexstr<'a, T>(dest: &'a mut [u8], data: &T) -> &'a str +where + T: IntoBytes + Immutable + ?Sized, +{ + let data = data.as_bytes(); + let mut i = 0; + for &byte in data.iter() { + dest[i] = HEX[(byte >> 4) as usize]; + dest[i + 1] = HEX[(byte & 15) as usize]; + i += 2; + } + // SAFETY: the hex chars emitted into `dest` are ASCII. + unsafe { core::str::from_utf8_unchecked(&dest[..i]) } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::println; + use core::convert::Infallible; + + struct TestConsole { + pub buf: [u8; 1024], + pub len: usize, + } + + impl TestConsole { + //fn reset(&mut self) { self.len = 0; } + fn as_slice(&self) -> &[u8] { + &self.buf[..self.len] + } + } + + impl Default for TestConsole { + fn default() -> Self { + Self { + buf: [0u8; 1024], + len: 0, + } + } + } + + impl uWrite for TestConsole { + type Error = Infallible; + fn write_str(&mut self, s: &str) -> Result<(), Infallible> { + let s = s.as_bytes(); + let end = self.len + s.len(); + self.buf[self.len..end].copy_from_slice(s); + self.len = end; + Ok(()) + } + } + + // From: http://www.abrahamlincolnonline.org/lincoln/speeches/gettysburg.htm + const GETTYSBURG_PRELUDE: &'static str = "\ +Four score and seven years ago our fathers brought forth on this \ +continent, a new nation, conceived in Liberty, and dedicated to the \ +proposition that all men are created equal."; + + const GETTYSBURG_PRELUDE_HEXDUMP: &'static [u8] = b"\ +00000000 46 6F 75 72 20 73 63 6F 72 65 20 61 6E 64 20 73 Four score and s\n\ +00000010 65 76 65 6E 20 79 65 61 72 73 20 61 67 6F 20 6F even years ago o\n\ +00000020 75 72 20 66 61 74 68 65 72 73 20 62 72 6F 75 67 ur fathers broug\n\ +00000030 68 74 20 66 6F 72 74 68 20 6F 6E 20 74 68 69 73 ht forth on this\n\ +00000040 20 63 6F 6E 74 69 6E 65 6E 74 2C 20 61 20 6E 65 continent, a ne\n\ +00000050 77 20 6E 61 74 69 6F 6E 2C 20 63 6F 6E 63 65 69 w nation, concei\n\ +00000060 76 65 64 20 69 6E 20 4C 69 62 65 72 74 79 2C 20 ved in Liberty, \n\ +00000070 61 6E 64 20 64 65 64 69 63 61 74 65 64 20 74 6F and dedicated to\n\ +00000080 20 74 68 65 20 70 72 6F 70 6F 73 69 74 69 6F 6E the proposition\n\ +00000090 20 74 68 61 74 20 61 6C 6C 20 6D 65 6E 20 61 72 that all men ar\n\ +000000A0 65 20 63 72 65 61 74 65 64 20 65 71 75 61 6C 2E e created equal.\n\ +"; + + // This is the SHA256 digest of the Gettysburg prelude. + const GETTYSBURG_DIGEST: [u8; 32] = [ + 0x1e, 0x6f, 0xd4, 0x03, 0x0f, 0x90, 0x34, 0xcd, 0x77, 0x57, 0x08, 0xa3, 0x96, 0xc3, 0x24, + 0xed, 0x42, 0x0e, 0xc5, 0x87, 0xeb, 0x3d, 0xd4, 0x33, 0xe2, 0x9f, 0x6a, 0xc0, 0x8b, 0x8c, + 0xc7, 0xba, + ]; + + const GETTYSBURG_DIGEST_HEXSTR: &'static str = + "1E6FD4030F9034CD775708A396C324ED420EC587EB3DD433E29F6AC08B8CC7BA"; + + #[test] + fn test_hexdump_short() { + let buf = [0u8, 1, 2, 3, 4, 5, 100, 128, 160]; + let mut console = TestConsole::default(); + hexdump_write(&mut console, &buf); + assert_eq!( + console.as_slice(), + b"00000000 00 01 02 03 04 05 64 80 A0 ......d..\n" + ); + } + + #[test] + fn test_hexdump_long() { + let mut console = TestConsole::default(); + hexdump_write(&mut console, GETTYSBURG_PRELUDE); + assert_eq!(console.as_slice(), GETTYSBURG_PRELUDE_HEXDUMP); + } + + #[test] + fn test_hexstr() { + let mut dest = [0u8; 100]; + let result = hexstr(&mut dest, &GETTYSBURG_DIGEST); + assert_eq!(result, GETTYSBURG_DIGEST_HEXSTR); + } +} diff --git a/util/console/lib.rs b/util/console/lib.rs new file mode 100644 index 00000000..402659f0 --- /dev/null +++ b/util/console/lib.rs @@ -0,0 +1,71 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +//! Console +//! +//! This crate provides basic console functionality. + +#![no_std] + +use core::convert::Infallible; + +pub use ufmt; +use ufmt::uWrite; +pub use ufmt::{uwrite, uwriteln}; + +pub mod hexdump; + +pub struct Console; + +unsafe extern "C" { + fn system_lowlevel_console_write(ptr: *const u8, length: usize); +} + +impl uWrite for Console { + type Error = Infallible; + + fn write_str(&mut self, s: &str) -> Result<(), Infallible> { + // ok: unsafe-usage + unsafe { + // SAFETY: The pointer can never be null. + system_lowlevel_console_write(s.as_ptr(), s.len()) + }; + Ok(()) + } +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => {{ + use $crate::ufmt; + $crate::uwrite!(&mut $crate::Console, $($arg)*).unwrap(); + }}; +} + +#[macro_export] +macro_rules! println { + ($($arg:tt)*) => {{ + use $crate::ufmt; + $crate::ufmt::uwriteln!(&mut $crate::Console, $($arg)*).unwrap(); + }}; +} + +#[macro_export] +macro_rules! trace { + ($($arg:tt)*) => { + if cfg!(feature = "trace") { + use $crate::ufmt; + $crate::uwrite!(&mut $crate::Console, $($arg)*).unwrap(); + } + }; +} + +#[macro_export] +macro_rules! traceln { + ($($arg:tt)*) => { + if cfg!(feature = "trace") { + use $crate::ufmt; + $crate::uwriteln!(&mut $crate::Console, $($arg)*).unwrap(); + } + }; +} diff --git a/util/console/pigweed.rs b/util/console/pigweed.rs new file mode 100644 index 00000000..3af23146 --- /dev/null +++ b/util/console/pigweed.rs @@ -0,0 +1,32 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +use core::arch::naked_asm; + +/// This is a low-level console output function that works with firmware +/// code. This function is a wrapper for the pigweed `DebugLog` syscall. +/// We make the syscall directly because this is a static library and we +/// don't want to create duplicate symbols for the syscall crate. +/// +/// # Safety +/// +/// Callers must supply a valid ptr and length. +#[unsafe(no_mangle)] +#[unsafe(naked)] +pub unsafe extern "C" fn system_lowlevel_console_write(ptr: *const u8, length: usize) { + // This bit of assembly code is the same as: + // let _ = syscall::debug_log(bytes); + naked_asm!(" + li t0, {id} + ecall + ret + ", + id = const 0xF002_u32, + ); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/util/console/stdout.rs b/util/console/stdout.rs new file mode 100644 index 00000000..44106ba1 --- /dev/null +++ b/util/console/stdout.rs @@ -0,0 +1,18 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use std::io::Write; + +/// This is a low-level console output function that works with host-based +/// code. This simply outputs to stdout. +/// +/// # Safety +/// +/// Callers must supply a valid ptr and length. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn system_lowlevel_console_write(ptr: *const u8, length: usize) { + // SAFETY: ptr and length must be valid. + let bytes = unsafe { core::slice::from_raw_parts(ptr, length) }; + let _ = std::io::stdout().write_all(bytes); + let _ = std::io::stdout().flush(); +} diff --git a/util/error/BUILD.bazel b/util/error/BUILD.bazel new file mode 100644 index 00000000..645693d4 --- /dev/null +++ b/util/error/BUILD.bazel @@ -0,0 +1,27 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "error", + srcs = [ + "flash.rs", + "lib.rs", + "kernel.rs", + "ipc.rs", + ], + crate_name = "util_error", + edition = "2024", + deps = [ + "@pigweed//pw_status/rust:pw_status", + "@rust_crates//:ufmt", + ], +) + +rust_doc( + name = "error_doc", + crate = ":error", +) diff --git a/util/error/flash.rs b/util/error/flash.rs new file mode 100644 index 00000000..08a49780 --- /dev/null +++ b/util/error/flash.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ErrorCode, ErrorModule}; +use pw_status::Error; + +// TODO: review the pw_status error codes. + +pub const FLASH_GENERIC: ErrorModule = ErrorModule::new(0x464c); //ascii `FL`. +pub const FLASH_GENERIC_BUSY: ErrorCode = FLASH_GENERIC.from_pw(0, Error::Unavailable); +pub const FLASH_GENERIC_ERASE_INVALID_ADDR: ErrorCode = + FLASH_GENERIC.from_pw(1, Error::InvalidArgument); +pub const FLASH_GENERIC_BAD_ALIGNMENT: ErrorCode = FLASH_GENERIC.from_pw(2, Error::InvalidArgument); +pub const FLASH_GENERIC_READ_TOO_LONG: ErrorCode = FLASH_GENERIC.from_pw(3, Error::InvalidArgument); +pub const FLASH_GENERIC_PROGRAM_EXCEEDS_WINDOW_SIZE: ErrorCode = + FLASH_GENERIC.from_pw(4, Error::InvalidArgument); +pub const FLASH_GENERIC_PROGRAM_SPANS_WINDOW_BOUNDARY: ErrorCode = + FLASH_GENERIC.from_pw(5, Error::InvalidArgument); +pub const FLASH_GENERIC_ADDR_OUT_OF_BOUNDS: ErrorCode = + FLASH_GENERIC.from_pw(6, Error::InvalidArgument); +pub const FLASH_GENERIC_INVALID_PAGE_SIZE: ErrorCode = + FLASH_GENERIC.from_pw(7, Error::InvalidArgument); +pub const FLASH_GENERIC_INVALID_SIZE: ErrorCode = FLASH_GENERIC.from_pw(8, Error::InvalidArgument); + +pub const FLASH_GENERIC_SFDP_INVALID_MEMORY_DENSITY: ErrorCode = + FLASH_GENERIC.from_pw(1024, Error::InvalidArgument); +pub const FLASH_GENERIC_SFDP_INVALID_SIGNATURE: ErrorCode = + FLASH_GENERIC.from_pw(1025, Error::InvalidArgument); +pub const FLASH_GENERIC_SFDP_NO_VALID_PARAMETER_HEADER_FOUND: ErrorCode = + FLASH_GENERIC.from_pw(1026, Error::InvalidArgument); +pub const FLASH_GENERIC_SFDP_PARAMETERS_TOO_SHORT: ErrorCode = + FLASH_GENERIC.from_pw(1027, Error::InvalidArgument); +pub const FLASH_GENERIC_SFDP_UNSUPPORTED_HEADER_MAJOR_REV: ErrorCode = + FLASH_GENERIC.from_pw(1028, Error::InvalidArgument); +pub const FLASH_GENERIC_SFDP_UNSUPPORTED_PARAMS_MAJOR_REV: ErrorCode = + FLASH_GENERIC.from_pw(1029, Error::InvalidArgument); +pub const FLASH_GENERIC_SFDP_PARAMETERS_TOO_LONG: ErrorCode = + FLASH_GENERIC.from_pw(1030, Error::InvalidArgument); + +pub const FLASH_OPENTITAN: ErrorModule = ErrorModule::new(0x464f); //ascii `FO`. diff --git a/util/error/ipc.rs b/util/error/ipc.rs new file mode 100644 index 00000000..fd97fe48 --- /dev/null +++ b/util/error/ipc.rs @@ -0,0 +1,12 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ErrorCode, ErrorModule}; +use pw_status::Error; + +pub const IPC_ERROR: ErrorModule = ErrorModule::new(0x4943); // ascii `IC` +pub const IPC_ERROR_RSP_BAD_LEN: ErrorCode = IPC_ERROR.from_pw(1, Error::InvalidArgument); +pub const IPC_ERROR_RSP_TOO_LARGE: ErrorCode = IPC_ERROR.from_pw(2, Error::InvalidArgument); +pub const IPC_ERROR_BAD_REQ: ErrorCode = IPC_ERROR.from_pw(3, Error::InvalidArgument); +pub const IPC_ERROR_BAD_REQ_LEN: ErrorCode = IPC_ERROR.from_pw(4, Error::InvalidArgument); +pub const IPC_ERROR_UNKNOWN_OP: ErrorCode = IPC_ERROR.from_pw(5, Error::Unknown); diff --git a/util/error/kernel.rs b/util/error/kernel.rs new file mode 100644 index 00000000..c8398f82 --- /dev/null +++ b/util/error/kernel.rs @@ -0,0 +1,22 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ErrorCode, ErrorModule}; + +pub const KERNEL_ERROR: ErrorModule = ErrorModule::new(0x4b45); // ascii `KE` +pub const KERNEL_ERROR_CANCELLED: ErrorCode = KERNEL_ERROR.error(1); +pub const KERNEL_ERROR_UNKNOWN: ErrorCode = KERNEL_ERROR.error(2); +pub const KERNEL_ERROR_INVALID_ARGUMENT: ErrorCode = KERNEL_ERROR.error(3); +pub const KERNEL_ERROR_DEADLINE_EXCEEDED: ErrorCode = KERNEL_ERROR.error(4); +pub const KERNEL_ERROR_NOT_FOUND: ErrorCode = KERNEL_ERROR.error(5); +pub const KERNEL_ERROR_ALREADY_EXISTS: ErrorCode = KERNEL_ERROR.error(6); +pub const KERNEL_ERROR_PERMISSION_DENIED: ErrorCode = KERNEL_ERROR.error(7); +pub const KERNEL_ERROR_RESOURCE_EXHAUSTED: ErrorCode = KERNEL_ERROR.error(8); +pub const KERNEL_ERROR_FAILED_PRECONDITION: ErrorCode = KERNEL_ERROR.error(9); +pub const KERNEL_ERROR_ABORTED: ErrorCode = KERNEL_ERROR.error(10); +pub const KERNEL_ERROR_OUT_OF_RANGE: ErrorCode = KERNEL_ERROR.error(11); +pub const KERNEL_ERROR_UNIMPLEMENTED: ErrorCode = KERNEL_ERROR.error(12); +pub const KERNEL_ERROR_INTERNAL: ErrorCode = KERNEL_ERROR.error(13); +pub const KERNEL_ERROR_UNAVAILABLE: ErrorCode = KERNEL_ERROR.error(14); +pub const KERNEL_ERROR_DATA_LOSS: ErrorCode = KERNEL_ERROR.error(15); +pub const KERNEL_ERROR_UNAUTHENTICATED: ErrorCode = KERNEL_ERROR.error(16); diff --git a/util/error/lib.rs b/util/error/lib.rs new file mode 100644 index 00000000..072c3364 --- /dev/null +++ b/util/error/lib.rs @@ -0,0 +1,91 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +use core::num::NonZero; +use ufmt::{uDebug, uDisplay, uwrite}; + +mod flash; +mod ipc; +mod kernel; + +pub use flash::*; +pub use ipc::*; +pub use kernel::*; + +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ErrorModule(pub NonZero); + +impl ErrorModule { + pub const fn new(val: u16) -> Self { + match NonZero::new(val) { + Some(val) => Self(val), + None => panic!("ErrorModule must be non-zero"), + } + } + + pub const fn error(self, code: u16) -> ErrorCode { + ErrorCode::new(((self.0.get() as u32) << 16) | (code as u32)) + } + + pub const fn from_pw(self, code: u16, err: pw_status::Error) -> ErrorCode { + // pw_status::Error is 5 bits. + self.error((code << 5) | (err as u16)) + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct ErrorCode(pub NonZero); +impl ErrorCode { + pub const fn new(val: u32) -> Self { + match NonZero::new(val) { + Some(val) => Self(val), + None => panic!("ErrorCode must be non-zero"), + } + } + + pub fn kernel_error(e: pw_status::Error) -> Self { + KERNEL_ERROR.error(e as u16) + } +} + +impl From for u32 { + fn from(e: ErrorCode) -> u32 { + e.0.get() + } +} + +impl uDisplay for ErrorCode { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + uwrite!(f, "0x{:x}", self.0.get()) + } +} + +impl uDebug for ErrorCode { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + uDisplay::fmt(self, f) + } +} + +impl core::fmt::Display for ErrorCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "0x{:x}", self.0.get()) + } +} + +impl core::fmt::Debug for ErrorCode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } +} + +impl core::error::Error for ErrorCode {} diff --git a/util/io/BUILD.bazel b/util/io/BUILD.bazel new file mode 100644 index 00000000..49000837 --- /dev/null +++ b/util/io/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "io", + srcs = [ + "io.rs", + ], + crate_name = "util_io", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "//util/error", + "@pigweed//pw_status/rust:pw_status", + ], +) + +rust_test( + name = "io_test", + crate = ":io", +) diff --git a/util/io/io.rs b/util/io/io.rs new file mode 100644 index 00000000..34851069 --- /dev/null +++ b/util/io/io.rs @@ -0,0 +1,62 @@ +#![no_std] + +use util_error::{ErrorCode, ErrorModule}; + +pub const IO_GENERIC: ErrorModule = ErrorModule::new(0x494F); // ascii: IO +pub const IO_GENERIC_READ_OUT_OF_BOUNDS: ErrorCode = + IO_GENERIC.from_pw(1, pw_status::Error::OutOfRange); + +/// Trait for random access read +pub trait RandomRead { + fn read(&mut self, start_addr: usize, dst: &mut [u8]) -> Result<(), ErrorCode>; + fn size(&self) -> usize; +} + +impl RandomRead for &[u8] { + fn read(&mut self, start_addr: usize, dst: &mut [u8]) -> Result<(), ErrorCode> { + // Explicit wrapping add. Overflows are expected to + // be detected in the indexing operation + let end_addr = start_addr.wrapping_add(dst.len()); + let src = self + .get(start_addr..end_addr) + .ok_or(IO_GENERIC_READ_OUT_OF_BOUNDS)?; + dst.copy_from_slice(src); + Ok(()) + } + fn size(&self) -> usize { + self.len() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn should_read() { + let mut src: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut dst: [u8; 3] = [0; 3]; + assert!(src.read(6, &mut dst).is_ok()); + assert_eq!(&dst, &[7, 8, 9]); + assert_eq!(RandomRead::size(&src), 9); + } + + #[test] + fn should_fail() { + let mut src: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9]; + let mut dst: [u8; 3] = [0; 3]; + assert!(src.read(7, &mut dst).is_err()); + } + + #[test] + fn invalid_start_address_should_not_panic() { + let mut src: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8]; + let mut dst: [u8; 4] = [0; 4]; + // Set `start_addr` so that adding `dst.len()` causes it to + // wrap around and become smaller than `src.len()` + let start_addr: usize = usize::MAX - (dst.len() - 1); + // Should not panic + let result = src.read(start_addr, &mut dst); + assert!(result.is_err()); + } +} diff --git a/util/ipc/BUILD.bazel b/util/ipc/BUILD.bazel new file mode 100644 index 00000000..5c6c300a --- /dev/null +++ b/util/ipc/BUILD.bazel @@ -0,0 +1,24 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library") +load("//target/earlgrey:defs.bzl", "TARGET_COMPATIBLE_WITH") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "ipc", + srcs = [ + "lib.rs", + ], + crate_name = "util_ipc", + edition = "2024", + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + "//util/error", + "@pigweed//pw_kernel/syscall:syscall_user", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) diff --git a/util/ipc/lib.rs b/util/ipc/lib.rs new file mode 100644 index 00000000..eca0c82a --- /dev/null +++ b/util/ipc/lib.rs @@ -0,0 +1,87 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] +use userspace::syscall::{self, Signals}; +use userspace::time::Instant; + +use util_error::ErrorCode; + +pub struct IpcChannel(u32); + +impl IpcChannel { + pub const fn new(channel: u32) -> Self { + IpcChannel(channel) + } + + pub fn handle(&self) -> u32 { self.0 } + + pub fn check_status(code: u32) -> Result<(), ErrorCode> { + if code == 0 { + Ok(()) + } else { + Err(ErrorCode::new(code)) + } + } + + pub fn transaction( + &self, + request: &[&[u8]], + response: &mut [&mut [u8]], + deadline: Instant, + ) -> Result { + if false { + //let _n = N; + //syscall::channel_transact_iovec(self.0, request, response, deadline) + Err(util_error::KERNEL_ERROR_UNIMPLEMENTED) + } else { + let mut buffer = [0u8; N]; + let mut offset = 0usize; + + for item in request.iter() { + let sz = offset + item.len(); + buffer[offset..sz].copy_from_slice(item); + offset = sz; + } + let req = unsafe { + // SAFETY: naughty creation of a const ref to the same slice + // so we can use the same buffer for send and recv. + core::slice::from_raw_parts(buffer.as_ptr(), offset) + }; + let rsplen = syscall::channel_transact(self.0, req, &mut buffer, deadline) + .map_err(ErrorCode::kernel_error)?; + + offset = 0usize; + let rsp = &buffer[..rsplen]; + for item in response.iter_mut() { + let sz = offset + item.len(); + // TODO: how to handle an incomplete response?. + if sz > rsp.len() { + break; + } + item.copy_from_slice(&rsp[offset..sz]); + offset = sz; + } + Ok(rsplen) + } + } + + pub fn wait_readable(&self) -> Result<(), ErrorCode> { + loop { + let w = syscall::object_wait(self.0, Signals::READABLE, Instant::MAX) + .map_err(ErrorCode::kernel_error)?; + if w.pending_signals.contains(Signals::READABLE) { + break; + } + } + Ok(()) + } + + pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result { + syscall::channel_read(self.0, offset, buffer).map_err(ErrorCode::kernel_error) + } + + pub fn respond(&self, buffer: &[u8]) -> Result<(), ErrorCode> { + syscall::channel_respond(self.0, buffer).map_err(ErrorCode::kernel_error) + } +} diff --git a/util/regcpy/BUILD.bazel b/util/regcpy/BUILD.bazel new file mode 100644 index 00000000..3d366e2e --- /dev/null +++ b/util/regcpy/BUILD.bazel @@ -0,0 +1,29 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "regcpy", + srcs = [ + "regcpy.rs", + ], + crate_name = "util_regcpy", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "@rust_crates//:aligned", + "@rust_crates//:zerocopy", + "@ureg", + ], +) + +rust_test( + name = "regcpy_test", + crate = ":regcpy", + edition = "2024", + rustc_flags = [ + "-C", + "debug-assertions", + ], +) diff --git a/util/regcpy/regcpy.rs b/util/regcpy/regcpy.rs new file mode 100644 index 00000000..417d2633 --- /dev/null +++ b/util/regcpy/regcpy.rs @@ -0,0 +1,972 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 +#![cfg_attr(not(test), no_std)] + +// TODO: should this be upstreamed into ureg? + +use aligned::Aligned; +use aligned::A4; +use core::cmp::min; +use zerocopy::FromBytes; +use zerocopy::Unalign; + +#[inline(never)] +pub fn copy_to_reg_unaligned( + reg: &ureg::RegRef< + impl ureg::WritableReg + ureg::ResettableReg, + impl ureg::MmioMut + Copy, + >, + src: &[u8], +) { + let (words, rem_bytes): (&[Unalign], &[u8]) = FromBytes::ref_from_prefix(src).unwrap(); + + for word in words { + reg.write(|_| word.get()); + } + if let Some(last_word) = last_word(rem_bytes) { + reg.write(|_| last_word); + } +} + +#[inline(never)] +pub fn copy_to_reg( + reg: &ureg::RegRef< + impl ureg::WritableReg + ureg::ResettableReg, + impl ureg::MmioMut + Copy, + >, + src: &Aligned, +) { + // Convert to regular slice; optimizer should be smart enough to realize it's still aligned. + let (words, rem_bytes): (&[u32], &[u8]) = FromBytes::ref_from_prefix(src.as_ref()).unwrap(); + + for word in words { + reg.write(|_| *word); + } + if let Some(last_word) = last_word(rem_bytes) { + reg.write(|_| last_word); + } +} + +#[inline(never)] +pub fn copy_to_reg_array( + array: &ureg::Array< + LEN, + ureg::RegRef< + impl ureg::WritableReg + ureg::ResettableReg, + impl ureg::MmioMut + Copy, + >, + >, + src: &Aligned, +) { + // Convert to regular slice; optimizer should be smart enough to realize it's still aligned. + let (words, rem_bytes): (&[u32], &[u8]) = FromBytes::ref_from_prefix(src.as_ref()).unwrap(); + + let words_to_copy = min(LEN, words.len()); + + #[allow(clippy::needless_range_loop)] // optimizes better + for i in 0..words_to_copy { + array.at(i).write(|_| words[i]); + } + let Some(reg) = array.get(words.len()) else { + return; + }; + let Some(last_word) = last_word(rem_bytes) else { + return; + }; + reg.write(|_| last_word); +} + +#[inline(never)] +pub fn copy_to_reg_array_unaligned( + array: &ureg::Array< + LEN, + ureg::RegRef< + impl ureg::WritableReg + ureg::ResettableReg, + impl ureg::MmioMut + Copy, + >, + >, + src: &[u8], +) { + let (words, rem_bytes): (&[Unalign], &[u8]) = FromBytes::ref_from_prefix(src).unwrap(); + + let words_to_copy = min(LEN, words.len()); + + #[allow(clippy::needless_range_loop)] // optimizes better + for i in 0..words_to_copy { + array.at(i).write(|_| words[i].get()); + } + let Some(reg) = array.get(words.len()) else { + return; + }; + let Some(last_word) = last_word(rem_bytes) else { + return; + }; + reg.write(|_| last_word); +} + +#[inline(never)] +pub fn copy_from_reg( + dest: &mut Aligned, + reg: &ureg::RegRef, impl ureg::Mmio + Copy>, +) { + let (words, rem_bytes): (&mut [u32], &mut [u8]) = FromBytes::mut_from_prefix(dest).unwrap(); + let words_to_copy = min(LEN, words.len()); + + #[allow(clippy::needless_range_loop)] + for i in 0..words_to_copy { + words[i] = reg.read(); + } + + if words_to_copy < LEN { + set_rem_bytes(rem_bytes, || reg.read()); + } +} + +#[inline(never)] +pub fn copy_from_reg_array( + dest: &mut Aligned, + array: &ureg::Array< + LEN, + ureg::RegRef, impl ureg::Mmio + Copy>, + >, +) { + let (words, rem_bytes): (&mut [u32], &mut [u8]) = FromBytes::mut_from_prefix(dest).unwrap(); + let words_to_copy = min(LEN, words.len()); + + #[allow(clippy::needless_range_loop)] + for i in 0..words_to_copy { + words[i] = array.at(i).read(); + } + + if words_to_copy < LEN { + set_rem_bytes(rem_bytes, || array.at(words_to_copy).read()); + } +} + +#[inline(never)] +pub fn copy_from_reg_array_unaligned( + dest: &mut [u8], + array: &ureg::Array< + LEN, + ureg::RegRef, impl ureg::Mmio + Copy>, + >, +) { + let (words, rem_bytes): (&mut [Unalign], &mut [u8]) = + FromBytes::mut_from_prefix(dest).unwrap(); + let words_to_copy = min(LEN, words.len()); + + #[allow(clippy::needless_range_loop)] + for i in 0..words_to_copy { + words[i].set(array.at(i).read()); + } + + if words_to_copy < LEN { + set_rem_bytes(rem_bytes, || array.at(words_to_copy).read()); + } +} + +#[inline(never)] +pub fn copy_from_reg_unaligned( + dest: &mut [u8], + reg: &ureg::RegRef, impl ureg::MmioMut + Copy>, +) { + let (words, rem_bytes): (&mut [Unalign], &mut [u8]) = + FromBytes::mut_from_prefix(dest).unwrap(); + + for word in words { + word.set(reg.read()) + } + set_rem_bytes(rem_bytes, || reg.read()); +} + +#[inline(always)] +fn last_word(rem_bytes: &[u8]) -> Option { + if rem_bytes.is_empty() { + None + } else { + Some(u32::from_le_bytes([ + rem_bytes[0], + rem_bytes.get(1).copied().unwrap_or_default(), + rem_bytes.get(2).copied().unwrap_or_default(), + 0, + ])) + } +} + +#[inline(always)] +fn set_rem_bytes(rem_bytes: &mut [u8], get_word: impl FnOnce() -> u32) { + if rem_bytes.is_empty() { + return; + } + let word = get_word(); + rem_bytes[0] = word as u8; + if rem_bytes.len() == 1 { + return; + } + rem_bytes[1] = (word >> 8) as u8; + if rem_bytes.len() == 2 { + return; + } + rem_bytes[2] = (word >> 16) as u8; +} + +#[cfg(test)] +mod test { + use super::*; + + use core::cell::RefCell; + use core::mem::transmute_copy; + use std::collections::HashMap; + use std::collections::VecDeque; + use std::rc::Rc; + + use ureg::Mmio; + use ureg::MmioMut; + use ureg::ReadWriteReg32; + use ureg::RegRef; + use ureg::UintType; + + fn uint_val(val: T) -> u64 { + // nosemgrep + unsafe { + // SAFETY: The underlying source type is the same as the destination type. + match T::TYPE { + ureg::UintType::U8 => core::mem::transmute_copy::(&val).into(), + ureg::UintType::U16 => core::mem::transmute_copy::(&val).into(), + ureg::UintType::U32 => core::mem::transmute_copy::(&val).into(), + ureg::UintType::U64 => core::mem::transmute_copy::(&val), + } + } + } + + #[derive(Clone, Default)] + struct FakeMmio { + fifos: Rc>>>, + write_log: Rc>>, + } + impl FakeMmio { + fn fifo_push(&self, addr: usize, val: u32) { + self.fifos + .borrow_mut() + .entry(addr) + .or_default() + .push_back(val); + } + fn take_log(&self) -> Vec<(usize, u64)> { + core::mem::take(&mut *self.write_log.borrow_mut()) + } + fn log(&self) -> Vec<(usize, u64)> { + self.write_log.borrow().clone() + } + } + impl Mmio for FakeMmio { + unsafe fn read_volatile(&self, src: *const T) -> T { + let addr = src as usize; + let Some(val) = self.fifos.borrow_mut().entry(addr).or_default().pop_front() else { + panic!("Unexpected read from addr 0x{addr:x}") + }; + if T::TYPE != UintType::U32 { + panic!("Read must be of type u32"); + } + // nosemgrep + unsafe { + // SAFETY: the type `T` is u32. + transmute_copy::(&val) + } + } + } + impl MmioMut for FakeMmio { + unsafe fn write_volatile(&self, dst: *mut T, src: T) { + self.write_log + .borrow_mut() + .push((dst as usize, uint_val(src))); + } + } + + #[test] + #[should_panic(expected = "Unexpected read from addr 0x40404040")] + pub fn test_fake_mmio_read_unexpected_addr() { + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, _>::new_with_mmio(0x4040_4040 as *mut _, &mmio) + }; + fifo_reg.read(); + } + + #[test] + #[should_panic(expected = "Read must be of type u32")] + pub fn test_fake_mmio_read_unexpected_type() { + let addr: usize = 0x4040; + let mmio = FakeMmio::default(); + mmio.fifo_push(addr, 42); + // nosemgrep + unsafe { + // SAFETY: the backend is FakeMmio. + mmio.read_volatile(addr as *const u64) + }; + } + + #[test] + pub fn test_fake_mmio_read() { + let addr = 0x4040_4040; + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, _>::new_with_mmio(addr as *mut _, &mmio) + }; + mmio.fifo_push(addr, 0xba5e_ba11); + mmio.fifo_push(addr, 0x1234_5678); + assert_eq!(fifo_reg.read(), 0xba5e_ba11); + assert_eq!(fifo_reg.read(), 0x1234_5678); + } + + #[test] + #[should_panic(expected = "Unexpected read from addr 0x40404040")] + pub fn test_fake_mmio_read_fifo_exhausted() { + let addr = 0x4040_4040; + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, _>::new_with_mmio(addr as *mut _, &mmio) + }; + mmio.fifo_push(addr, 0xba5e_ba11); + mmio.fifo_push(addr, 0x1234_5678); + assert_eq!(fifo_reg.read(), 0xba5e_ba11); + assert_eq!(fifo_reg.read(), 0x1234_5678); + fifo_reg.read(); + } + + #[test] + #[rustfmt::skip] + pub fn test_fake_mmio_write() { + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, _>::new_with_mmio(0x4040_4040 as *mut _, &mmio) + }; + // nosemgrep + let fifo_reg2 = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, _>::new_with_mmio(0x5050_5050 as *mut _, &mmio) + }; + + assert_eq!( + mmio.log(), + vec![], + ); + fifo_reg.write(|_| 0xba5e_ba11); + assert_eq!( + mmio.log(), + vec![ + (0x4040_4040, 0xba5e_ba11), + ], + ); + fifo_reg.write(|_| 0xabba_abba); + assert_eq!( + mmio.log(), + vec![ + (0x4040_4040, 0xba5e_ba11), + (0x4040_4040, 0xabba_abba), + ], + ); + fifo_reg2.write(|_| 0x1234_5678); + assert_eq!( + mmio.log(), + vec![ + (0x4040_4040, 0xba5e_ba11), + (0x4040_4040, 0xabba_abba), + (0x5050_5050, 0x1234_5678), + ], + ); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_to_reg_unaligned() { + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, &FakeMmio>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_to_reg_unaligned(&fifo_reg, &[]); + assert_eq!( + mmio.take_log(), + vec![], + ); + copy_to_reg_unaligned(&fifo_reg, &[0x12]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_0012)], + ); + + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_3412)], + ); + + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34, 0x56]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0056_3412)], + ); + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34, 0x56, 0x78]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x7856_3412)], + ); + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34, 0x56, 0x78, 0x9a]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0x0000_009a), + ], + ); + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0x0000_bc9a), + ], + ); + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0xf0de_bc9a), + ], + ); + copy_to_reg_unaligned(&fifo_reg, &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xdd]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0xf0de_bc9a), + (0x4040_4040, 0x0000_00dd), + ], + ); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_to_reg() { + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, &FakeMmio>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_to_reg(&fifo_reg, &Aligned([])); + assert_eq!( + mmio.take_log(), + vec![], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_0012)], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_3412)], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34, 0x56])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0056_3412)], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34, 0x56, 0x78])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x7856_3412)], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0x0000_009a), + ], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0x0000_bc9a), + ], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0xf0de_bc9a), + ], + ); + copy_to_reg(&fifo_reg, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xdd])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4040, 0xf0de_bc9a), + (0x4040_4040, 0x0000_00dd), + ], + ); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_to_reg_array() { + let mmio = FakeMmio::default(); + // nosemgrep + let reg_array = unsafe { + // SAFETY: the backend is FakeMmio. + ureg::Array::<3, RegRef, &FakeMmio>>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_to_reg_array(®_array, &Aligned([])); + assert_eq!( + mmio.take_log(), + vec![], + ); + + copy_to_reg_array(®_array, &Aligned([0x12])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_0012)], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_3412)], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34, 0x56])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0056_3412)], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34, 0x56, 0x78])); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x7856_3412)], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0x0000_009a), + ], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0x0000_bc9a), + ], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0xf0de_bc9a), + ], + ); + + copy_to_reg_array(®_array, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xdd])); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0xf0de_bc9a), + (0x4040_4048, 0x0000_00dd), + ], + ); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_to_reg_array_unaligned() { + let mmio = FakeMmio::default(); + let reg_array = unsafe { + ureg::Array::<3, RegRef, &FakeMmio>>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_to_reg_array_unaligned(®_array, &[]); + assert_eq!( + mmio.take_log(), + vec![], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_0012)], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0000_3412)], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34, 0x56]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x0056_3412)], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34, 0x56, 0x78]); + assert_eq!( + mmio.take_log(), + vec![(0x4040_4040, 0x7856_3412)], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34, 0x56, 0x78, 0x9a]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0x0000_009a), + ], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0x0000_bc9a), + ], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0xf0de_bc9a), + ], + ); + + copy_to_reg_array_unaligned(®_array, &[0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0xdd]); + assert_eq!( + mmio.take_log(), + vec![ + (0x4040_4040, 0x7856_3412), + (0x4040_4044, 0xf0de_bc9a), + (0x4040_4048, 0x0000_00dd), + ], + ); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_from_reg() { + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, &FakeMmio>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_from_reg::<10>(&mut Aligned([]), &fifo_reg); + assert_eq!( + mmio.take_log(), + vec![], + ); + + mmio.fifo_push(0x4040_4040, 0x0000_0012); + let mut result = Aligned::([0_u8; 1]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12])); + + mmio.fifo_push(0x4040_4040, 0x0000_3412); + let mut result = Aligned::([0_u8; 2]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34])); + + mmio.fifo_push(0x4040_4040, 0x0056_3412); + let mut result = Aligned::([0_u8; 3]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + let mut result = Aligned::([0_u8; 4]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4040, 0x0000_009A); + let mut result = Aligned::([0_u8; 5]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4040, 0x0000_BC9A); + let mut result = Aligned::([0_u8; 6]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4040, 0x00DE_BC9A); + let mut result = Aligned::([0_u8; 7]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4040, 0xF0DE_BC9A); + let mut result = Aligned::([0_u8; 8]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4040, 0xF0DE_BC9A); + mmio.fifo_push(0x4040_4040, 0x0000_00DD); + let mut result = Aligned::([0_u8; 9]); + copy_from_reg::<10>(&mut result, &fifo_reg); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0xDD])); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_from_reg_array() { + let mmio = FakeMmio::default(); + // nosemgrep + let reg_array = unsafe { + // SAFETY: the backend is FakeMmio. + ureg::Array::<3, RegRef, &FakeMmio>>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_from_reg_array(&mut Aligned([]), ®_array); + assert_eq!( + mmio.take_log(), + vec![], + ); + + mmio.fifo_push(0x4040_4040, 0x0000_0012); + let mut result = Aligned::([0_u8; 1]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12])); + + mmio.fifo_push(0x4040_4040, 0x0000_3412); + let mut result = Aligned::([0_u8; 2]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34])); + + mmio.fifo_push(0x4040_4040, 0x0056_3412); + let mut result = Aligned::([0_u8; 3]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + let mut result = Aligned::([0_u8; 4]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0x0000_009A); + let mut result = Aligned::([0_u8; 5]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0x0000_BC9A); + let mut result = Aligned::([0_u8; 6]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0x00DE_BC9A); + let mut result = Aligned::([0_u8; 7]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0xF0DE_BC9A); + let mut result = Aligned::([0_u8; 8]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0])); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0xF0DE_BC9A); + mmio.fifo_push(0x4040_4048, 0x0000_00DD); + let mut result = Aligned::([0_u8; 9]); + copy_from_reg_array(&mut result, ®_array); + assert_eq!(&result, &Aligned([0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0xDD])); + } + + #[test] + #[rustfmt::skip] + pub fn test_copy_from_reg_array_unaligned() { + let mmio = FakeMmio::default(); + let reg_array = unsafe { + ureg::Array::<3, RegRef, &FakeMmio>>::new_with_mmio( + 0x4040_4040 as *mut _, + &mmio, + ) + }; + + copy_from_reg_array_unaligned(&mut [], ®_array); + assert_eq!( + mmio.take_log(), + vec![], + ); + + mmio.fifo_push(0x4040_4040, 0x0000_0012); + let mut result = [0_u8; 1]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12]); + + mmio.fifo_push(0x4040_4040, 0x0000_3412); + let mut result = [0_u8; 2]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34]); + + mmio.fifo_push(0x4040_4040, 0x0056_3412); + let mut result = [0_u8; 3]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56]); + + mmio.fifo_push(0x4040_4040, 0x78563412); + let mut result = [0_u8; 4]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78]); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0x0000_009A); + let mut result = [0_u8; 5]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9A]); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0x0000_BC9A); + let mut result = [0_u8; 6]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC]); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0x00DE_BC9A); + let mut result = [0_u8; 7]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE]); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0xF0DE_BC9A); + let mut result = [0_u8; 8]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); + + mmio.fifo_push(0x4040_4040, 0x78563412); + mmio.fifo_push(0x4040_4044, 0xF0DE_BC9A); + mmio.fifo_push(0x4040_4048, 0x0000_00DD); + let mut result = [0_u8; 9]; + copy_from_reg_array_unaligned(&mut result, ®_array); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0xDD]); + } + + #[test] + pub fn test_copy_from_reg_unaligned() { + let addr: usize = 0x4040_4040; + let mmio = FakeMmio::default(); + // nosemgrep + let fifo_reg = unsafe { + // SAFETY: the backend is FakeMmio. + RegRef::, &FakeMmio>::new_with_mmio(addr as *mut _, &mmio) + }; + copy_from_reg_unaligned(&mut [], &fifo_reg); + + mmio.fifo_push(addr, 0x7856_3412); + let mut result = [0_u8; 1]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12]); + + mmio.fifo_push(addr, 0x7856_3412); + let mut result = [0_u8; 2]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34]); + + mmio.fifo_push(addr, 0x7856_3412); + let mut result = [0_u8; 3]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34, 0x56]); + + mmio.fifo_push(addr, 0x7856_3412); + let mut result = [0_u8; 4]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78]); + + mmio.fifo_push(addr, 0x7856_3412); + mmio.fifo_push(addr, 0xf0de_bc9a); + let mut result = [0_u8; 5]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9a]); + + mmio.fifo_push(addr, 0x7856_3412); + mmio.fifo_push(addr, 0xf0de_bc9a); + let mut result = [0_u8; 6]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]); + + mmio.fifo_push(addr, 0x7856_3412); + mmio.fifo_push(addr, 0xf0de_bc9a); + let mut result = [0_u8; 7]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde]); + + mmio.fifo_push(addr, 0x7856_3412); + mmio.fifo_push(addr, 0xf0de_bc9a); + let mut result = [0_u8; 8]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!(result, [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0]); + + mmio.fifo_push(addr, 0x7856_3412); + mmio.fifo_push(addr, 0xf0de_bc9a); + mmio.fifo_push(addr, 0x2c); + let mut result = [0_u8; 9]; + copy_from_reg_unaligned(&mut result, &fifo_reg); + assert_eq!( + result, + [0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x2c] + ); + } +} diff --git a/util/ringbuffer/BUILD.bazel b/util/ringbuffer/BUILD.bazel new file mode 100644 index 00000000..7efd7865 --- /dev/null +++ b/util/ringbuffer/BUILD.bazel @@ -0,0 +1,27 @@ +# Licensed under the Apache-2.0 license +# SPDX-License-Identifier: Apache-2.0 + +load("@rules_rust//rust:defs.bzl", "rust_doc", "rust_library", "rust_test") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "ringbuffer", + srcs = [ + "lib.rs", + ], + crate_name = "util_ringbuffer", + edition = "2024", + deps = [ + ], +) + +rust_test( + name = "ringbuffer_test", + crate = ":ringbuffer", +) + +rust_doc( + name = "ringbuffer_doc", + crate = ":ringbuffer", +) diff --git a/util/ringbuffer/lib.rs b/util/ringbuffer/lib.rs new file mode 100644 index 00000000..73c2dd84 --- /dev/null +++ b/util/ringbuffer/lib.rs @@ -0,0 +1,179 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![cfg_attr(not(test), no_std)] + +/// A simple single-threaded ring buffer of type `T` with a backing array of size `N`. +/// +/// The effective capacity of this queue is `N - 1`. +/// +/// If producer == consumer, the queue is empty. +/// If (producer + 1) % N == consumer, the queue is full. +pub struct RingBuffer { + producer: usize, + consumer: usize, + data: [T; N], +} + +/// Creates a default empty `RingBuffer`. +/// +/// # Panics +/// +/// Panics at compile time if `N <= 1`. +impl Default for RingBuffer { + fn default() -> Self { + // The queue requires at least 2 slots to store 1 item (N-1 capacity). + // Using a const block to ensure this is checked at compile time. + const { assert!(N > 1, "RingBuffer size N must be greater than 1") }; + Self { + producer: 0, + consumer: 0, + data: [T::default(); N], + } + } +} + + +impl RingBuffer { + /// Pushes an item into the buffer. + /// + /// Returns `Ok(())` if the item was successfully added, or `Err(item)` if the buffer is full. + pub fn push(&mut self, item: T) -> Result<(), T> { + let next = (self.producer + 1) % N; + if next != self.consumer { + self.data[self.producer] = item; + self.producer = next; + Ok(()) + } else { + Err(item) + } + } + + /// Removes and returns the first item from the buffer. + /// + /// Returns `Some(item)` if the buffer is not empty, or `None` if it is. + pub fn pop(&mut self) -> Option { + if self.consumer != self.producer { + let item = self.data[self.consumer]; + self.consumer = (self.consumer + 1) % N; + Some(item) + } else { + None + } + } + + /// Returns the number of items currently in the buffer. + pub fn len(&self) -> usize { + (self.producer + N - self.consumer) % N + } + + /// Returns `true` if the buffer is empty. + pub fn is_empty(&self) -> bool { + self.producer == self.consumer + } + + /// Returns `true` if the buffer is full. + pub fn is_full(&self) -> bool { + (self.producer + 1) % N == self.consumer + } + + /// Pushes as many items from the slice into the buffer as possible. + /// + /// Returns `Ok(())` if all items were added, or `Err(&[T])` containing the + /// remaining unbuffered items if the buffer became full. + pub fn push_slice<'a>(&mut self, s: &'a [T]) -> Result<(), &'a [T]> { + let free = (self.consumer + N - self.producer - 1) % N; + let n = core::cmp::min(s.len(), free); + + let mut remaining = n; + let mut src_idx = 0; + + let chunk1 = core::cmp::min(remaining, N - self.producer); + if chunk1 > 0 { + self.data[self.producer..self.producer + chunk1].copy_from_slice(&s[src_idx..src_idx + chunk1]); + self.producer = (self.producer + chunk1) % N; + remaining -= chunk1; + src_idx += chunk1; + } + + if remaining > 0 { + self.data[0..remaining].copy_from_slice(&s[src_idx..src_idx + remaining]); + self.producer = remaining; + } + + if n < s.len() { + Err(&s[n..]) + } else { + Ok(()) + } + } + + /// Returns a slice containing the contiguous part of the buffered data. + /// + /// If the data wraps around the end of the backing array, this only returns + /// the first part. Callers should use `consume()` and `as_slice()` again to + /// retrieve the wrapped portion. + pub fn as_slice(&self) -> &[T] { + if self.consumer <= self.producer { + // The available slice is contiguous in the array. + &self.data[self.consumer..self.producer] + } else { + // Slices can't wrap around the end of the array, so give just the chunk we can give. + &self.data[self.consumer..] + } + } + + /// Advances the consumer pointer by `n` items, effectively removing them from the buffer. + /// + /// If `n` is greater than the current length, only the available items are consumed. + pub fn consume(&mut self, n: usize) { + let n = core::cmp::min(n, self.len()); + self.consumer = (self.consumer + n) % N; + } +} + +#[cfg(test)] +#[allow(clippy::bool_assert_comparison)] +mod tests { + use super::*; + + #[test] + fn test_push_pop() { + let mut q = RingBuffer::::default(); + + assert_eq!(q.push(b'b'), Ok(())); + assert_eq!(q.push(b'y'), Ok(())); + assert_eq!(q.push(b'e'), Ok(())); + assert_eq!(q.push(b'x'), Err(b'x')); // Overflow + assert_eq!(q.len(), 3); + assert_eq!(q.is_empty(), false); + assert_eq!(q.is_full(), true); + + assert_eq!(q.pop(), Some(b'b')); + assert_eq!(q.pop(), Some(b'y')); + assert_eq!(q.pop(), Some(b'e')); + assert_eq!(q.pop(), None); // No more items. + assert_eq!(q.len(), 0); + assert_eq!(q.is_empty(), true); + assert_eq!(q.is_full(), false); + } + + #[test] + fn test_as_slice() { + let mut q = RingBuffer::::default(); + assert_eq!(q.push_slice(b"Hello"), Ok(())); + + assert_eq!(q.as_slice(), b"Hello"); + q.consume(5); + assert_eq!(q.is_empty(), true); + + // This part will be wrapped around the end of the array, + // so we need to perform two `as_slice` ops to get the whole thing. + assert_eq!(q.push_slice(b"World"), Ok(())); + assert_eq!(q.as_slice(), b"Wor"); + q.consume(3); + assert_eq!(q.as_slice(), b"ld"); + q.consume(2); + assert_eq!(q.is_empty(), true); + } +} diff --git a/util/types/BUILD.bazel b/util/types/BUILD.bazel new file mode 100644 index 00000000..8769f3fa --- /dev/null +++ b/util/types/BUILD.bazel @@ -0,0 +1,23 @@ +load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test") + +rust_library( + name = "types", + srcs = [ + "fourcc.rs", + "lib.rs", + "opcode.rs", + "power_of_2.rs", + ], + crate_name = "util_types", + edition = "2024", + visibility = ["//visibility:public"], + deps = [ + "@rust_crates//:ufmt", + "@rust_crates//:zerocopy", + ], +) + +rust_test( + name = "types_test", + crate = ":types", +) diff --git a/util/types/fourcc.rs b/util/types/fourcc.rs new file mode 100644 index 00000000..dad7630a --- /dev/null +++ b/util/types/fourcc.rs @@ -0,0 +1,61 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +pub mod __private { + #[allow(unused_imports)] + pub use ufmt; +} + +/// A trait for `FourCC` identifiers. +/// This is unsafe to implement; you must be sure that the target type is 4 bytes in size and that +/// each byte contains a printable ascii character. +pub unsafe trait FourCC: Sized { + fn as_str(&self) -> &str { + unsafe { + let ptr = self as *const Self as *const u8; + let s = core::slice::from_raw_parts(ptr, 4); + core::str::from_utf8_unchecked(s) + } + } +} + +#[macro_export] +macro_rules! impl_fourcc { + ($t:ty) => { + const _: () = { + use $crate::fourcc::FourCC; + use $crate::fourcc::__private::ufmt; + + unsafe impl FourCC for $t {} + + impl ufmt::uDisplay for $t { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + let s = unsafe { + let ptr = self as *const Self as *const u8; + core::slice::from_raw_parts(ptr, 4) + }; + for byte in s { + if (0x20..0x7f).contains(byte) { + ufmt::uwrite!(f, "{}", *byte as char)?; + } else { + ufmt::uwrite!(f, "\\x{:02x}", *byte)?; + } + } + Ok(()) + } + } + + impl ufmt::uDebug for $t { + fn fmt(&self, f: &mut ufmt::Formatter<'_, W>) -> Result<(), W::Error> + where + W: ufmt::uWrite + ?Sized, + { + ufmt::uDisplay::fmt(self, f) + } + } + }; + }; +} diff --git a/util/types/lib.rs b/util/types/lib.rs new file mode 100644 index 00000000..362eb5ce --- /dev/null +++ b/util/types/lib.rs @@ -0,0 +1,15 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +#![no_std] + +pub mod fourcc; +mod opcode; +mod power_of_2; + +pub use opcode::Opcode; +pub use power_of_2::PowerOf2Usize; + +pub trait Blocking { + fn wait_for_notification(&self); +} diff --git a/util/types/opcode.rs b/util/types/opcode.rs new file mode 100644 index 00000000..688374f7 --- /dev/null +++ b/util/types/opcode.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use zerocopy::{FromBytes, Immutable, IntoBytes}; + +#[derive(Clone, Copy, PartialEq, Eq, FromBytes, IntoBytes, Immutable)] +pub struct Opcode(u32); + +impl Opcode { + pub const fn new(val: [u8; 4]) -> Self { + Opcode(u32::from_le_bytes(val)) + } +} diff --git a/util/types/power_of_2.rs b/util/types/power_of_2.rs new file mode 100644 index 00000000..d40849e5 --- /dev/null +++ b/util/types/power_of_2.rs @@ -0,0 +1,70 @@ +// Licensed under the Apache-2.0 license +// SPDX-License-Identifier: Apache-2.0 + +use core::hint::assert_unchecked; + +/// Represents a `usize`` that is guaranteed to be a power-of-two. The compiler +/// can take advantage of this fact when optimizing (for example, using bitwise +/// arithmetic instead of division). +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +pub struct PowerOf2Usize(usize); +impl PowerOf2Usize { + // WARNING: Do not add any functions or derives (such as + // zerocopy::FromBytes) that make it possible to modify this value without + // confirming that it is still a power-of-two. As the compiler is relying on + // the power-of-two assertion for safety, any such changes are unsound. + + #[inline(always)] + pub const fn new(val: usize) -> Option { + if !val.is_power_of_two() { + return None; + } + Some(Self(val)) + } + #[inline(always)] + pub const fn get(self) -> usize { + // SAFETY: These assertions are safe because self.0 can only be set by + // Self::new, and we check for the same preconditions there. + // (LLVM is too stupid to realize that is_power_of_two() implies != 0) + unsafe { assert_unchecked(self.0 != 0) }; + unsafe { assert_unchecked(self.0.is_power_of_two()) }; + self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::PowerOf2Usize; + + #[test] + pub fn test() { + assert_eq!(PowerOf2Usize::new(0), None); + assert_eq!(PowerOf2Usize::new(1).unwrap().get(), 1); + assert_eq!(PowerOf2Usize::new(2).unwrap().get(), 2); + assert_eq!(PowerOf2Usize::new(3), None); + assert_eq!(PowerOf2Usize::new(4).unwrap().get(), 4); + assert_eq!(PowerOf2Usize::new(5), None); + assert_eq!(PowerOf2Usize::new(6), None); + assert_eq!(PowerOf2Usize::new(7), None); + assert_eq!(PowerOf2Usize::new(8).unwrap().get(), 8); + assert_eq!(PowerOf2Usize::new(9), None); + assert_eq!(PowerOf2Usize::new(0x7fff_ffff), None); + assert_eq!(PowerOf2Usize::new(0x8000_0000).unwrap().get(), 0x8000_0000); + assert_eq!(PowerOf2Usize::new(0x8000_0001), None); + assert_eq!(PowerOf2Usize::new(0xc000_0000), None); + assert_eq!(PowerOf2Usize::new(0xffff_ffff), None); + + #[cfg(target_pointer_width = "64")] + { + assert_eq!(PowerOf2Usize::new(0x7fff_ffff_ffff_ffff), None); + assert_eq!( + PowerOf2Usize::new(0x8000_0000_0000_0000).unwrap().get(), + 0x8000_0000_0000_0000 + ); + assert_eq!(PowerOf2Usize::new(0x8000_0000_0000_0001), None); + assert_eq!(PowerOf2Usize::new(0xc000_0000_0000_0000), None); + assert_eq!(PowerOf2Usize::new(0xffff_ffff_ffff_ffff), None); + } + } +}