munge
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 69f28fe..a4bc21c 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock
@@ -1,5 +1,5 @@ { - "lockFileVersion": 18, + "lockFileVersion": 24, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -19,7 +19,8 @@ "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", "https://bcr.bazel.build/modules/apple_support/1.17.1/MODULE.bazel": "655c922ab1209978a94ef6ca7d9d43e940cd97d9c172fb55f94d91ac53f8610b", "https://bcr.bazel.build/modules/apple_support/1.23.0/MODULE.bazel": "317d47e3f65b580e7fb4221c160797fda48e32f07d2dfff63d754ef2316dcd25", - "https://bcr.bazel.build/modules/apple_support/1.23.0/source.json": "cb90a670c368cd37b5a7021486fd3f9a3fb0fcb8f45af43399137938d32ced76", + "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", + "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.31.2/MODULE.bazel": "7bee702b4862612f29333590f4b658a5832d433d6f8e4395f090e8f4e85d442f", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.38.0/MODULE.bazel": "6307fec451ba9962c1c969eb516ebfe1e46528f7fa92e1c9ac8646bef4cdaa3f", "https://bcr.bazel.build/modules/aspect_bazel_lib/1.40.3/MODULE.bazel": "668e6bcb4d957fc0e284316dba546b705c8d43c857f87119619ee83c4555b859", @@ -44,6 +45,7 @@ "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.32.0/MODULE.bazel": "095d67022a58cb20f7e20e1aefecfa65257a222c18a938e2914fd257b5f1ccdc", "https://bcr.bazel.build/modules/bazel_features/1.32.0/source.json": "2546c766986a6541f0bacd3e8542a1f621e2b14a80ea9e88c6f89f7eedf64ae1", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", @@ -165,6 +167,7 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", "https://bcr.bazel.build/modules/rules_cc/0.2.4/MODULE.bazel": "1ff1223dfd24f3ecf8f028446d4a27608aa43c3f41e346d22838a4223980b8cc", "https://bcr.bazel.build/modules/rules_cc/0.2.4/source.json": "2bd87ef9b41d4753eadf65175745737135cba0e70b479bdc204ef0c67404d0c4", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", @@ -193,11 +196,12 @@ "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", + "https://bcr.bazel.build/modules/rules_java/8.14.0/source.json": "8a88c4ca9e8759da53cddc88123880565c520503321e2566b4e33d0287a3d4bc", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.0/MODULE.bazel": "9c064c434606d75a086f15ade5edb514308cccd1544c2b2a89bbac4310e41c71", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", - "https://bcr.bazel.build/modules/rules_java/8.6.1/source.json": "f18d9ad3c4c54945bf422ad584fa6c5ca5b3116ff55a5b1bc77e5c1210be5960", "https://bcr.bazel.build/modules/rules_jni/0.11.0/MODULE.bazel": "f701405ceafc721bd6e1d6efee0094e941dc92e0652f73f431878a22aab0ba48", "https://bcr.bazel.build/modules/rules_jni/0.11.0/source.json": "368017b9548943ad80c741547c6912273e2d255b6d1c580a7afe60a1fef912bc", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", @@ -293,8 +297,8 @@ "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.2.13/MODULE.bazel": "aa6deb1b83c18ffecd940c4119aff9567cd0a671d7bba756741cb2ef043a29d5", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", - "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198", "https://bcr.bazel.build/modules/zstd/1.5.6/MODULE.bazel": "471ebe7d3cdd8c6469390fcf623eb4779ff55fbee0a87f1dc57a1def468b96d4", "https://bcr.bazel.build/modules/zstd/1.5.6/source.json": "02010c3333fc89b44fe861db049968decb6e688411f7f9d4f6791d74f9adfb51" @@ -317,36 +321,9 @@ "recordedRepoMappingEntries": [] } }, - "//third_party/hsm:extensions.bzl%hsm": { - "general": { - "bzlTransitiveDigest": "FNKStqvrQ6Eikdj24+vIUXQQ9yp7jfSDhlpg6gpl4EE=", - "usagesDigest": "7eja32+CLusTLtPh6EwF/x8FVeysZDXvIY7eEXbQJ2c=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "cloud_kms_hsm": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@//third_party/hsm:BUILD.cloud_kms_hsm.bazel", - "url": "https://github.com/GoogleCloudPlatform/kms-integrations/releases/download/pkcs11-v1.8/libkmsp11-1.8-linux-amd64.tar.gz", - "strip_prefix": "libkmsp11-1.8-linux-amd64", - "sha256": "3b932f22a8abb631442c3276e9c309554c81855526b74fbc9edaddcb57a557f7" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@aspect_bazel_lib+//lib:extensions.bzl%toolchains": { "general": { - "bzlTransitiveDigest": "QYzNsxOEydJ+3EvC2QMD9H0UjNzWD4r3AZ3bgke1PYY=", + "bzlTransitiveDigest": "UW8xG1Kwu5Xqz58UquEM4xIcKekzW/gjuYTnU97IFUw=", "usagesDigest": "OSDiT17Iojc2nI9CxYoJwyt0eQLLaRhbxOSwSp/RGCc=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -753,128 +730,9 @@ ] } }, - "@@aspect_rules_js+//npm:extensions.bzl%pnpm": { - "general": { - "bzlTransitiveDigest": "tmLEs2ngwNrJq1OgblYDQTIjF8TjHPE1t98jMTDqD1g=", - "usagesDigest": "tSjL49zyVxawlaiGFcrp3a28a6CEbLJE2VWEMr+2Ii0=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "pnpm": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_rule", - "attributes": { - "package": "pnpm", - "version": "8.6.7", - "root_package": "", - "link_workspace": "", - "link_packages": {}, - "integrity": "sha512-vRIWpD/L4phf9Bk2o/O2TDR8fFoJnpYrp2TKqTIZF/qZ2/rgL3qKXzHofHgbXsinwMoSEigz28sqk3pQ+yMEQQ==", - "url": "", - "commit": "", - "patch_args": [ - "-p0" - ], - "patches": [], - "custom_postinstall": "", - "npm_auth": "", - "npm_auth_basic": "", - "npm_auth_username": "", - "npm_auth_password": "", - "lifecycle_hooks": [], - "extra_build_content": "load(\"@aspect_rules_js//js:defs.bzl\", \"js_binary\")\njs_binary(name = \"pnpm\", data = glob([\"package/**\"]), entry_point = \"package/dist/pnpm.cjs\", visibility = [\"//visibility:public\"])", - "generate_bzl_library_targets": false, - "extract_full_archive": true, - "system_tar": "auto" - } - }, - "pnpm__links": { - "repoRuleId": "@@aspect_rules_js+//npm/private:npm_import.bzl%npm_import_links", - "attributes": { - "package": "pnpm", - "version": "8.6.7", - "dev": false, - "root_package": "", - "link_packages": {}, - "deps": {}, - "transitive_closure": {}, - "lifecycle_build_target": false, - "lifecycle_hooks_env": [], - "lifecycle_hooks_execution_requirements": [ - "no-sandbox" - ], - "lifecycle_hooks_use_default_shell_env": false, - "bins": {}, - "package_visibility": [ - "//visibility:public" - ], - "replace_package": "" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "aspect_bazel_lib+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "aspect_bazel_lib+", - "bazel_tools", - "bazel_tools" - ], - [ - "aspect_rules_js+", - "aspect_bazel_lib", - "aspect_bazel_lib+" - ], - [ - "aspect_rules_js+", - "bazel_features", - "bazel_features+" - ], - [ - "aspect_rules_js+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "aspect_rules_js+", - "bazel_tools", - "bazel_tools" - ], - [ - "bazel_features+", - "bazel_features_globals", - "bazel_features++version_extension+bazel_features_globals" - ], - [ - "bazel_features+", - "bazel_features_version", - "bazel_features++version_extension+bazel_features_version" - ] - ] - } - }, - "@@pigweed+//pw_build:pw_rust_crates_extension.bzl%pw_rust_crates_extension": { - "general": { - "bzlTransitiveDigest": "xYEcMiyyO+NfbdLyYLvKjnmEp4mBibFLOLVw8oRoV6k=", - "usagesDigest": "z7CtbK0kUgRAfLkVT5iDYRSzuwSWhsI8W9REdmbChhw=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "rust_crates": { - "repoRuleId": "@@pigweed+//pw_build:pw_rust_crates_extension.bzl%_crates_io_hub", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [] - } - }, "@@pigweed+//pw_toolchain/cc:pw_cxx_toolchain.bzl%pw_cxx_toolchain": { "general": { - "bzlTransitiveDigest": "Ra/Vtw43gGL83DZW8FsxeeY8A98P3aHzSmg4dX/QNNk=", + "bzlTransitiveDigest": "hciO8VUJTXRWfoaBvBdqqwBlHLoS7lHSff+HOQIyMd0=", "usagesDigest": "Xbpc0tIAtYpAPrZ66Usv8vtKskTRFdwZ23gL9rU1kGU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -916,7 +774,7 @@ }, "@@pigweed+//pw_toolchain/rust:extensions.bzl%pw_rust": { "general": { - "bzlTransitiveDigest": "pVUeogHygMYtC9swbEiXBrUm5winIz9hB4Th6ndT/Gk=", + "bzlTransitiveDigest": "xY4xQ8o+XnS/OvktxUguY5mBbB/1PfZuL8KGSvfF46A=", "usagesDigest": "SOxoNL6ar3yJlcO3rdvgKJ0EYI6s9GaDpJtfgBJeTNI=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1028,61 +886,6 @@ ] } }, - "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { - "general": { - "bzlTransitiveDigest": "yA4GkX1zdUTOXU4qKsh/YkjZKHAWkG8Fd7adNeGwiRc=", - "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", - "recordedFileInputs": { - "@@pybind11_bazel+//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" - }, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "pybind11": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", - "strip_prefix": "pybind11-2.12.0", - "urls": [ - "https://github.com/pybind/pybind11/archive/v2.12.0.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "pybind11_bazel+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_android+//bzlmod_extensions:apksig.bzl%apksig_extension": { - "general": { - "bzlTransitiveDigest": "BjalZzJZCCeFUitHa3nPXYW0o22VinHOSCBGaN0y87Q=", - "usagesDigest": "e4ckeOnzg06Fr7DT0gu4mzeX8LSpZuX/wUu21TSbi94=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "apksig": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz", - "build_file": "@@rules_android+//bzlmod_extensions:apksig.BUILD" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_android+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": { "general": { "bzlTransitiveDigest": "NAy+0M15JNVEBb8Tny6t7j3lKqTnsAMjoBB6LJ+C370=", @@ -1099,114 +902,9 @@ "recordedRepoMappingEntries": [] } }, - "@@rules_fuzzing+//fuzzing/private:extensions.bzl%non_module_dependencies": { - "general": { - "bzlTransitiveDigest": "mGiTB79hRNjmeDTQdzkpCHyzXhErMbufeAmySBt7s5s=", - "usagesDigest": "T13X7WXnVd863yIkg/0xDmftJ/CetmPbmrkGVQ99gkE=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "platforms": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz", - "https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz" - ], - "sha256": "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74" - } - }, - "rules_python": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "d70cd72a7a4880f0000a6346253414825c19cdd40a28289bdf67b8e6480edff8", - "strip_prefix": "rules_python-0.28.0", - "url": "https://github.com/bazelbuild/rules_python/releases/download/0.28.0/rules_python-0.28.0.tar.gz" - } - }, - "bazel_skylib": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "sha256": "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", - "urls": [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz" - ] - } - }, - "com_google_absl": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240116.1.zip" - ], - "strip_prefix": "abseil-cpp-20240116.1", - "integrity": "sha256-7capMWOvWyoYbUaHF/b+I2U6XLMaHmky8KugWvfXYuk=" - } - }, - "rules_fuzzing_oss_fuzz": { - "repoRuleId": "@@rules_fuzzing+//fuzzing/private/oss_fuzz:repository.bzl%oss_fuzz_repository", - "attributes": {} - }, - "honggfuzz": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@rules_fuzzing+//:honggfuzz.BUILD", - "sha256": "6b18ba13bc1f36b7b950c72d80f19ea67fbadc0ac0bb297ec89ad91f2eaa423e", - "url": "https://github.com/google/honggfuzz/archive/2.5.zip", - "strip_prefix": "honggfuzz-2.5" - } - }, - "rules_fuzzing_jazzer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "ee6feb569d88962d59cb59e8a31eb9d007c82683f3ebc64955fd5b96f277eec2", - "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer/0.20.1/jazzer-0.20.1.jar" - } - }, - "rules_fuzzing_jazzer_api": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_jar", - "attributes": { - "sha256": "f5a60242bc408f7fa20fccf10d6c5c5ea1fcb3c6f44642fec5af88373ae7aa1b", - "url": "https://repo1.maven.org/maven2/com/code-intelligence/jazzer-api/0.20.1/jazzer-api-0.20.1.jar" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_fuzzing+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_java+//java:rules_java_deps.bzl%compatibility_proxy": { - "general": { - "bzlTransitiveDigest": "84xJEZ1jnXXwo8BXMprvBm++rRt4jsTu9liBxz0ivps=", - "usagesDigest": "jTQDdLDxsS43zuRmg1faAjIEPWdLAbDAowI1pInQSoo=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "compatibility_proxy": { - "repoRuleId": "@@rules_java+//java:rules_java_deps.bzl%_compatibility_proxy_repo_rule", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_java+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", + "bzlTransitiveDigest": "rL/34P1aFDq2GqVC2zCFgQ8nTuOC6ziogocpvG50Qz8=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1268,35 +966,6 @@ ] } }, - "@@rules_libusb+//:extensions.bzl%libusb": { - "general": { - "bzlTransitiveDigest": "q8w6UeaTkAyBBb3iDZ6OxV87Iq/zovx8RrRjEpxxqao=", - "usagesDigest": "mS8IemUWNRRKkBagACH+EOS/mM9aBoJISPKaD2aTfbI=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "libusb": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/libusb/libusb/releases/download/v1.0.27/libusb-1.0.27.tar.bz2" - ], - "build_file": "@@rules_libusb+//:libusb.BUILD", - "strip_prefix": "libusb-1.0.27", - "sha256": "ffaa41d741a8a3bee244ac8e54a72ea05bf2879663c098c82fc5757853441575" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_libusb+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, "@@rules_nodejs+//nodejs:extensions.bzl%node": { "general": { "bzlTransitiveDigest": "SqbzUarOVzAfK28Ca5+NIU3LUwnW/b3h0xXBUS97oyI=", @@ -1418,131 +1087,9 @@ "recordedRepoMappingEntries": [] } }, - "@@rules_probe_rs+//probe_rs:extensions.bzl%probe_rs": { - "general": { - "bzlTransitiveDigest": "qACxDYMmZFnJ5z3DgFxaM5vcM6BluXK+tGm6QjKzbo0=", - "usagesDigest": "UCZMqqV7nRwSIgEQDhXaj9l7SOxiqzZuKi0lVFV0DE0=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "probe_rs": { - "repoRuleId": "@@rules_probe_rs+//probe_rs/private:repositories.bzl%probe_rs_repo", - "attributes": { - "tools": [ - "probe-rs" - ], - "platforms": { - "probe_rs_tools_x86_64_unknown_linux_gnu": [ - "@platforms//os:linux", - "@platforms//cpu:x86_64" - ], - "probe_rs_tools_aarch64_unknown_linux_gnu": [ - "@platforms//os:linux", - "@platforms//cpu:arm64" - ], - "probe_rs_tools_x86_64_apple_darwin": [ - "@platforms//os:macos", - "@platforms//cpu:x86_64" - ], - "probe_rs_tools_aarch64_apple_darwin": [ - "@platforms//os:macos", - "@platforms//cpu:arm64" - ], - "probe_rs_tools_x86_64_pc_windows_msvc": [ - "@platforms//os:windows", - "@platforms//cpu:x86_64" - ] - } - } - }, - "probe_rs_tools_x86_64_unknown_linux_gnu": { - "repoRuleId": "@@rules_probe_rs+//probe_rs/private:repositories.bzl%probe_rs_tools_archive_repo", - "attributes": { - "tools": [ - "probe-rs" - ], - "url": "https://github.com/probe-rs/probe-rs/releases/download/v0.27.0/probe-rs-tools-x86_64-unknown-linux-gnu.tar.xz", - "sha256": "314c40db7ded9ae5622c6af8fe5149bcf5c507d7d6f5b7a415d6acf8d2005cb0", - "strip_prefix": "probe-rs-tools-x86_64-unknown-linux-gnu", - "exec_compatible_with": [ - "@platforms//os:linux", - "@platforms//cpu:x86_64" - ] - } - }, - "probe_rs_tools_aarch64_unknown_linux_gnu": { - "repoRuleId": "@@rules_probe_rs+//probe_rs/private:repositories.bzl%probe_rs_tools_archive_repo", - "attributes": { - "tools": [ - "probe-rs" - ], - "url": "https://github.com/probe-rs/probe-rs/releases/download/v0.27.0/probe-rs-tools-aarch64-unknown-linux-gnu.tar.xz", - "sha256": "7aed7af58c9e267b7145490de61d0147033fadb0a70313fe7dbd363bfd98a728", - "strip_prefix": "probe-rs-tools-aarch64-unknown-linux-gnu", - "exec_compatible_with": [ - "@platforms//os:linux", - "@platforms//cpu:arm64" - ] - } - }, - "probe_rs_tools_x86_64_apple_darwin": { - "repoRuleId": "@@rules_probe_rs+//probe_rs/private:repositories.bzl%probe_rs_tools_archive_repo", - "attributes": { - "tools": [ - "probe-rs" - ], - "url": "https://github.com/probe-rs/probe-rs/releases/download/v0.27.0/probe-rs-tools-x86_64-apple-darwin.tar.xz", - "sha256": "c00a87173008b8279c8d2669e90ce878629abb95ceaa003099adb68d62f3fafb", - "strip_prefix": "probe-rs-tools-x86_64-apple-darwin", - "exec_compatible_with": [ - "@platforms//os:macos", - "@platforms//cpu:x86_64" - ] - } - }, - "probe_rs_tools_aarch64_apple_darwin": { - "repoRuleId": "@@rules_probe_rs+//probe_rs/private:repositories.bzl%probe_rs_tools_archive_repo", - "attributes": { - "tools": [ - "probe-rs" - ], - "url": "https://github.com/probe-rs/probe-rs/releases/download/v0.27.0/probe-rs-tools-aarch64-apple-darwin.tar.xz", - "sha256": "7b1ad0d61f6355a9f095e4d06a5e6149ad9824f4725d4976104486941d652d5c", - "strip_prefix": "probe-rs-tools-aarch64-apple-darwin", - "exec_compatible_with": [ - "@platforms//os:macos", - "@platforms//cpu:arm64" - ] - } - }, - "probe_rs_tools_x86_64_pc_windows_msvc": { - "repoRuleId": "@@rules_probe_rs+//probe_rs/private:repositories.bzl%probe_rs_tools_archive_repo", - "attributes": { - "tools": [ - "probe-rs" - ], - "url": "https://github.com/probe-rs/probe-rs/releases/download/v0.27.0/probe-rs-tools-x86_64-pc-windows-msvc.zip", - "sha256": "88a80e304caf706235c5fd2c75e8c2f6280ff86472348fc3bef19eadb9e47b8c", - "exec_compatible_with": [ - "@platforms//os:windows", - "@platforms//cpu:x86_64" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_probe_rs+", - "rules_probe_rs", - "rules_probe_rs+" - ] - ] - } - }, "@@rules_python+//python/uv:uv.bzl%uv": { "general": { - "bzlTransitiveDigest": "477hS4MXeJ7LqPNLTqL+1ltraV5lqwOw3tEXWqnJRt8=", + "bzlTransitiveDigest": "8vT1ddXtljNxYD0tJkksqzeKE6xqx4Ix+tXthAppjTI=", "usagesDigest": "icnInV8HDGrRQf9x8RMfxWfBHgT3OgRlYovS/9POEJw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -1583,12 +1130,12 @@ }, "@@rules_rust+//crate_universe:extension.bzl%crate": { "general": { - "bzlTransitiveDigest": "R3uUM8wZ6+Ue81AfVYJjzZJDkn6UdJu2gq59GrtQIY0=", + "bzlTransitiveDigest": "ip4D12Lz5jyA9AqXvt5BGvSeGwmBkz0xVNNhO0ywVSw=", "usagesDigest": "WfdfJaIbRmjr4fMHKZY9ihDeOMFR/bzh5E/2qr531WI=", "recordedFileInputs": { "@@//third_party/crates_io/Cargo.lock": "7b0c74ed1c866c9bc1a67b6809dac1b18014ad3907c0feab15b46bd4f8bc7d47", "@@//third_party/crates_io/Cargo.toml": "9a5623aec342fc0a155822290970b5051ffa281843a4f66c54ebc4fe91647c8f", - "@@//third_party/crates_io/crates_no_std/Cargo.lock": "f6378898fa08add3632613c54241edf68787789c59ea9ccccb051f6f47ab68bc", + "@@//third_party/crates_io/crates_no_std/Cargo.lock": "aa20e23f9aebaf59a30831fa5cb56bce46741b36a02e795b3cc09cff3cb5d372", "@@//third_party/crates_io/crates_no_std/Cargo.toml": "b7d1554800404f815e933bf28da4815b25ca6e86d5f79fcb11f5422ccdffc3a8", "@@pigweed+//third_party/crates_io/crates_no_std/Cargo.lock": "d38da5fa5f942b59a8017730285afe69aaf6f741b6171f361d66202de3473bc2", "@@pigweed+//third_party/crates_io/crates_no_std/Cargo.toml": "00e4a621a4a49d1b269bb2e688d8b5bbabb8700eb7b583ccaa20c36a60aebf98", @@ -8982,10 +8529,6 @@ } } }, - "moduleExtensionMetadata": { - "useAllRepos": "NO", - "reproducible": false - }, "recordedRepoMappingEntries": [ [ "bazel_features+", @@ -9034,148 +8577,7 @@ ] ] } - }, - "@@rules_rust+//crate_universe/private:internal_extensions.bzl%cu_nr": { - "general": { - "bzlTransitiveDigest": "lIBKOfm2y3ftMR7jZfrHt+wWa5enJJaJMb8vfpIQV94=", - "usagesDigest": "h9wqdPgSnpDxdRNP6kO+DMoTuAWtxtek2SnJOOL321c=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "cargo_bazel_bootstrap": { - "repoRuleId": "@@rules_rust+//cargo/private:cargo_bootstrap.bzl%cargo_bootstrap_repository", - "attributes": { - "srcs": [ - "@@rules_rust+//crate_universe:src/api.rs", - "@@rules_rust+//crate_universe:src/api/lockfile.rs", - "@@rules_rust+//crate_universe:src/cli.rs", - "@@rules_rust+//crate_universe:src/cli/generate.rs", - "@@rules_rust+//crate_universe:src/cli/query.rs", - "@@rules_rust+//crate_universe:src/cli/render.rs", - "@@rules_rust+//crate_universe:src/cli/splice.rs", - "@@rules_rust+//crate_universe:src/cli/vendor.rs", - "@@rules_rust+//crate_universe:src/config.rs", - "@@rules_rust+//crate_universe:src/context.rs", - "@@rules_rust+//crate_universe:src/context/crate_context.rs", - "@@rules_rust+//crate_universe:src/context/platforms.rs", - "@@rules_rust+//crate_universe:src/lib.rs", - "@@rules_rust+//crate_universe:src/lockfile.rs", - "@@rules_rust+//crate_universe:src/main.rs", - "@@rules_rust+//crate_universe:src/metadata.rs", - "@@rules_rust+//crate_universe:src/metadata/cargo_bin.rs", - "@@rules_rust+//crate_universe:src/metadata/cargo_tree_resolver.rs", - "@@rules_rust+//crate_universe:src/metadata/cargo_tree_rustc_wrapper.bat", - "@@rules_rust+//crate_universe:src/metadata/cargo_tree_rustc_wrapper.sh", - "@@rules_rust+//crate_universe:src/metadata/dependency.rs", - "@@rules_rust+//crate_universe:src/metadata/metadata_annotation.rs", - "@@rules_rust+//crate_universe:src/rendering.rs", - "@@rules_rust+//crate_universe:src/rendering/template_engine.rs", - "@@rules_rust+//crate_universe:src/rendering/templates/module_bzl.j2", - "@@rules_rust+//crate_universe:src/rendering/templates/partials/header.j2", - "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/aliases_map.j2", - "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/deps_map.j2", - "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/repo_git.j2", - "@@rules_rust+//crate_universe:src/rendering/templates/partials/module/repo_http.j2", - "@@rules_rust+//crate_universe:src/rendering/templates/vendor_module.j2", - "@@rules_rust+//crate_universe:src/rendering/verbatim/alias_rules.bzl", - "@@rules_rust+//crate_universe:src/select.rs", - "@@rules_rust+//crate_universe:src/splicing.rs", - "@@rules_rust+//crate_universe:src/splicing/cargo_config.rs", - "@@rules_rust+//crate_universe:src/splicing/crate_index_lookup.rs", - "@@rules_rust+//crate_universe:src/splicing/splicer.rs", - "@@rules_rust+//crate_universe:src/test.rs", - "@@rules_rust+//crate_universe:src/utils.rs", - "@@rules_rust+//crate_universe:src/utils/starlark.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/glob.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/label.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/select.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/select_dict.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/select_list.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/select_scalar.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/select_set.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/serialize.rs", - "@@rules_rust+//crate_universe:src/utils/starlark/target_compatible_with.rs", - "@@rules_rust+//crate_universe:src/utils/symlink.rs", - "@@rules_rust+//crate_universe:src/utils/target_triple.rs" - ], - "binary": "cargo-bazel", - "cargo_lockfile": "@@rules_rust+//crate_universe:Cargo.lock", - "cargo_toml": "@@rules_rust+//crate_universe:Cargo.toml", - "version": "1.86.0", - "timeout": 900, - "rust_toolchain_cargo_template": "@rust_host_tools//:bin/{tool}", - "rust_toolchain_rustc_template": "@rust_host_tools//:bin/{tool}", - "compressed_windows_toolchain_names": false - } - } - }, - "moduleExtensionMetadata": { - "explicitRootModuleDirectDeps": [ - "cargo_bazel_bootstrap" - ], - "explicitRootModuleDirectDevDeps": [], - "useAllRepos": "NO", - "reproducible": false - }, - "recordedRepoMappingEntries": [ - [ - "bazel_features+", - "bazel_features_globals", - "bazel_features++version_extension+bazel_features_globals" - ], - [ - "bazel_features+", - "bazel_features_version", - "bazel_features++version_extension+bazel_features_version" - ], - [ - "rules_cc+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_cc+", - "rules_cc", - "rules_cc+" - ], - [ - "rules_rust+", - "bazel_features", - "bazel_features+" - ], - [ - "rules_rust+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "rules_rust+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_rust+", - "cui", - "rules_rust++cu+cui" - ], - [ - "rules_rust+", - "rules_cc", - "rules_cc+" - ], - [ - "rules_rust+", - "rules_rust", - "rules_rust+" - ], - [ - "rules_rust+", - "rules_rust_ctve", - "rules_rust++i2+rules_rust_ctve" - ] - ] - } } - } + }, + "facts": {} }
diff --git a/apps/i2c-slave-echo/BUILD.bazel b/apps/i2c-slave-echo/BUILD.bazel new file mode 100644 index 0000000..7930ef1 --- /dev/null +++ b/apps/i2c-slave-echo/BUILD.bazel
@@ -0,0 +1,28 @@ +# Licensed under the Apache-2.0 license + +load("@pigweed//pw_kernel/tooling:app_package.bzl", "app_package") +load("@rules_rust//rust:defs.bzl", "rust_binary") + +rust_binary( + name = "i2c_slave_echo", + srcs = ["src/main.rs"], + edition = "2024", + tags = ["kernel"], + visibility = ["//visibility:public"], + deps = [ + ":app_i2c_slave_echo", + "//services/i2c/api:i2c_api", + "//services/i2c/client:i2c_client", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + "@pigweed//pw_status/rust:pw_status", + ], +) + +app_package( + name = "app_i2c_slave_echo", + app_name = "i2c_slave_echo", + edition = "2024", + system_config = "//target/ast1060-evb/i2c-slave:system_config", + tags = ["kernel"], +)
diff --git a/apps/i2c-slave-echo/src/main.rs b/apps/i2c-slave-echo/src/main.rs new file mode 100644 index 0000000..fd96ec1 --- /dev/null +++ b/apps/i2c-slave-echo/src/main.rs
@@ -0,0 +1,137 @@ +// Licensed under the Apache-2.0 license + +//! I2C Slave Echo Application +//! +//! Sits on I2C2 at address 0x42 and implements a simple register model: +//! +//! - **Write** `[reg, value]` — stores `value` at `reg` and prints the access. +//! - **Write** `[reg]` followed by **Read** — returns the stored value for `reg` +//! and prints the access. +//! +//! # Protocol +//! +//! ```text +//! Master writes [reg, val] → slave stores reg_map[reg] = val +//! prints: "WRITE reg=0xRR val=0xVV" +//! +//! Master writes [reg] → slave sets read pointer to reg +//! Master reads N bytes → slave returns reg_map[reg..reg+N] +//! prints: "READ reg=0xRR val=0xVV" +//! ``` +//! +//! # Hardware +//! +//! The AST1060 I2C2 controller is used in simultaneous master+slave mode: +//! master operations use the I2CM* register set, slave operations use I2CS*. + +#![no_main] +#![no_std] + +use app_i2c_slave_echo::handle; +use i2c_api::{BusIndex, I2cAddress, I2cTargetClient, SlaveEventKind}; +use i2c_client::IpcI2cClient; +use pw_status::Result; +use userspace::entry; + +/// I2C bus index (I2C2). +const SLAVE_BUS: BusIndex = BusIndex::BUS_2; + +/// Slave address. +const SLAVE_ADDR: u8 = 0x42; + +/// Number of registers in the echo register map. +const REG_MAP_SIZE: usize = 256; + +fn slave_echo_loop() -> Result<()> { + let mut client = IpcI2cClient::new(handle::I2C); + + let addr = I2cAddress::new(SLAVE_ADDR).map_err(|_| pw_status::Error::InvalidArgument)?; + + // Configure and enable slave mode. + client + .configure_target_address(SLAVE_BUS, addr) + .map_err(|_| pw_status::Error::Internal)?; + client + .enable_receive(SLAVE_BUS) + .map_err(|_| pw_status::Error::Internal)?; + + pw_log::info!( + "I2C slave echo running on bus {} at address 0x{:02x}", + SLAVE_BUS.value() as u32, + SLAVE_ADDR as u32, + ); + + // Register map — all bytes initialised to zero. + let mut reg_map = [0u8; REG_MAP_SIZE]; + // Current register pointer (set by a 1-byte write, used for reads). + let mut reg_ptr: u8 = 0; + + // Pre-load the initial read response. + let _ = client.slave_set_response(SLAVE_BUS, &[reg_map[reg_ptr as usize]]); + + let mut rx_buf = [0u8; 32]; + + loop { + match client.slave_wait_event(SLAVE_BUS, &mut rx_buf) { + Ok((SlaveEventKind::DataReceived, n)) => { + match n { + 0 => { + // Zero-length write — ignore (e.g. probe). + } + 1 => { + // Register pointer set; update read response. + reg_ptr = rx_buf[0]; + let val = reg_map[reg_ptr as usize]; + // pw_log::info!( + // "READ reg=0x{:02x} val=0x{:02x}", + // reg_ptr as u32, + // val as u32, + // ); + let _ = client.slave_set_response(SLAVE_BUS, &[val]); + } + _ => { + // Write: byte 0 = register address, byte 1 = value. + reg_ptr = rx_buf[0]; + let val = rx_buf[1]; + reg_map[reg_ptr as usize] = val; + // pw_log::info!( + // "WRITE reg=0x{:02x} val=0x{:02x}", + // reg_ptr as u32, + // val as u32, + // ); + // Update read response in case master reads back immediately. + let _ = client.slave_set_response(SLAVE_BUS, &[val]); + } + } + } + Ok((SlaveEventKind::ReadRequest, _)) => { + // Master read our pre-loaded response; log it. + let val = reg_map[reg_ptr as usize]; + // pw_log::info!( + // "READ reg=0x{:02x} val=0x{:02x}", + // reg_ptr as u32, + // val as u32, + // ); + let _ = val; + } + Ok((SlaveEventKind::Stop, _)) => { + // Stop condition — nothing to do. + } + Err(_) => { + // Timeout or error — keep looping. + } + } + } +} + +#[entry] +fn entry() -> ! { + pw_log::info!("I2C slave echo starting"); + let _ = slave_echo_loop(); + loop {} +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +}
diff --git a/services/i2c/api/src/lib.rs b/services/i2c/api/src/lib.rs index 62834c3..8164d11 100644 --- a/services/i2c/api/src/lib.rs +++ b/services/i2c/api/src/lib.rs
@@ -98,4 +98,4 @@ pub use operation::Operation; // Re-export target mode types -pub use target::{I2cTargetClient, TargetMessage, TARGET_MESSAGE_MAX_LEN}; +pub use target::{I2cTargetClient, SlaveEventKind, TargetMessage, TARGET_MESSAGE_MAX_LEN};
diff --git a/services/i2c/api/src/target.rs b/services/i2c/api/src/target.rs index e44b2d1..ef04777 100644 --- a/services/i2c/api/src/target.rs +++ b/services/i2c/api/src/target.rs
@@ -122,6 +122,30 @@ ) -> Result<usize, Self::Error>; } +/// The type of event reported by a slave wait operation. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum SlaveEventKind { + /// Master wrote data to us. + DataReceived = 0, + /// Master requested to read from us (TX data was sent). + ReadRequest = 1, + /// Stop condition received. + Stop = 2, +} + +impl SlaveEventKind { + /// Decode from a raw byte. + pub fn from_u8(val: u8) -> Option<Self> { + match val { + 0 => Some(Self::DataReceived), + 1 => Some(Self::ReadRequest), + 2 => Some(Self::Stop), + _ => None, + } + } +} + /// Maximum size of a target message payload. pub const TARGET_MESSAGE_MAX_LEN: usize = 255;
diff --git a/services/i2c/api/src/wire.rs b/services/i2c/api/src/wire.rs index f993660..2e8161f 100644 --- a/services/i2c/api/src/wire.rs +++ b/services/i2c/api/src/wire.rs
@@ -62,6 +62,18 @@ ConfigureSpeed = 5, /// Bus recovery RecoverBus = 6, + /// Configure slave address on a bus + ConfigureSlave = 7, + /// Enable slave receive mode + EnableSlave = 8, + /// Disable slave receive mode + DisableSlave = 9, + /// Blocking poll for received slave data + SlaveReceive = 10, + /// Block until next slave event; returns event kind + any received data + SlaveWaitEvent = 11, + /// Pre-load TX buffer with data to send on next master read + SlaveSetResponse = 12, } impl I2cOp { @@ -75,6 +87,12 @@ 4 => Some(Self::Probe), 5 => Some(Self::ConfigureSpeed), 6 => Some(Self::RecoverBus), + 7 => Some(Self::ConfigureSlave), + 8 => Some(Self::EnableSlave), + 9 => Some(Self::DisableSlave), + 10 => Some(Self::SlaveReceive), + 11 => Some(Self::SlaveWaitEvent), + 12 => Some(Self::SlaveSetResponse), _ => None, } } @@ -160,6 +178,79 @@ } } + /// Create a new ConfigureSlave request header + pub const fn configure_slave(bus: u8, address: u8) -> Self { + Self { + op: I2cOp::ConfigureSlave as u8, + bus, + address, + write_len: 0, + read_len: 0, + } + } + + /// Create a new EnableSlave request header + pub const fn enable_slave(bus: u8) -> Self { + Self { + op: I2cOp::EnableSlave as u8, + bus, + address: 0, + write_len: 0, + read_len: 0, + } + } + + /// Create a new DisableSlave request header + pub const fn disable_slave(bus: u8) -> Self { + Self { + op: I2cOp::DisableSlave as u8, + bus, + address: 0, + write_len: 0, + read_len: 0, + } + } + + /// Create a new SlaveReceive request header + /// + /// `max_len` is the maximum number of bytes the caller can accept. + pub const fn slave_receive(bus: u8, max_len: u16) -> Self { + Self { + op: I2cOp::SlaveReceive as u8, + bus, + address: 0, + write_len: 0, + read_len: max_len, + } + } + + /// Create a new SlaveWaitEvent request header + /// + /// `max_rx_len` is the maximum number of received data bytes the caller + /// can accept (not counting the leading event-kind byte). + pub const fn slave_wait_event(bus: u8, max_rx_len: u16) -> Self { + Self { + op: I2cOp::SlaveWaitEvent as u8, + bus, + address: 0, + write_len: 0, + read_len: max_rx_len, + } + } + + /// Create a new SlaveSetResponse request header + /// + /// `data_len` is the number of bytes in the following payload. + pub const fn slave_set_response(bus: u8, data_len: u16) -> Self { + Self { + op: I2cOp::SlaveSetResponse as u8, + bus, + address: 0, + write_len: data_len, + read_len: 0, + } + } + /// Encode header to bytes (little-endian) pub fn to_bytes(&self) -> [u8; Self::SIZE] { let write_le = self.write_len.to_le_bytes(); @@ -362,6 +453,106 @@ Ok(I2cRequestHeader::SIZE) } +/// Encode a configure-slave request into a buffer +/// +/// # Errors +/// - `WireError::BufferTooSmall` if buffer cannot hold header +pub fn encode_configure_slave_request(buf: &mut [u8], bus: u8, address: u8) -> Result<usize, WireError> { + if buf.len() < I2cRequestHeader::SIZE { + return Err(WireError::BufferTooSmall); + } + let header = I2cRequestHeader::configure_slave(bus, address); + buf[..I2cRequestHeader::SIZE].copy_from_slice(&header.to_bytes()); + Ok(I2cRequestHeader::SIZE) +} + +/// Encode an enable-slave request into a buffer +/// +/// # Errors +/// - `WireError::BufferTooSmall` if buffer cannot hold header +pub fn encode_enable_slave_request(buf: &mut [u8], bus: u8) -> Result<usize, WireError> { + if buf.len() < I2cRequestHeader::SIZE { + return Err(WireError::BufferTooSmall); + } + let header = I2cRequestHeader::enable_slave(bus); + buf[..I2cRequestHeader::SIZE].copy_from_slice(&header.to_bytes()); + Ok(I2cRequestHeader::SIZE) +} + +/// Encode a disable-slave request into a buffer +/// +/// # Errors +/// - `WireError::BufferTooSmall` if buffer cannot hold header +pub fn encode_disable_slave_request(buf: &mut [u8], bus: u8) -> Result<usize, WireError> { + if buf.len() < I2cRequestHeader::SIZE { + return Err(WireError::BufferTooSmall); + } + let header = I2cRequestHeader::disable_slave(bus); + buf[..I2cRequestHeader::SIZE].copy_from_slice(&header.to_bytes()); + Ok(I2cRequestHeader::SIZE) +} + +/// Encode a slave-receive request into a buffer +/// +/// `max_len` is the maximum number of bytes the caller can accept in the response. +/// +/// # Errors +/// - `WireError::PayloadTooLarge` if max_len exceeds MAX_PAYLOAD_SIZE +/// - `WireError::BufferTooSmall` if buffer cannot hold header +pub fn encode_slave_receive_request(buf: &mut [u8], bus: u8, max_len: u16) -> Result<usize, WireError> { + if max_len as usize > MAX_PAYLOAD_SIZE { + return Err(WireError::PayloadTooLarge); + } + if buf.len() < I2cRequestHeader::SIZE { + return Err(WireError::BufferTooSmall); + } + let header = I2cRequestHeader::slave_receive(bus, max_len); + buf[..I2cRequestHeader::SIZE].copy_from_slice(&header.to_bytes()); + Ok(I2cRequestHeader::SIZE) +} + +/// Encode a slave-wait-event request into a buffer. +/// +/// `max_rx_len` is the maximum received data bytes the caller accepts (not +/// counting the leading event-kind byte in the response payload). +/// +/// # Errors +/// - `WireError::PayloadTooLarge` if max_rx_len exceeds MAX_PAYLOAD_SIZE - 1 +/// - `WireError::BufferTooSmall` if buffer cannot hold header +pub fn encode_slave_wait_event_request(buf: &mut [u8], bus: u8, max_rx_len: u16) -> Result<usize, WireError> { + if max_rx_len as usize >= MAX_PAYLOAD_SIZE { + return Err(WireError::PayloadTooLarge); + } + if buf.len() < I2cRequestHeader::SIZE { + return Err(WireError::BufferTooSmall); + } + let header = I2cRequestHeader::slave_wait_event(bus, max_rx_len); + buf[..I2cRequestHeader::SIZE].copy_from_slice(&header.to_bytes()); + Ok(I2cRequestHeader::SIZE) +} + +/// Encode a slave-set-response request into a buffer. +/// +/// The `data` slice is the TX payload the slave will send on the next master read. +/// +/// # Errors +/// - `WireError::PayloadTooLarge` if data exceeds MAX_PAYLOAD_SIZE +/// - `WireError::BufferTooSmall` if buffer cannot hold header + data +pub fn encode_slave_set_response_request(buf: &mut [u8], bus: u8, data: &[u8]) -> Result<usize, WireError> { + if data.len() > MAX_PAYLOAD_SIZE { + return Err(WireError::PayloadTooLarge); + } + let data_len = u16::try_from(data.len()).map_err(|_| WireError::PayloadTooLarge)?; + let total_len = I2cRequestHeader::SIZE + data.len(); + if buf.len() < total_len { + return Err(WireError::BufferTooSmall); + } + let header = I2cRequestHeader::slave_set_response(bus, data_len); + buf[..I2cRequestHeader::SIZE].copy_from_slice(&header.to_bytes()); + buf[I2cRequestHeader::SIZE..total_len].copy_from_slice(data); + Ok(total_len) +} + // ============================================================================ // Decoding Helpers // ============================================================================
diff --git a/services/i2c/backend-aspeed/src/lib.rs b/services/i2c/backend-aspeed/src/lib.rs index 32587f7..86a4343 100644 --- a/services/i2c/backend-aspeed/src/lib.rs +++ b/services/i2c/backend-aspeed/src/lib.rs
@@ -39,8 +39,8 @@ #![no_std] -use aspeed_ddk::i2c_core::{Ast1060I2c, Controller as DdkController, I2cConfig, I2cError}; -use i2c_api::ResponseCode; +use aspeed_ddk::i2c_core::{Ast1060I2c, Controller as DdkController, I2cConfig, I2cError, SlaveConfig, SlaveEvent}; +use i2c_api::{ResponseCode, SlaveEventKind}; use pw_log; @@ -86,10 +86,19 @@ /// The backend exclusively owns `ast1060_pac::Peripherals`, ensuring no /// aliased register access. The `unsafe` is confined to [`new()`] where /// `Peripherals::steal()` is called. +/// Maximum TX buffer size per bus (matches hardware buffer size). +const SLAVE_TX_BUF_SIZE: usize = 32; + pub struct AspeedI2cBackend { peripherals: ast1060_pac::Peripherals, /// Tracks which buses have been initialized via `init_bus()`. initialized: u16, + /// Tracks which buses have slave mode configured via `configure_slave()`. + slave_configured: u16, + /// Pre-loaded TX data to send when master reads from us (one buffer per bus). + slave_tx_bufs: [[u8; SLAVE_TX_BUF_SIZE]; 14], + /// Valid byte count in each `slave_tx_bufs` slot. + slave_tx_lens: [usize; 14], } impl AspeedI2cBackend { @@ -105,6 +114,9 @@ Self { peripherals: unsafe { ast1060_pac::Peripherals::steal() }, initialized: 0, + slave_configured: 0, + slave_tx_bufs: [[0u8; SLAVE_TX_BUF_SIZE]; 14], + slave_tx_lens: [0usize; 14], } } @@ -258,4 +270,194 @@ let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::with_static_clocks()); i2c.recover_bus().map_err(map_i2c_error) } + + // ----------------------------------------------------------------------- + // Slave operations + // ----------------------------------------------------------------------- + + /// Configure a bus as an I2C slave at the given 7-bit address. + /// + /// The bus must have been initialized via [`init_bus()`] first. + /// After configuration, call [`enable_slave()`] to start receiving. + pub fn configure_slave(&mut self, bus: u8, addr: u8) -> Result<(), ResponseCode> { + if !self.is_bus_initialized(bus) { + return Err(ResponseCode::ServerError); + } + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + let config = SlaveConfig::new(addr).map_err(map_i2c_error)?; + i2c.configure_slave(&config).map_err(map_i2c_error)?; + self.slave_configured |= 1 << bus; + pw_log::info!("I2C bus {} slave configured at address 0x{:02x}", bus as u8, addr as u8); + Ok(()) + } + + /// Enable slave receive mode on a previously configured bus. + pub fn enable_slave(&mut self, bus: u8) -> Result<(), ResponseCode> { + if !self.is_bus_initialized(bus) { + return Err(ResponseCode::ServerError); + } + if (self.slave_configured & (1 << bus)) == 0 { + return Err(ResponseCode::NotInitialized); + } + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + i2c.enable_slave(); + pw_log::info!("I2C bus {} slave enabled", bus as u8); + Ok(()) + } + + /// Disable slave receive mode on a bus. + pub fn disable_slave(&mut self, bus: u8) -> Result<(), ResponseCode> { + if !self.is_bus_initialized(bus) { + return Err(ResponseCode::ServerError); + } + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + i2c.disable_slave(); + pw_log::info!("I2C bus {} slave disabled", bus as u8); + Ok(()) + } + + /// Pre-load the TX buffer for a bus. + /// + /// Data stored here will be sent to the master when it issues a read + /// transaction to our slave address, as part of `slave_wait_event`. + pub fn slave_set_response(&mut self, bus: u8, data: &[u8]) -> Result<(), ResponseCode> { + if !self.is_bus_initialized(bus) { + return Err(ResponseCode::ServerError); + } + let len = data.len().min(SLAVE_TX_BUF_SIZE); + self.slave_tx_bufs[bus as usize][..len].copy_from_slice(&data[..len]); + self.slave_tx_lens[bus as usize] = len; + Ok(()) + } + + /// Block until the next slave event, handling reads automatically. + /// + /// On `DataReceived`, reads bytes into `rx_buf` and returns + /// `(SlaveEventKind::DataReceived, n)` where `n` is the byte count. + /// + /// On `ReadRequest`, sends the pre-loaded TX buffer (set via + /// [`slave_set_response`]) and returns `(SlaveEventKind::ReadRequest, 0)`. + /// + /// On `Stop`, returns `(SlaveEventKind::Stop, 0)`. + pub fn slave_wait_event( + &mut self, + bus: u8, + rx_buf: &mut [u8], + ) -> Result<(SlaveEventKind, usize), ResponseCode> { + if !self.is_bus_initialized(bus) { + return Err(ResponseCode::ServerError); + } + if (self.slave_configured & (1 << bus)) == 0 { + return Err(ResponseCode::NotInitialized); + } + + // Copy TX buffer to local stack before borrowing peripherals. + let tx_len = self.slave_tx_lens[bus as usize]; + let mut tx_local = [0u8; SLAVE_TX_BUF_SIZE]; + tx_local[..tx_len].copy_from_slice(&self.slave_tx_bufs[bus as usize][..tx_len]); + + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + + const POLL_BUDGET: usize = 10_000; + for _ in 0..POLL_BUDGET { + match i2c.handle_slave_interrupt() { + Some(SlaveEvent::DataReceived { len: _ }) => { + let n = i2c.slave_read(rx_buf).map_err(map_i2c_error)?; + return Ok((SlaveEventKind::DataReceived, n)); + } + Some(SlaveEvent::ReadRequest) => { + let _ = i2c.slave_write(&tx_local[..tx_len]); + return Ok((SlaveEventKind::ReadRequest, 0)); + } + Some(SlaveEvent::DataSent { len: _ }) => { + // Read transaction completed — treat same as ReadRequest + // (data was already pre-loaded and sent by hardware). + return Ok((SlaveEventKind::ReadRequest, 0)); + } + Some(SlaveEvent::Stop) => { + return Ok((SlaveEventKind::Stop, 0)); + } + Some(SlaveEvent::WriteRequest) | None => { + continue; + } + } + } + + Err(ResponseCode::Timeout) + } + + /// Poll for received slave data, blocking until data arrives or timeout. + /// + /// Returns the number of bytes written into `buf`, or 0 if a Stop was + /// received before any data (e.g. a probe). Returns `ResponseCode::Timeout` + /// if no activity is seen within the polling budget. + pub fn slave_receive(&mut self, bus: u8, buf: &mut [u8]) -> Result<usize, ResponseCode> { + if !self.is_bus_initialized(bus) { + return Err(ResponseCode::ServerError); + } + if (self.slave_configured & (1 << bus)) == 0 { + return Err(ResponseCode::NotInitialized); + } + let (regs, buffs) = self.controller_regs(bus)?; + let ctrl = aspeed_ddk::i2c_core::I2cController { + controller: DdkController(bus), + registers: regs, + buff_registers: buffs, + }; + let mut i2c = Ast1060I2c::from_initialized(&ctrl, I2cConfig::default()); + + // Polling loop — spins until a relevant event or budget exhausted. + // At 200 MHz, ~10_000 iterations ≈ a few hundred microseconds. + const POLL_BUDGET: usize = 10_000; + for _ in 0..POLL_BUDGET { + match i2c.handle_slave_interrupt() { + Some(SlaveEvent::DataReceived { len: _ }) => { + let n = i2c.slave_read(buf).map_err(map_i2c_error)?; + return Ok(n); + } + Some(SlaveEvent::Stop) => { + // Stop without data (e.g. probe or zero-length write). + return Ok(0); + } + Some(SlaveEvent::WriteRequest) | Some(SlaveEvent::ReadRequest) => { + // Transaction in progress — keep polling for data. + continue; + } + Some(SlaveEvent::DataSent { len: _ }) => { + // Master read from us; not relevant for receive path. + continue; + } + None => { + // No hardware event yet — keep polling. + continue; + } + } + } + + Err(ResponseCode::Timeout) + } }
diff --git a/services/i2c/client/src/lib.rs b/services/i2c/client/src/lib.rs index 5f50a0c..6048c45 100644 --- a/services/i2c/client/src/lib.rs +++ b/services/i2c/client/src/lib.rs
@@ -24,11 +24,14 @@ use i2c_api::{ wire::{ - self, encode_probe_request, encode_read_request, encode_write_read_request, - encode_write_request, I2cResponseHeader, WireError, + self, encode_configure_slave_request, encode_disable_slave_request, + encode_enable_slave_request, encode_probe_request, encode_read_request, + encode_slave_receive_request, encode_slave_set_response_request, + encode_slave_wait_event_request, encode_write_read_request, encode_write_request, + I2cResponseHeader, WireError, }, - BusIndex, I2cAddress, I2cClient, I2cError, I2cErrorKind, NoAcknowledgeSource, Operation, - ResponseCode, + BusIndex, I2cAddress, I2cClient, I2cError, I2cErrorKind, I2cTargetClient, NoAcknowledgeSource, + Operation, ResponseCode, SlaveEventKind, TargetMessage, TARGET_MESSAGE_MAX_LEN, }; use userspace::syscall; @@ -207,3 +210,134 @@ Ok(()) } } + +impl IpcI2cClient { + /// Pre-load data the slave will send when the master reads from us. + /// + /// Must be called before [`slave_wait_event`] if a read response is needed. + pub fn slave_set_response(&mut self, bus: BusIndex, data: &[u8]) -> Result<(), I2cError> { + let req_len = + encode_slave_set_response_request(&mut self.request_buf, bus.value(), data) + .map_err(wire_to_i2c_error)?; + let resp_len = self.send_recv(req_len)?; + let _ = self.decode_response(resp_len)?; + Ok(()) + } + + /// Block until the next slave event on `bus`. + /// + /// Returns the event kind and, for `DataReceived`, the received bytes + /// written into `rx_buf`. The returned `usize` is the number of bytes + /// written. + pub fn slave_wait_event( + &mut self, + bus: BusIndex, + rx_buf: &mut [u8], + ) -> Result<(SlaveEventKind, usize), I2cError> { + let max_rx = rx_buf.len().min(wire::MAX_PAYLOAD_SIZE - 1); + let req_len = encode_slave_wait_event_request( + &mut self.request_buf, + bus.value(), + max_rx as u16, + ) + .map_err(wire_to_i2c_error)?; + + let resp_len = self.send_recv(req_len)?; + let data = self.decode_response(resp_len)?; + + if data.is_empty() { + return Err(I2cError::from_code(ResponseCode::ServerError)); + } + + let kind = SlaveEventKind::from_u8(data[0]) + .ok_or_else(|| I2cError::from_code(ResponseCode::ServerError))?; + + let rx_len = if kind == SlaveEventKind::DataReceived { + let n = (data.len() - 1).min(rx_buf.len()); + rx_buf[..n].copy_from_slice(&data[1..1 + n]); + n + } else { + 0 + }; + + Ok((kind, rx_len)) + } +} + +impl I2cTargetClient for IpcI2cClient { + fn configure_target_address( + &mut self, + bus: BusIndex, + address: I2cAddress, + ) -> Result<(), Self::Error> { + let req_len = + encode_configure_slave_request(&mut self.request_buf, bus.value(), address.value()) + .map_err(wire_to_i2c_error)?; + let resp_len = self.send_recv(req_len)?; + let _ = self.decode_response(resp_len)?; + Ok(()) + } + + fn enable_receive(&mut self, bus: BusIndex) -> Result<(), Self::Error> { + let req_len = encode_enable_slave_request(&mut self.request_buf, bus.value()) + .map_err(wire_to_i2c_error)?; + let resp_len = self.send_recv(req_len)?; + let _ = self.decode_response(resp_len)?; + Ok(()) + } + + fn disable_receive(&mut self, bus: BusIndex) -> Result<(), Self::Error> { + let req_len = encode_disable_slave_request(&mut self.request_buf, bus.value()) + .map_err(wire_to_i2c_error)?; + let resp_len = self.send_recv(req_len)?; + let _ = self.decode_response(resp_len)?; + Ok(()) + } + + fn wait_for_messages( + &mut self, + bus: BusIndex, + messages: &mut [TargetMessage], + _timeout: Option<core::time::Duration>, + ) -> Result<usize, Self::Error> { + let mut count = 0; + for msg in messages.iter_mut() { + let req_len = encode_slave_receive_request( + &mut self.request_buf, + bus.value(), + TARGET_MESSAGE_MAX_LEN as u16, + ) + .map_err(wire_to_i2c_error)?; + let resp_len = self.send_recv(req_len)?; + let data = self.decode_response(resp_len)?; + if data.is_empty() { + // No data (Stop or timeout) — no more messages pending. + break; + } + let copy_len = core::cmp::min(data.len(), TARGET_MESSAGE_MAX_LEN); + msg.data_mut()[..copy_len].copy_from_slice(&data[..copy_len]); + msg.set_len(copy_len); + count += 1; + } + Ok(count) + } + + fn register_notification( + &mut self, + _bus: BusIndex, + _notification_mask: u32, + ) -> Result<(), Self::Error> { + // Interrupt-driven notifications are not yet implemented. + Err(I2cError::from_code(ResponseCode::ServerError)) + } + + fn get_pending_messages( + &mut self, + bus: BusIndex, + messages: &mut [TargetMessage], + ) -> Result<usize, Self::Error> { + // Non-blocking variant: same as wait_for_messages (hardware poll returns + // immediately on timeout with 0 bytes). + self.wait_for_messages(bus, messages, None) + } +}
diff --git a/services/i2c/server/src/main.rs b/services/i2c/server/src/main.rs index 55c2066..9a5ae37 100644 --- a/services/i2c/server/src/main.rs +++ b/services/i2c/server/src/main.rs
@@ -109,7 +109,6 @@ response: &mut [u8], backend: &mut AspeedI2cBackend, ) -> usize { - pw_log::info!("I2C server dispatch"); // Parse header let Some(header) = I2cRequestHeader::from_bytes(request) else { return encode_error(response, ResponseCode::ServerError); @@ -203,6 +202,94 @@ } // ------------------------------------------------------------------ + // ConfigureSlave: set slave address on a bus + // ------------------------------------------------------------------ + I2cOp::ConfigureSlave => { + pw_log::info!("I2C dispatch configure slave"); + match backend.configure_slave(header.bus, header.address) { + Ok(()) => encode_success(response, 0), + Err(code) => encode_error(response, code), + } + } + + // ------------------------------------------------------------------ + // EnableSlave: activate slave receive mode + // ------------------------------------------------------------------ + I2cOp::EnableSlave => { + pw_log::info!("I2C dispatch enable slave"); + match backend.enable_slave(header.bus) { + Ok(()) => encode_success(response, 0), + Err(code) => encode_error(response, code), + } + } + + // ------------------------------------------------------------------ + // DisableSlave: deactivate slave receive mode + // ------------------------------------------------------------------ + I2cOp::DisableSlave => { + pw_log::info!("I2C dispatch disable slave"); + match backend.disable_slave(header.bus) { + Ok(()) => encode_success(response, 0), + Err(code) => encode_error(response, code), + } + } + + // ------------------------------------------------------------------ + // SlaveReceive: blocking poll for incoming data + // ------------------------------------------------------------------ + I2cOp::SlaveReceive => { + pw_log::info!("I2C dispatch slave receive"); + let rlen = header.read_len as usize; + let avail = response.len().saturating_sub(I2cResponseHeader::SIZE); + if rlen > avail { + return encode_error(response, ResponseCode::BufferTooLarge); + } + let buf = &mut response[I2cResponseHeader::SIZE..I2cResponseHeader::SIZE + rlen]; + match backend.slave_receive(header.bus, buf) { + Ok(n) => encode_success(response, n), + Err(code) => encode_error(response, code), + } + } + + // ------------------------------------------------------------------ + // SlaveSetResponse: pre-load TX buffer for next master read + // ------------------------------------------------------------------ + I2cOp::SlaveSetResponse => { + // pw_log::info!("I2C dispatch slave set response"); + let wlen = header.write_len as usize; + if payload.len() < wlen { + return encode_error(response, ResponseCode::BufferTooSmall); + } + match backend.slave_set_response(header.bus, &payload[..wlen]) { + Ok(()) => encode_success(response, 0), + Err(code) => encode_error(response, code), + } + } + + // ------------------------------------------------------------------ + // SlaveWaitEvent: block until next slave event, return kind + data + // + // Response payload layout: + // byte 0: SlaveEventKind as u8 + // bytes 1..: received data (only for DataReceived events) + // ------------------------------------------------------------------ + I2cOp::SlaveWaitEvent => { + let max_rx = header.read_len as usize; + // Reserve space for event-kind byte + rx data. + let avail = response.len().saturating_sub(I2cResponseHeader::SIZE + 1); + let rx_cap = max_rx.min(avail); + let rx_buf = &mut response[I2cResponseHeader::SIZE + 1..I2cResponseHeader::SIZE + 1 + rx_cap]; + match backend.slave_wait_event(header.bus, rx_buf) { + Ok((kind, rx_len)) => { + let total = 1 + rx_len; + response[I2cResponseHeader::SIZE] = kind as u8; + encode_success(response, total) + } + Err(code) => encode_error(response, code), + } + } + + // ------------------------------------------------------------------ // Not yet implemented // ------------------------------------------------------------------ I2cOp::ConfigureSpeed | I2cOp::Transaction => {
diff --git a/target/ast1060-evb/harness/uart_test_exec.py b/target/ast1060-evb/harness/uart_test_exec.py index 4f1fc1a..bfd942a 100644 --- a/target/ast1060-evb/harness/uart_test_exec.py +++ b/target/ast1060-evb/harness/uart_test_exec.py
@@ -79,24 +79,35 @@ self.log_file_handle.write(text) self.log_file_handle.flush() - def print_uart_data(self, data: str): - """Print UART data, with detokenized output on the following line in green. + def print_uart_data(self, raw: bytes): + """Print UART data, detokenizing base64-encoded pw_log tokens when possible. - If --notok is set, the raw tokenized data is suppressed and only the - detokenized output is printed. When --elf is supplied, the detokenized - string (not the raw token) is written to the log. + raw -- the undecoded bytes straight from the serial port. + + pw_tokenizer embeds base64-encoded token frames in otherwise plain text. + We let detokenize_base64 scan the byte string for those frames and + substitute decoded strings in-place; anything that is not a token frame + is left as-is. The result is a plain str that we print in green when + detokenization changed anything. + + If --notok is set the raw line is suppressed and only the decoded output + is shown. When no ELF / detokenizer is available we just decode and + print the raw bytes. """ + text = raw.decode("utf-8", errors="replace") + if self.detokenizer: - detokenized = self.detokenizer.detokenize_text(data) - if detokenized != data: + detokenized = self.detokenizer.detokenize_text(raw).decode("utf-8", errors="replace") + if detokenized != text: if not getattr(self.args, "notok", False): - print(data, end="", flush=True) - self._write_log(data) + print(text, end="", flush=True) + self._write_log(text) print(f"\033[32m{detokenized}\033[0m", end="", flush=True) self._write_log(detokenized) return - print(data, end="", flush=True) - self._write_log(data) + + print(text, end="", flush=True) + self._write_log(text) def run_command(self, cmd: list, check: bool = True) -> Tuple[int, str, str]: """Run command and return (returncode, stdout, stderr).""" @@ -211,27 +222,22 @@ self.log_file_handle.close() self.log_file_handle = None - def read_serial_data(self, timeout_seconds: float = 1.0) -> str: - """Read available data from serial port.""" + def read_serial_data(self, timeout_seconds: float = 1.0) -> bytes: + """Read available data from serial port, returning raw bytes.""" if not self.serial_port or self.args.skip_uart: - return "" + return b"" if self.args.dry_run: - return "" + return b"" try: self.serial_port.timeout = timeout_seconds data = self.serial_port.read(1024) - - if data: - decoded = data.decode("utf-8", errors="ignore") - return decoded - - return "" + return data if data else b"" except Exception as e: self.log(f"Serial read error: {e}") - return "" + return b"" def write_serial_data(self, data: bytes) -> bool: """Write data to serial port.""" @@ -264,7 +270,7 @@ return True start_time = time.time() - buffer = "" + buffer = b"" while time.time() - start_time < timeout: data = self.read_serial_data(0.1) @@ -274,7 +280,7 @@ self.print_uart_data(data) # Look for 'U' character - if "U" in buffer: + if b"U" in buffer: self.log("\nUART bootloader ready detected!") return True @@ -346,18 +352,21 @@ return True start_time = time.time() - buffer = "" + line_buffer = "" test_results = {"passed": 0, "failed": 0, "skipped": 0} while time.time() - start_time < actual_timeout: data = self.read_serial_data(0.5) if data: - buffer += data if not self.args.quiet: self.print_uart_data(data) - lines = buffer.split("\n") - for line in lines: + line_buffer += data.decode("utf-8", errors="replace") + lines = line_buffer.split("\n") + # Keep the last (possibly incomplete) line in the buffer + line_buffer = lines[-1] + + for line in lines[:-1]: if "PASS" in line: test_results["passed"] += 1 elif "FAIL" in line: @@ -378,8 +387,6 @@ self.log(f"\nFailure detected: {pattern}") return False - buffer = "\n".join(lines[-10:]) - self.log(f"\nTest monitoring timeout. Results so far: {test_results}") return test_results["failed"] == 0
diff --git a/target/ast1060-evb/i2c-slave/BUILD.bazel b/target/ast1060-evb/i2c-slave/BUILD.bazel new file mode 100644 index 0000000..98386ad --- /dev/null +++ b/target/ast1060-evb/i2c-slave/BUILD.bazel
@@ -0,0 +1,86 @@ +# Licensed under the Apache-2.0 license + +load("@pigweed//pw_kernel/tooling:app_package.bzl", "app_package") +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/ast1060-evb:defs.bzl", "TARGET_COMPATIBLE_WITH") +load("//target:uart_boot_image.bzl", "uart_boot_image") +load("//target/ast1060-evb/harness:uart_upload_test.bzl", "uart_upload") + +# --------------------------------------------------------------------------- +# System image: I2C server + slave echo app +# --------------------------------------------------------------------------- + +system_image( + name = "i2c_slave", + apps = [ + "//apps/i2c-slave-echo:i2c_slave_echo", + "//services/i2c/server:i2c_server", + ], + kernel = ":target", + platform = "//target/ast1060-evb", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + visibility = ["//visibility:public"], +) + +# Generate UART bootable image for flashing to the physical board +uart_boot_image( + name = "i2c_slave_uart", + src = ":i2c_slave", + out = "i2c_slave_uart.bin", +) + +# Upload-only target for manual testing +# Run with: bazel run :upload_i2c_slave --config=k_ast1060_evb +uart_upload( + name = "upload_i2c_slave", + image = ":i2c_slave_uart", + skip_gpio = True, +) + +filegroup( + name = "system_config", + srcs = ["system.json5"], + visibility = ["//visibility:public"], +) + +target_codegen( + name = "codegen", + arch = "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + system_config = ":system_config", + target_compatible_with = TARGET_COMPATIBLE_WITH, +) + +target_linker_script( + name = "linker_script", + system_config = ":system_config", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + template = "//target/ast1060-evb:linker_script_template", +) + +rust_binary( + name = "target", + srcs = [ + "target.rs", + ], + edition = "2024", + tags = ["kernel"], + target_compatible_with = TARGET_COMPATIBLE_WITH, + deps = [ + ":codegen", + ":linker_script", + "//target/ast1060-evb:entry", + "//target/ast1060-evb:console_backend_uart", + "@rust_crates//:cortex-m-semihosting", + "@pigweed//pw_kernel/arch/arm_cortex_m:arch_arm_cortex_m", + "@pigweed//pw_kernel/kernel", + "@pigweed//pw_kernel/target:target_common", + "@pigweed//pw_kernel/userspace", + "@pigweed//pw_log/rust:pw_log", + ], +)
diff --git a/target/ast1060-evb/i2c-slave/system.json5 b/target/ast1060-evb/i2c-slave/system.json5 new file mode 100644 index 0000000..c3f99db --- /dev/null +++ b/target/ast1060-evb/i2c-slave/system.json5
@@ -0,0 +1,87 @@ +// Licensed under the Apache-2.0 license + +// AST1060-EVB I2C Slave Echo System Configuration +// +// Memory Layout: +// 0x00000000 - 0x000004A0: Vector table + kernel annotations +// 0x000004A0 - 0x00020000: Kernel code (~126KB) +// 0x00020000 - 0x00040000: i2c_server flash (128KB) +// 0x00040000 - 0x00050000: i2c_slave_echo flash (64KB) +// 0x00060000 - 0x00080000: Kernel RAM (128KB) +// 0x00080000 - 0x00090000: i2c_server RAM (64KB) +// 0x00090000 - 0x00098000: i2c_slave_echo RAM (32KB) +{ + arch: { + type: "armv7m", + vector_table_start_address: 0x00000000, + vector_table_size_bytes: 1280, + }, + kernel: { + flash_start_address: 0x00000500, + flash_size_bytes: 129792, + ram_start_address: 0x00060000, + ram_size_bytes: 131072, + }, + apps: [ + // ──── I2C Server ──── + { + name: "i2c_server", + flash_size_bytes: 131072, + ram_size_bytes: 65536, + process: { + name: "i2c server process", + objects: [ + { + name: "I2C", + type: "channel_handler", + }, + ], + memory_mappings: [ + { + // AST1060 I2C peripheral block: I2cglobal through I2cFilter + // Base: 0x7e7b0000, covers all 14 controllers + buffers + filter + name: "i2c_regs", + type: "device", + start_address: 0x7e7b0000, + size_bytes: 0x4000, + }, + { + name: "scu", + type: "device", + start_address: 0x7e6e2000, + size_bytes: 0x1000, // SCU register block (scu310 at +0x310 read for APB clock) + }, + ], + threads: [ + { + name: "i2c server thread", + stack_size_bytes: 4096, + }, + ], + }, + }, + // ──── I2C Slave Echo ──── + { + name: "i2c_slave_echo", + flash_size_bytes: 65536, + ram_size_bytes: 32768, + process: { + name: "i2c slave echo process", + objects: [ + { + name: "I2C", + type: "channel_initiator", + handler_app: "i2c_server", + handler_object_name: "I2C", + }, + ], + threads: [ + { + name: "i2c slave echo thread", + stack_size_bytes: 2048, + }, + ], + }, + }, + ], +}
diff --git a/target/ast1060-evb/i2c-slave/target.rs b/target/ast1060-evb/i2c-slave/target.rs new file mode 100644 index 0000000..e452ef8 --- /dev/null +++ b/target/ast1060-evb/i2c-slave/target.rs
@@ -0,0 +1,29 @@ +// Licensed under the Apache-2.0 license + +//! AST1060-EVB I2C Slave Echo Target + +#![no_std] +#![no_main] + +use target_common::{TargetInterface, declare_target}; +use {console_backend as _, entry as _}; + +pub struct Target {} + +impl TargetInterface for Target { + const NAME: &'static str = "AST1060-EVB I2C Slave Echo"; + + fn main() -> ! { + codegen::start(); + #[expect(clippy::empty_loop)] + loop {} + } + + fn shutdown(code: u32) -> ! { + pw_log::info!("Shutting down with code {}", code as u32); + #[expect(clippy::empty_loop)] + loop {} + } +} + +declare_target!(Target);
diff --git a/target/ast1060-evb/i2c/i2c_client_test.rs b/target/ast1060-evb/i2c/i2c_client_test.rs index 5838ca1..d060f98 100644 --- a/target/ast1060-evb/i2c/i2c_client_test.rs +++ b/target/ast1060-evb/i2c/i2c_client_test.rs
@@ -30,6 +30,8 @@ use app_i2c_client::handle; use i2c_api::{BusIndex, I2cAddress, I2cClient, I2cClientBlocking}; +#[allow(unused_imports)] +use i2c_api::I2cTargetClient; use i2c_client::IpcI2cClient; use pw_status::{Error, Result}; use userspace::entry; @@ -42,6 +44,19 @@ /// I2C bus for master tests (I2C1 — connected to ADT7490 on the EVB) const I2C_BUS: BusIndex = BusIndex::BUS_2; +/// I2C bus for slave tests (same bus as master — I2C2). +/// +/// The AST1060 supports simultaneous master and slave operation on the same +/// controller: master uses I2CM* registers, slave uses I2CS* registers. +/// Loopback is achieved by writing to SLAVE_ADDR on I2C_BUS from the master; +/// the slave controller on the same bus will receive the transaction. +#[allow(dead_code)] +const SLAVE_BUS: BusIndex = BusIndex::BUS_2; + +/// Slave address used for slave-mode tests. +#[allow(dead_code)] +const SLAVE_ADDR: u8 = 0x30; + /// ADT7490 temperature sensor 7-bit address (on-board) const ADT7490_ADDR: u8 = 0x42; @@ -129,22 +144,24 @@ for &(reg, expected) in &ADT7490_REGS { // Set register pointer if let Err(_) = client.write(I2C_BUS, addr, &[reg]) { - pw_log::error!("[FAIL] reg write pointer"); + pw_log::error!("[FAIL] write reg=0x%02X", reg as u32); results.fail(); continue; } + pw_log::info!("write reg=0x%02X", reg as u32); // Read one byte let mut buf = [0u8; 1]; match client.read(I2C_BUS, addr, &mut buf) { Ok(_) => { + pw_log::info!("read reg=0x%02X val=0x%02X", reg as u32, buf[0] as u32); if buf[0] == expected { - pw_log::info!("[PASS] reg match"); + pw_log::info!("[PASS] reg=0x%02X match", reg as u32); results.pass(); } else { // Some registers are dynamic (e.g. temperature) — still pass pw_log::info!( - "reg 0x%02X: got 0x%02X, expected 0x%02X (dynamic OK)", + "[PASS] reg=0x%02X got=0x%02X expected=0x%02X (dynamic OK)", reg as u32, buf[0] as u32, expected as u32, @@ -153,7 +170,7 @@ } } Err(_) => { - pw_log::error!("[FAIL] reg read"); + pw_log::error!("[FAIL] read reg=0x%02X", reg as u32); results.fail(); } } @@ -177,11 +194,11 @@ let mut buf = [0u8; 1]; match client.write_read(I2C_BUS, addr, &[0x3D], &mut buf) { Ok(_) => { - pw_log::info!("[PASS] write_read Device ID = 0x%02X", buf[0] as u32); + pw_log::info!("[PASS] write_read reg=0x3D val=0x%02X", buf[0] as u32); results.pass(); } Err(_) => { - pw_log::error!("[FAIL] write_read Device ID"); + pw_log::error!("[FAIL] write_read reg=0x3D"); results.fail(); } } @@ -217,6 +234,136 @@ } // ============================================================================ +// Slave tests (commented out pending hardware validation) +// ============================================================================ + +/* + +/// Verify the full IPC slave configuration path: configure → enable → disable. +/// +/// Does not require external hardware — validates that the IPC plumbing and +/// hardware register writes succeed without error. +fn test_slave_configure(client: &mut IpcI2cClient, results: &mut TestResults) { + let addr = match I2cAddress::new(SLAVE_ADDR) { + Ok(a) => a, + Err(_) => { + pw_log::error!("[FAIL] slave configure: invalid address 0x{:02x}", SLAVE_ADDR as u32); + results.fail(); + return; + } + }; + + match client.configure_target_address(SLAVE_BUS, addr) { + Ok(()) => { + pw_log::info!("[PASS] slave configure @ 0x{:02x} on bus {:?}", SLAVE_ADDR as u32, SLAVE_BUS.value() as u32); + results.pass(); + } + Err(_) => { + pw_log::error!("[FAIL] slave configure"); + results.fail(); + return; + } + } + + match client.enable_receive(SLAVE_BUS) { + Ok(()) => { + pw_log::info!("[PASS] slave enable_receive"); + results.pass(); + } + Err(_) => { + pw_log::error!("[FAIL] slave enable_receive"); + results.fail(); + return; + } + } + + match client.disable_receive(SLAVE_BUS) { + Ok(()) => { + pw_log::info!("[PASS] slave disable_receive"); + results.pass(); + } + Err(_) => { + pw_log::error!("[FAIL] slave disable_receive"); + results.fail(); + } + } +} + +/// Loopback test: master writes to slave address, slave receives the data. +/// +/// # Hardware +/// +/// Uses the AST1060's simultaneous master+slave capability on I2C2. +/// The master (I2CM* registers) initiates a write to SLAVE_ADDR; the slave +/// (I2CS* registers) on the same controller receives the transaction. +/// No external wiring is required. +fn test_slave_loopback(client: &mut IpcI2cClient, results: &mut TestResults) { + const WRITE_DATA: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; + + let slave_addr = match I2cAddress::new(SLAVE_ADDR) { + Ok(a) => a, + Err(_) => { + pw_log::error!("[FAIL] slave loopback: invalid address"); + results.fail(); + return; + } + }; + + // Re-enable slave (configure was called and disabled above). + if client.configure_target_address(SLAVE_BUS, slave_addr).is_err() { + pw_log::error!("[FAIL] slave loopback: configure"); + results.fail(); + return; + } + if client.enable_receive(SLAVE_BUS).is_err() { + pw_log::error!("[FAIL] slave loopback: enable"); + results.fail(); + return; + } + + // Master write to slave address on the same bus. + match client.write(I2C_BUS, slave_addr, &WRITE_DATA) { + Ok(()) => { + pw_log::info!("[PASS] slave loopback: master write"); + results.pass(); + } + Err(_) => { + pw_log::error!("[FAIL] slave loopback: master write"); + results.fail(); + let _ = client.disable_receive(SLAVE_BUS); + return; + } + } + + // Slave receive — should return the 4 bytes written above. + let mut messages = [i2c_api::TargetMessage::default(); 1]; + match client.wait_for_messages(SLAVE_BUS, &mut messages, None) { + Ok(0) => { + pw_log::error!("[FAIL] slave loopback: no message received"); + results.fail(); + } + Ok(_) => { + let data = messages[0].data(); + if data == &WRITE_DATA { + pw_log::info!("[PASS] slave loopback: received correct data"); + results.pass(); + } else { + pw_log::error!("[FAIL] slave loopback: data mismatch"); + results.fail(); + } + } + Err(_) => { + pw_log::error!("[FAIL] slave loopback: wait_for_messages error"); + results.fail(); + } + } + + let _ = client.disable_receive(SLAVE_BUS); +} + +*/ // end slave tests + +// ============================================================================ // Entry point // ============================================================================ @@ -234,6 +381,14 @@ test_write_read_device_id(&mut client, &mut results); test_probe_vacant(&mut client, &mut results); + // pw_log::info!("========================================"); + // pw_log::info!("I2C Slave Tests (IPC slave path, I2C2)"); + // pw_log::info!("Slave addr: 0x30"); + // pw_log::info!("========================================"); + + // test_slave_configure(&mut client, &mut results); + // test_slave_loopback(&mut client, &mut results); + pw_log::info!("========================================"); pw_log::info!( "Results: %u passed, %u failed",