diff --git a/.gitignore b/.gitignore index 59a4c3e..253fdce 100644 --- a/.gitignore +++ b/.gitignore @@ -23,9 +23,12 @@ **/.venv/ **/__pycache__/ -# Pico firmware -*/pico-signer.elf -*/pico-signer.uf2 +# HW Signer firmware build artifacts +*.elf +*.uf2 +*.bak +hwsigner-secure/target/ +embassy-rp-fork/target/ threshold/target threshold-ffi/target cosigner/target @@ -33,6 +36,7 @@ server/target e2e/signer-server/target ark/target ark-ffi/target +hwsigner-secure/target # IDE .vscode/ diff --git a/Makefile b/Makefile index 3908ee6..df78380 100644 --- a/Makefile +++ b/Makefile @@ -6,21 +6,24 @@ # make e2e-ark Run Ark E2E test # make hardware Start regtest for hardware device (no Ark) # make hardware-ark Start regtest for hardware device with Ark -# make flash Build & flash Pico 2 firmware (UF2) +# make hw-build Build HW Signer TrustZone firmware (Secure + NS) +# make hw-flash Flash HW Signer via debug probe +# make hw-test Smoke test HW Signer over USB HID # make down Stop everything # ═══════════════════════════════════════════════════════════════════════════════ -.PHONY: e2e e2e-ark hardware hardware-ark flash down \ +.PHONY: e2e e2e-ark hardware hardware-ark down \ ffi-build ffi-android threshold-ffi-build threshold-ffi-android ark-ffi-build ark-ffi-android \ - cosigner-build server-build signer-build pico-build \ + cosigner-build server-build signer-build \ + hw-build hw-build-secure hw-build-ns hw-flash hw-flash-probe hw-test \ regtest-up regtest-down bitcoin-init mine-loop adb-reverse \ signer-run signer-stop server-run server-stop \ arkd-up arkd-down arkd-init \ - proto threshold-test threshold-ffi-test pico-flash-probe pico-test \ + proto threshold-test threshold-ffi-test \ flutter flutter-run ark-newaddress crypto-bench \ stress-test load-test \ signet-hardware-ark signet-down e2e-mutinynet e2e-mutinynet-ark \ - e2e-test e2e-ark-test regtest regtest-ark regtest-hardware regtest-hardware-ark regtest-hardware-ark-down pico-flash + e2e-test e2e-ark-test regtest regtest-ark regtest-hardware regtest-hardware-ark regtest-hardware-ark-down # ── Variables ───────────────────────────────────────────────────────────────── @@ -96,24 +99,7 @@ hardware-ark: cosigner-build server-build ffi-build ffi-android --wasm ../cosigner/target/wasm32-wasip1/release/cosigner.wasm \ --port 50051 -# 5) Build and flash Pico 2 firmware via UF2 (hold BOOTSEL + plug in USB first) -flash: pico-build - @echo "Converting ELF to UF2..." - cp pico-signer/target/thumbv8m.main-none-eabihf/release/pico-signer pico-signer/pico-signer.elf - picotool uf2 convert pico-signer/pico-signer.elf pico-signer/pico-signer.uf2 --family rp2350-arm-s - @echo "" - @echo "==> Created pico-signer/pico-signer.uf2" - @echo "==> Copy to the RP2350 drive: cp pico-signer/pico-signer.uf2 /media/$$USER/RP2350/" - @echo "" - @if [ -d "/media/$$USER/RP2350" ]; then \ - cp pico-signer/pico-signer.uf2 /media/$$USER/RP2350/ && \ - echo "Copied! Pico will reboot with new firmware."; \ - else \ - echo "RP2350 drive not found. Hold BOOTSEL + plug in the Pico, then run:"; \ - echo " cp pico-signer/pico-signer.uf2 /media/$$USER/RP2350/"; \ - fi - -# Stop everything (server, signer, mine loop, Docker) +# 5) Stop everything (server, signer, mine loop, Docker) down: @echo "Stopping all services..." -pkill -f "target/release/server" || true @@ -125,6 +111,38 @@ down: sudo rm -rf $(DATA_DIR) 2>/dev/null || true @echo "All stopped." +# ═══════════════════════════════════════════════════════════════════════════════ +# HW SIGNER (TrustZone — Secure + Non-Secure worlds) +# ═══════════════════════════════════════════════════════════════════════════════ + +# Build Secure world (rp235x-hal, crypto, SAU — generates target/veneers.o) +hw-build-secure: + @echo "Building HW Signer Secure world..." + cd hwsigner-secure && cargo +nightly build --release + +# Build Non-Secure world (Embassy, USB HID — links veneers.o from Secure build) +hw-build-ns: hw-build-secure + @echo "Building HW Signer Non-Secure world..." + cd hwsigner && cargo clean && cargo +nightly build --release + +# Build both worlds +hw-build: hw-build-ns + +# Flash both worlds via debug probe (requires SWD probe connected) +hw-flash: hw-build + @echo "Flashing via debug probe..." + cp hwsigner-secure/target/thumbv8m.main-none-eabihf/release/hwsigner-secure hwsigner-secure/hwsigner-secure.elf + cp hwsigner/target/thumbv8m.main-none-eabihf/release/hwsigner hwsigner/hwsigner.elf + probe-rs download --chip RP2350 hwsigner-secure/hwsigner-secure.elf + probe-rs download --chip RP2350 hwsigner/hwsigner.elf + probe-rs reset --chip RP2350 + @echo "Flashed and reset!" + +# Smoke test HW Signer over USB HID (no phone needed) +hw-test: + @echo "Testing HW Signer over USB HID..." + scripts/.venv/bin/python3 scripts/test_hwsigner.py $(ARGS) + # ═══════════════════════════════════════════════════════════════════════════════ # BUILD TARGETS # ═══════════════════════════════════════════════════════════════════════════════ @@ -178,10 +196,6 @@ signer-build: -sudo chown -R $(USER):$(USER) e2e/signer-server/target 2>/dev/null || true cd e2e/signer-server && cargo build --release -pico-build: - @echo "Building Pico Signer firmware..." - cd pico-signer && cargo build --release - # ═══════════════════════════════════════════════════════════════════════════════ # INFRASTRUCTURE # ═══════════════════════════════════════════════════════════════════════════════ @@ -268,14 +282,6 @@ threshold-ffi-test: @echo "Running threshold-ffi tests..." cd threshold-ffi && cargo test -pico-flash-probe: pico-build - @echo "Flashing via debug probe..." - cd pico-signer && cargo run --release - -pico-test: - @echo "Testing Pico Signer over USB HID..." - scripts/.venv/bin/python3 scripts/test_pico.py $(ARGS) - flutter: ffi-android cd ap && flutter run @@ -354,4 +360,3 @@ regtest-down: down regtest-hardware: hardware regtest-hardware-ark: hardware-ark regtest-hardware-ark-down: down -pico-flash: flash diff --git a/README.md b/README.md index 79b47b2..86c386a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Merlin Wallet -A **self-custodial Bitcoin wallet** powered by **FROST threshold signatures** and a **Raspberry Pi Pico 2 hardware signer**. No single device ever holds the full private key. +A **self-custodial Bitcoin wallet** powered by **FROST threshold signatures** and an **RP2350 hardware signer**. No single device ever holds the full private key. Three independent identities — your phone, a hardware signer, and a coordination server — jointly control your funds through a 2-of-3 threshold scheme. Transactions require cooperation between any two parties, eliminating single points of failure while keeping the user in control. @@ -18,7 +18,7 @@ Three independent identities — your phone, a hardware signer, and a coordinati +---------------------+---------------------+ | | +-------------+--------------+ +--------------+-------------+ -| Android Phone | USB OTG | Pico 2 Hardware Signer | +| Android Phone | USB OTG | HW Signer (RP2350) | | Flutter App +-------------+ Embassy firmware | | Identity 1/3 | HID 64B | Identity 2/3 | | Signing + wallet logic | reports | Recovery key in flash | @@ -28,7 +28,7 @@ Three independent identities — your phone, a hardware signer, and a coordinati | Identity | Held by | Purpose | |----------|---------|---------| | **Signing** | Phone (local) | Day-to-day transaction signing | -| **Recovery** | Pico 2 (USB HID) | Policy changes, recovery operations | +| **Recovery** | HW Signer (USB HID) | Policy changes, recovery operations | | **Server** | Coordination server | Co-signs transactions, never learns the full key | Any 2-of-3 can produce a valid Taproot (BIP-340) signature. The server alone cannot move funds. @@ -43,10 +43,10 @@ MPCWallet/ +-- threshold-ffi/ C-ABI shared library wrapping threshold for Dart FFI +-- server/ Rust gRPC coordination server (Wasmtime + cosigner WASM) +-- cosigner/ WASM cosigner component (server-side threshold crypto) -+-- pico-signer/ Raspberry Pi Pico 2 firmware (Embassy + USB HID) ++-- hwsigner/ Hardware signer firmware (Embassy + USB HID, RP2350) +-- protocol/ gRPC stubs and proto definitions +-- e2e/ End-to-end integration tests (includes signer-server) -+-- scripts/ Utilities (bitcoin.sh, test_pico.py, udev rules) ++-- scripts/ Utilities (bitcoin.sh, test_hwsigner.py, udev rules) +-- docker-compose.yml Bitcoin regtest environment (bitcoind + electrs) +-- Makefile Build, flash, and run targets ``` @@ -61,7 +61,7 @@ High-level Dart API that orchestrates the full MPC protocol. Manages two local i ### Threshold Library (`threshold/`) -`#![no_std]` Rust implementation of FROST (Flexible Round-Optimized Schnorr Threshold Signatures) over secp256k1 using the `k256` crate. Includes the full 3-round DKG protocol, Pedersen VSS, nonce commitment generation, signature share computation, Lagrange interpolation, Taproot key tweaking, and key refresh. Compiles for four targets: native (tests & server), `wasm32-wasip1` (cosigner), `thumbv8m.main-none-eabihf` (Pico 2), and Dart FFI (`libthreshold_ffi.so`). +`#![no_std]` Rust implementation of FROST (Flexible Round-Optimized Schnorr Threshold Signatures) over secp256k1 using the `k256` crate. Includes the full 3-round DKG protocol, Pedersen VSS, nonce commitment generation, signature share computation, Lagrange interpolation, Taproot key tweaking, and key refresh. Compiles for four targets: native (tests & server), `wasm32-wasip1` (cosigner), `thumbv8m.main-none-eabihf` (hwsigner), and Dart FFI (`libthreshold_ffi.so`). ### Threshold FFI (`threshold-ffi/`) @@ -75,13 +75,13 @@ Rust gRPC server that participates as the third identity in DKG and signing. Eac WASI P2 Component Model guest that encapsulates all threshold cryptography on the server side. Compiled to `wasm32-wasip1` and loaded by the server into per-user Wasmtime instances. Exposes DKG, nonce generation, signing, and key refresh operations through a WIT interface. Shares no memory between users. -### Pico Signer Firmware (`pico-signer/`) +### HW Signer Firmware (`hwsigner/`) Embassy-based async firmware for the RP2350 (Raspberry Pi Pico 2). Communicates over vendor-defined USB HID (64-byte reports) using a chunking protocol for JSON messages up to 8KB. Persists key material to the last 4KB flash sector after DKG. Handles all six commands: `dkg_init`, `dkg_round2`, `dkg_round3`, `generate_nonce`, `sign`, `get_info`. ### Signer Server (`e2e/signer-server/`) -Standalone Rust TCP server that implements the same JSON command protocol as the Pico firmware. Used in E2E and integration tests to simulate the hardware signer without physical hardware. +Standalone Rust TCP server that implements the same JSON command protocol as the hwsigner firmware. Used in E2E and integration tests to simulate the hardware signer without physical hardware. ## Protocol @@ -95,11 +95,11 @@ Any two identities can co-sign. Each generates an ephemeral nonce pair and excha ### Spending Policies -Key refresh creates additional key shares with time-windowed spending limits. Transactions below the threshold use the policy key (phone + server, no hardware signer needed). Policy updates and deletion require a recovery signature from the Pico. +Key refresh creates additional key shares with time-windowed spending limits. Transactions below the threshold use the policy key (phone + server, no hardware signer needed). Policy updates and deletion require a recovery signature from the hardware signer. ### USB HID Chunking -Messages between the phone and Pico are split into 64-byte HID reports: +Messages between the phone and hardware signer are split into 64-byte HID reports: ``` First report: [channel:2][cmd:1][seq:2][total_len:2][payload:57B] @@ -120,12 +120,12 @@ Channel `0x0101`, command `0x05` (MSG). Sequence numbers are big-endian `u16`. L ```bash # Install Rust targets rustup target add wasm32-wasip1 # Cosigner WASM component -rustup target add thumbv8m.main-none-eabihf # Pico 2 firmware +rustup target add thumbv8m.main-none-eabihf # HW Signer firmware # Install cargo-component for building WASI components cargo install cargo-component -# Install probe-rs for Pico flashing (optional, UF2 also supported) +# Install probe-rs for HW Signer flashing (optional, UF2 also supported) cargo install probe-rs-tools ``` @@ -152,15 +152,15 @@ In a second terminal: cd ap && flutter run # Launch on emulator ``` -### 3b. Physical device + Pico hardware signer +### 3b. Physical device + hardware signer -Flash the Pico (hold BOOTSEL, plug in USB, release): +Flash the device (hold BOOTSEL, plug in USB, release): ```bash -make pico-flash # Build firmware, convert to UF2, copy to RP2350 drive +make hw-flash # Build firmware, convert to UF2, copy to RP2350 drive ``` -Connect your Android phone via ADB (wireless debugging recommended to free the USB port for the Pico): +Connect your Android phone via ADB (wireless debugging recommended to free the USB port for the signer): ```bash adb pair : # Pair once @@ -174,22 +174,22 @@ In a second terminal: cd ap && flutter run # Select "Hardware Signer (USB)" in onboarding ``` -Connect the Pico to the phone via USB OTG adapter. The app will auto-discover it. +Connect the signer to the phone via USB OTG adapter. The app will auto-discover it. -### 4. Smoke test the Pico (no phone needed) +### 4. Smoke test the hardware signer (no phone needed) ```bash # Quick: just get_info -make pico-test +make hw-test # Full: 2-of-2 DKG + sign with signer-server as second participant -make pico-test ARGS="--full-dkg" +make hw-test ARGS="--full-dkg" ``` Requires the Python `hidapi` package (`pip install hidapi`) and the udev rule: ```bash -sudo cp scripts/99-pico-signer.rules /etc/udev/rules.d/ +sudo cp scripts/99-hwsigner.rules /etc/udev/rules.d/ sudo udevadm control --reload-rules && sudo udevadm trigger ``` @@ -205,8 +205,8 @@ cd threshold-ffi && cargo test # End-to-end (builds all deps, starts Docker automatically) make e2e-test -# Pico firmware over USB HID -make pico-test ARGS="--full-dkg" +# HW Signer firmware over USB HID +make hw-test ARGS="--full-dkg" ## Performance & Stress Testing @@ -250,9 +250,9 @@ This target: | `threshold-ffi-build` | Build threshold FFI shared library (`libthreshold_ffi.so`) | | `threshold-test` | Run threshold Rust unit tests | | `threshold-ffi-test` | Run threshold-ffi tests | -| `pico-build` | Build Pico 2 firmware | -| `pico-flash` | Build, convert to UF2, and copy to RP2350 drive | -| `pico-test` | Test Pico over USB HID (`ARGS="--full-dkg"` for full test) | +| `hw-build` | Build HW Signer firmware | +| `hw-flash` | Build, convert to UF2, and copy to RP2350 drive | +| `hw-test` | Test HW Signer over USB HID (`ARGS="--full-dkg"` for full test) | | `adb-reverse` | Forward ports 50051 + 50001 from phone to PC | | `regtest` | Full dev stack: Docker + init + signer + server | | `regtest-hardware` | Hardware dev stack: Docker + init + ADB + server | @@ -263,13 +263,13 @@ This target: ## Security Model - The **full private key never exists** on any single device. -- The **hardware signer's secret share** never leaves the Pico's flash memory. -- The **server cannot unilaterally sign** — it always needs cooperation from the phone or Pico. +- The **hardware signer's secret share** never leaves the device's flash memory. +- The **server cannot unilaterally sign** — it always needs cooperation from the phone or hardware signer. - The server is designed to run inside a **Trusted Execution Environment (TEE)**, ensuring the server operator cannot access key shares in memory. - Each user's server-side key share runs in an **isolated WASM sandbox** (Wasmtime) with no shared memory between users. - Signing requests are **authenticated** with Schnorr signatures over timestamped messages to prevent replay attacks. - Policy changes (update/delete spending limits) require a **recovery signature** from the hardware signer. -- The Pico uses the RP2350's **hardware TRNG** for all randomness. +- The hardware signer uses the RP2350's **hardware TRNG** for all randomness. ## References diff --git a/ap/android/app/src/main/kotlin/com/example/ap/UsbHidPlugin.kt b/ap/android/app/src/main/kotlin/com/example/ap/UsbHidPlugin.kt index b21220f..c58f2e5 100644 --- a/ap/android/app/src/main/kotlin/com/example/ap/UsbHidPlugin.kt +++ b/ap/android/app/src/main/kotlin/com/example/ap/UsbHidPlugin.kt @@ -16,7 +16,7 @@ import io.flutter.plugin.common.MethodChannel import java.util.concurrent.Executors /** - * Flutter platform channel plugin for USB HID communication with Pico Signer. + * Flutter platform channel plugin for USB HID communication with HW Signer. * * Exposes methods: enumerate, open, close, writeReport, readReport * via MethodChannel "com.mpcwallet.ap/usb_hid". @@ -31,7 +31,7 @@ class UsbHidPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { private const val CHANNEL = "com.mpcwallet.ap/usb_hid" private const val ACTION_USB_PERMISSION = "com.mpcwallet.ap.USB_PERMISSION" - // Pico Signer USB IDs (pid.codes open-source VID) + // HW Signer USB IDs (pid.codes open-source VID) private const val VENDOR_ID = 0x1209 private const val PRODUCT_ID = 0x0001 @@ -77,7 +77,7 @@ class UsbHidPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { } /** - * List connected USB devices matching Pico Signer VID/PID. + * List connected USB devices matching HW Signer VID/PID. */ private fun enumerate(result: MethodChannel.Result) { val manager = usbManager ?: run { @@ -104,7 +104,7 @@ class UsbHidPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { } /** - * Open connection to the first matching Pico Signer device. + * Open connection to the first matching HW Signer device. */ private fun open(result: MethodChannel.Result) { val manager = usbManager ?: run { @@ -115,7 +115,7 @@ class UsbHidPlugin : FlutterPlugin, MethodChannel.MethodCallHandler { val device = manager.deviceList.values.firstOrNull { d -> d.vendorId == VENDOR_ID && d.productId == PRODUCT_ID } ?: run { - result.error("USB_NOT_FOUND", "No Pico Signer device found", null) + result.error("USB_NOT_FOUND", "No HW Signer device found", null) return } diff --git a/ap/android/app/src/main/res/xml/device_filter.xml b/ap/android/app/src/main/res/xml/device_filter.xml index 9b72750..b57dd20 100644 --- a/ap/android/app/src/main/res/xml/device_filter.xml +++ b/ap/android/app/src/main/res/xml/device_filter.xml @@ -1,5 +1,5 @@ - + diff --git a/ap/lib/screens/onboarding/signer_selection_screen.dart b/ap/lib/screens/onboarding/signer_selection_screen.dart index d361498..24809d0 100644 --- a/ap/lib/screens/onboarding/signer_selection_screen.dart +++ b/ap/lib/screens/onboarding/signer_selection_screen.dart @@ -25,7 +25,7 @@ class SignerSelectionScreen extends StatelessWidget { const SizedBox(height: 8), Text( 'Your recovery key is managed by an external signing device.\n' - 'Connect your Pico 2 signer via USB to continue.', + 'Connect your hardware signer via USB to continue.', style: GoogleFonts.inter(color: Colors.white70, height: 1.5), ), const SizedBox(height: 24), @@ -49,7 +49,7 @@ class SignerSelectionScreen extends StatelessWidget { style: GoogleFonts.inter( fontWeight: FontWeight.w600, fontSize: 15)), const SizedBox(height: 4), - Text('Connect a Pico 2 signing device via USB', + Text('Connect a hardware signing device via USB', style: GoogleFonts.inter( color: Colors.white54, fontSize: 13)), ], diff --git a/ap/lib/screens/policies/edit_policy_screen.dart b/ap/lib/screens/policies/edit_policy_screen.dart index 7e4fd5f..cdbcc8b 100644 --- a/ap/lib/screens/policies/edit_policy_screen.dart +++ b/ap/lib/screens/policies/edit_policy_screen.dart @@ -163,7 +163,7 @@ class _EditPolicyScreenState extends State { Future _showErrorFeedback(Object e) async { final msg = e.toString(); - final isHardwareError = msg.contains('No Pico Signer device found') || + final isHardwareError = msg.contains('No HW Signer device found') || msg.contains('USB') || msg.contains('transport'); if (isHardwareError) { @@ -176,7 +176,7 @@ class _EditPolicyScreenState extends State { style: GoogleFonts.inter( color: Colors.white, fontWeight: FontWeight.bold)), content: Text( - 'Connect your Pico Signer via USB OTG, then tap Retry.', + 'Connect your HW Signer via USB OTG, then tap Retry.', style: GoogleFonts.inter(color: Colors.white70)), actions: [ TextButton( diff --git a/ap/lib/screens/policies/policies_screen.dart b/ap/lib/screens/policies/policies_screen.dart index 33b0b61..f525ac0 100644 --- a/ap/lib/screens/policies/policies_screen.dart +++ b/ap/lib/screens/policies/policies_screen.dart @@ -77,7 +77,7 @@ class _PoliciesScreenState extends State { } } catch (e) { if (mounted) { - final isHardwareError = e.toString().contains('No Pico Signer device found') || + final isHardwareError = e.toString().contains('No HW Signer device found') || e.toString().contains('USB') || e.toString().contains('transport'); if (isHardwareError) { @@ -92,7 +92,7 @@ class _PoliciesScreenState extends State { color: Colors.white, fontWeight: FontWeight.bold), ), content: Text( - 'Connect your Pico Signer via USB OTG, then tap Retry.', + 'Connect your HW Signer via USB OTG, then tap Retry.', style: GoogleFonts.inter(color: Colors.white70), ), actions: [ diff --git a/ap/lib/usb/usb_hardware_signer.dart b/ap/lib/usb/usb_hardware_signer.dart index a4c657c..8385929 100644 --- a/ap/lib/usb/usb_hardware_signer.dart +++ b/ap/lib/usb/usb_hardware_signer.dart @@ -24,7 +24,7 @@ class UsbHardwareSigner implements HardwareSignerInterface { Future connect() async { final devices = await _transport.enumerate(); if (devices.isEmpty) { - throw StateError('No Pico Signer device found. ' + throw StateError('No HW Signer device found. ' 'Connect the device via USB OTG and try again.'); } await _transport.open(); diff --git a/ap/lib/usb/usb_hid_transport.dart b/ap/lib/usb/usb_hid_transport.dart index 8bdbf38..5bd043c 100644 --- a/ap/lib/usb/usb_hid_transport.dart +++ b/ap/lib/usb/usb_hid_transport.dart @@ -1,7 +1,7 @@ /// USB HID transport layer. /// /// Bridges Flutter platform channels (Android USB Host API) with the -/// HID chunking protocol to send/receive JSON commands to the Pico Signer. +/// HID chunking protocol to send/receive JSON commands to the HW Signer. library; import 'dart:convert'; @@ -13,7 +13,7 @@ class UsbHidTransport { final _reassembler = HidReassembler(); bool _connected = false; - /// Discover connected Pico Signer devices. + /// Discover connected HW Signer devices. Future>> enumerate() async { final result = await _channel.invokeMethod('enumerate'); return (result as List).map((e) => Map.from(e)).toList(); diff --git a/e2e/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= b/e2e/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= deleted file mode 100644 index 134699b..0000000 Binary files a/e2e/.dart_tool/test/incremental_kernel.Ly9AZGFydD0zLjM= and /dev/null differ diff --git a/embassy-rp-fork/.cargo-ok b/embassy-rp-fork/.cargo-ok new file mode 100644 index 0000000..5f8b795 --- /dev/null +++ b/embassy-rp-fork/.cargo-ok @@ -0,0 +1 @@ +{"v":1} \ No newline at end of file diff --git a/embassy-rp-fork/.cargo_vcs_info.json b/embassy-rp-fork/.cargo_vcs_info.json new file mode 100644 index 0000000..197f173 --- /dev/null +++ b/embassy-rp-fork/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "b8afe4ff5fd590903861e44224171f37b9ae6e62" + }, + "path_in_vcs": "embassy-rp" +} \ No newline at end of file diff --git a/embassy-rp-fork/CHANGELOG.md b/embassy-rp-fork/CHANGELOG.md new file mode 100644 index 0000000..773301b --- /dev/null +++ b/embassy-rp-fork/CHANGELOG.md @@ -0,0 +1,121 @@ +# Changelog for embassy-rp + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## Unreleased - ReleaseDate + +## 0.9.0 - 2025-11-27 + +- Add documentation for pio `get_x` about autopush. +- Fix several minor typos in documentation +- Add PIO SPI +- Add PIO I2S input +- Add PIO onewire parasite power strong pullup +- add `wait_for_alarm` and `alarm_scheduled` methods to rtc module ([#4216](https://github.com/embassy-rs/embassy/pull/4216)) +- rp235x: use msplim for stack guard instead of MPU +- Add reset_to_usb_boot for rp235x ([#4705](https://github.com/embassy-rs/embassy/pull/4705)) +- Add fix #4822 in PIO onewire. Change to disable the state machine before setting y register ([#4824](https://github.com/embassy-rs/embassy/pull/4824)) +- Add PIO::Ws2812 color order support +- Add TX-only, no SCK SPI support +- Remove atomic-polyfill with critical-section instead ([#4948](https://github.com/embassy-rs/embassy/pull/4948)) + +## 0.8.0 - 2025-08-26 + +## 0.7.1 - 2025-08-26 +- add `i2c` internal pullup options ([#4564](https://github.com/embassy-rs/embassy/pull/4564)) + +## 0.7.0 - 2025-08-04 + +- changed: update to latest embassy-time-queue-utils + +## 0.6.0 - 2025-07-16 + +- update to latest embassy-usb-driver + +## 0.5.0 - 2025-07-15 + +- Fix wrong `funcsel` on RP2350 gpout/gpin ([#3975](https://github.com/embassy-rs/embassy/pull/3975)) +- Fix potential race condition in `ADC::wait_for_ready` ([#4012](https://github.com/embassy-rs/embassy/pull/4012)) +- `flash`: rename `BOOTROM_BASE` to `BOOTRAM_BASE` ([#4014](https://github.com/embassy-rs/embassy/pull/4014)) +- Remove `Peripheral` trait & rename `PeripheralRef` to `Peri` ([#3999](https://github.com/embassy-rs/embassy/pull/3999)) +- Fix watchdog count on RP235x ([#4021](https://github.com/embassy-rs/embassy/pull/4021)) +- I2C: ensure that wakers are registered before checking status of `wait_on` helpers ([#4043](https://github.com/embassy-rs/embassy/pull/4043)) +- Modify `Uarte` and `BufferedUarte` initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) +- `uart`: increase RX FIFO watermark from 1/8 to 7/8 ([#4055](https://github.com/embassy-rs/embassy/pull/4055)) +- Add `spinlock_mutex` ([#4017](https://github.com/embassy-rs/embassy/pull/4017)) +- Enable input mode for PWM pins on RP235x and disable it on drop ([#4093](https://github.com/embassy-rs/embassy/pull/4093)) +- Add `impl rand_core::CryptoRng for Trng` ([#4096](https://github.com/embassy-rs/embassy/pull/4096)) +- `pwm`: enable pull-down resistors for pins in `Drop` implementation ([#4115](https://github.com/embassy-rs/embassy/pull/4115)) +- Rewrite PIO onewire implementation ([#4128](https://github.com/embassy-rs/embassy/pull/4128)) +- Implement RP2040 overclocking ([#4150](https://github.com/embassy-rs/embassy/pull/4150)) +- Implement RP235x overclocking ([#4187](https://github.com/embassy-rs/embassy/pull/4187)) +- `trng`: improve error handling ([#4139](https://github.com/embassy-rs/embassy/pull/4139)) +- Remove `` from `Uart` and `BufferedUart` ([#4155](https://github.com/embassy-rs/embassy/pull/4155)) +- Make bit-depth of I2S PIO program configurable ([#4193](https://github.com/embassy-rs/embassy/pull/4193)) +- Add the possibility to document `bind_interrupts` `struct`s ([#4206](https://github.com/embassy-rs/embassy/pull/4206)) +- Add missing `Debug` and `defmt::Format` `derive`s for ADC & `AnyPin` ([#4205](https://github.com/embassy-rs/embassy/pull/4205)) +- Add `rand-core` v0.9 support ([#4217](https://github.com/embassy-rs/embassy/pull/4217)) +- Update `embassy-sync` to v0.7.0 ([#4234](https://github.com/embassy-rs/embassy/pull/4234)) +- Add compatibility with ws2812 leds that have 4 addressable lights ([#4236](https://github.com/embassy-rs/embassy/pull/4236)) +- Implement input/output inversion ([#4237](https://github.com/embassy-rs/embassy/pull/4237)) +- Add `multicore::current_core` API ([#4362](https://github.com/embassy-rs/embassy/pull/4362)) + +## 0.4.0 - 2025-03-09 + +- Add PIO functions. ([#3857](https://github.com/embassy-rs/embassy/pull/3857)) + The functions added in this change are `get_addr` `get_tx_threshold`, `set_tx_threshold`, `get_rx_threshold`, `set_rx_threshold`, `set_thresholds`. +- Expose the watchdog reset reason. ([#3877](https://github.com/embassy-rs/embassy/pull/3877)) +- Update pio-rs, reexport, move instr methods to SM. ([#3865](https://github.com/embassy-rs/embassy/pull/3865)) +- rp235x: add ImageDef features. ([#3890](https://github.com/embassy-rs/embassy/pull/3890)) +- doc: Fix "the the" ([#3903](https://github.com/embassy-rs/embassy/pull/3903)) +- pio: Add access to DMA engine byte swapping ([#3935](https://github.com/embassy-rs/embassy/pull/3935)) +- Modify BufferedUart initialization to take pins before interrupts ([#3983](https://github.com/embassy-rs/embassy/pull/3983)) + +## 0.3.1 - 2025-02-06 + +Small release fixing a few gnarly bugs, upgrading is strongly recommended. + +- Fix a race condition in the time driver that could cause missed interrupts. ([#3758](https://github.com/embassy-rs/embassy/issues/3758), [#3763](https://github.com/embassy-rs/embassy/pull/3763)) +- rp235x: Make atomics work across cores. ([#3851](https://github.com/embassy-rs/embassy/pull/3851)) +- rp235x: add workaround "SIO spinlock stuck after reset" bug, same as RP2040 ([#3851](https://github.com/embassy-rs/embassy/pull/3851)) +- rp235x: Ensure core1 is reset if core0 resets. ([#3851](https://github.com/embassy-rs/embassy/pull/3851)) +- rp235xb: correct ADC channel numbers. ([#3823](https://github.com/embassy-rs/embassy/pull/3823)) +- rp235x: enable watchdog tick generator. ([#3777](https://github.com/embassy-rs/embassy/pull/3777)) +- Relax I2C address validity check to allow using 7-bit addresses that would be reserved for 10-bit addresses. ([#3809](https://github.com/embassy-rs/embassy/issues/3809), [#3810](https://github.com/embassy-rs/embassy/pull/3810)) + +## 0.3.0 - 2025-01-05 + +- Updated `embassy-time` to v0.4 +- Initial rp235x support +- Setup timer0 tick when initializing clocks +- Allow separate control of duty cycle for each channel in a pwm slice by splitting the Pwm driver. +- Implement `embedded_io::Write` for Uart<'d, T: Instance, Blocking> and UartTx<'d, T: Instance, Blocking> +- Add `set_pullup()` to OutputOpenDrain. + +## 0.2.0 - 2024-08-05 + +- Add read_to_break_with_count +- add option to provide your own boot2 +- Add multichannel ADC +- Add collapse_debuginfo to fmt.rs macros. +- Use raw slices .len() method instead of unsafe hacks. +- Add missing word "pin" in rp pwm documentation +- Add Clone and Copy to Error types +- fix spinlocks staying locked after reset. +- wait until read matches for PSM accesses. +- Remove generics +- fix drop implementation of BufferedUartRx and BufferedUartTx +- implement `embedded_storage_async::nor_flash::MultiwriteNorFlash` +- rp usb: wake ep-wakers after stalling +- rp usb: add stall implementation +- Add parameter for enabling pull-up and pull-down in RP PWM input mode +- rp: remove mod sealed. +- rename pins data type and the macro +- rename pwm channels to pwm slices, including in documentation +- rename the Channel trait to Slice and the PwmPin to PwmChannel +- i2c: Fix race condition that appears on fast repeated transfers. +- Add a basic "read to break" function diff --git a/embassy-rp-fork/Cargo.toml b/embassy-rp-fork/Cargo.toml new file mode 100644 index 0000000..5d298ab --- /dev/null +++ b/embassy-rp-fork/Cargo.toml @@ -0,0 +1,306 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2024" +name = "embassy-rp" +version = "0.9.0" +build = "build.rs" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 or RP235x microcontroller" +documentation = "https://docs.embassy.dev/embassy-rp" +readme = "README.md" +keywords = [ + "embedded", + "async", + "rp235x", + "rp2040", + "embedded-hal", +] +categories = [ + "embedded", + "hardware-support", + "no-std", + "asynchronous", +] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" + +[[package.metadata.embassy.build]] +target = "thumbv6m-none-eabi" +features = [ + "defmt", + "rp2040", + "time-driver", +] + +[[package.metadata.embassy.build]] +target = "thumbv6m-none-eabi" +features = [ + "log", + "rp2040", + "time-driver", +] + +[[package.metadata.embassy.build]] +target = "thumbv6m-none-eabi" +features = [ + "intrinsics", + "rp2040", + "time-driver", +] + +[[package.metadata.embassy.build]] +target = "thumbv6m-none-eabi" +features = [ + "qspi-as-gpio", + "rp2040", + "time-driver", +] + +[[package.metadata.embassy.build]] +target = "thumbv8m.main-none-eabihf" +features = [ + "defmt", + "rp235xa", + "time-driver", +] + +[[package.metadata.embassy.build]] +target = "thumbv8m.main-none-eabihf" +features = [ + "log", + "rp235xa", + "time-driver", +] + +[[package.metadata.embassy.build]] +target = "thumbv8m.main-none-eabihf" +features = [ + "binary-info", + "rp235xa", + "time-driver", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/" +features = [ + "defmt", + "unstable-pac", + "time-driver", +] + +[[package.metadata.embassy_docs.flavors]] +name = "rp2040" +target = "thumbv6m-none-eabi" +features = ["rp2040"] + +[[package.metadata.embassy_docs.flavors]] +name = "rp235xa" +target = "thumbv8m.main-none-eabihf" +features = ["rp235xa"] + +[[package.metadata.embassy_docs.flavors]] +name = "rp235xb" +target = "thumbv8m.main-none-eabihf" +features = ["rp235xb"] + +[package.metadata.docs.rs] +features = [ + "defmt", + "unstable-pac", + "time-driver", + "rp2040", +] + +[features] +_rp235x = ["rp-pac/rp235x"] +_test = [] +# Skip pre_init (SIO/PSM/ACTLR reset) when running as TrustZone Non-Secure. +# These registers are Secure-only and would fault from NS state. +trustzone-ns = [] +binary-info = [ + "rt", + "dep:rp-binary-info", + "rp-binary-info?/binary-info", +] +boot2-at25sf128a = [] +boot2-gd25q64cs = [] +boot2-generic-03h = [] +boot2-is25lp080 = [] +boot2-none = [] +boot2-ram-memcpy = [] +boot2-w25q080 = [] +boot2-w25x10cl = [] +chrono = ["dep:chrono"] +critical-section-impl = ["critical-section/restore-state-u8"] +default = ["rt"] +defmt = [ + "dep:defmt", + "embassy-usb-driver/defmt", + "embassy-hal-internal/defmt", +] +imagedef-none = [] +imagedef-nonsecure-exe = [] +imagedef-secure-exe = [] +intrinsics = [] +log = ["dep:log"] +qspi-as-gpio = [] +rom-func-cache = [] +rom-v2-intrinsics = [] +rp2040 = ["rp-pac/rp2040"] +rp235xa = ["_rp235x"] +rp235xb = ["_rp235x"] +rt = ["rp-pac/rt"] +run-from-ram = [] +time-driver = [ + "dep:embassy-time-driver", + "embassy-time-driver?/tick-hz-1_000_000", + "dep:embassy-time-queue-utils", +] +unstable-pac = [] + +[lib] +name = "embassy_rp" +path = "src/lib.rs" + +[dependencies.cfg-if] +version = "1.0.0" + +[dependencies.chrono] +version = "0.4" +optional = true +default-features = false + +[dependencies.cortex-m] +version = "0.7.6" + +[dependencies.cortex-m-rt] +version = ">=0.6.15,<0.8" + +[dependencies.critical-section] +version = "1.2.0" + +[dependencies.defmt] +version = "1.0.1" +optional = true + +[dependencies.document-features] +version = "0.2.10" + +[dependencies.embassy-embedded-hal] +version = "0.5.0" + +[dependencies.embassy-futures] +version = "0.1.2" + +[dependencies.embassy-hal-internal] +version = "0.3.0" +features = [ + "cortex-m", + "prio-bits-2", +] + +[dependencies.embassy-sync] +version = "0.7.2" + +[dependencies.embassy-time] +version = "0.5.0" + +[dependencies.embassy-time-driver] +version = "0.2.1" +optional = true + +[dependencies.embassy-time-queue-utils] +version = "0.3.0" +optional = true + +[dependencies.embassy-usb-driver] +version = "0.2.0" + +[dependencies.embedded-hal-02] +version = "0.2.6" +features = ["unproven"] +package = "embedded-hal" + +[dependencies.embedded-hal-1] +version = "1.0" +package = "embedded-hal" + +[dependencies.embedded-hal-async] +version = "1.0" + +[dependencies.embedded-hal-nb] +version = "1.0" + +[dependencies.embedded-io] +version = "0.6.1" + +[dependencies.embedded-io-async] +version = "0.6.1" + +[dependencies.embedded-storage] +version = "0.3" + +[dependencies.embedded-storage-async] +version = "0.4.1" + +[dependencies.fixed] +version = "1.28.0" + +[dependencies.log] +version = "0.4.14" +optional = true + +[dependencies.nb] +version = "1.1.0" + +[dependencies.pio] +version = "0.3" + +[dependencies.rand-core-06] +version = "0.6" +package = "rand_core" + +[dependencies.rand-core-09] +version = "0.9" +package = "rand_core" + +[dependencies.rp-binary-info] +version = "0.1.0" +optional = true + +[dependencies.rp-pac] +version = "7.0.0" + +[dependencies.rp2040-boot2] +version = "0.3" + +[dependencies.sha2-const-stable] +version = "0.1" + +[dependencies.smart-leds] +version = "0.4.0" + +[dev-dependencies.embassy-executor] +version = "0.9.0" +features = [ + "arch-std", + "executor-thread", +] + +[dev-dependencies.static_cell] +version = "2" diff --git a/embassy-rp-fork/Cargo.toml.orig b/embassy-rp-fork/Cargo.toml.orig new file mode 100644 index 0000000..8e4bb92 --- /dev/null +++ b/embassy-rp-fork/Cargo.toml.orig @@ -0,0 +1,195 @@ +[package] +name = "embassy-rp" +version = "0.9.0" +edition = "2024" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 or RP235x microcontroller" +keywords = ["embedded", "async", "rp235x", "rp2040", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-rp" + +[package.metadata.embassy] +build = [ + {target = "thumbv6m-none-eabi", features = ["defmt", "rp2040", "time-driver"]}, + {target = "thumbv6m-none-eabi", features = ["log", "rp2040", "time-driver"]}, + {target = "thumbv6m-none-eabi", features = ["intrinsics", "rp2040", "time-driver"]}, + {target = "thumbv6m-none-eabi", features = ["qspi-as-gpio", "rp2040", "time-driver"]}, + {target = "thumbv8m.main-none-eabihf", features = ["defmt", "rp235xa", "time-driver"]}, + {target = "thumbv8m.main-none-eabihf", features = ["log", "rp235xa", "time-driver"]}, + {target = "thumbv8m.main-none-eabihf", features = ["binary-info", "rp235xa", "time-driver"]}, +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/" +features = ["defmt", "unstable-pac", "time-driver"] +flavors = [ + { name = "rp2040", target = "thumbv6m-none-eabi", features = ["rp2040"] }, + { name = "rp235xa", target = "thumbv8m.main-none-eabihf", features = ["rp235xa"] }, + { name = "rp235xb", target = "thumbv8m.main-none-eabihf", features = ["rp235xb"] }, +] + +[package.metadata.docs.rs] +# TODO: it's not GREAT to set a specific target, but docs.rs builds will fail otherwise +# for now, default to rp2040 +features = ["defmt", "unstable-pac", "time-driver", "rp2040"] + +[features] +default = [ "rt" ] + +## Enable the `rt` feature of [`rp-pac`](https://docs.rs/rp-pac). +## With `rt` enabled the PAC provides interrupt vectors instead of letting [`cortex-m-rt`](https://docs.rs/cortex-m-rt) do that. +## See for more info. +rt = [ "rp-pac/rt" ] + +## Enable [defmt support](https://docs.rs/defmt) and enables `defmt` debug-log messages and formatting in embassy drivers. +defmt = ["dep:defmt", "embassy-usb-driver/defmt", "embassy-hal-internal/defmt"] +## Enable log support +log = ["dep:log"] +## Enable chrono support +chrono = ["dep:chrono"] + +## Configure the [`critical-section`](https://docs.rs/critical-section) crate to use an implementation that is safe for multicore use on rp2040. +critical-section-impl = ["critical-section/restore-state-u8"] + +## Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. +## This is unstable because semver-minor (non-breaking) releases of `embassy-rp` may major-bump (breaking) the PAC version. +## If this is an issue for you, you're encouraged to directly depend on a fixed version of the PAC. +## There are no plans to make this stable. +unstable-pac = [] + +## Enable the timer for use with `embassy-time` with a 1MHz tick rate. +time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-1_000_000", "dep:embassy-time-queue-utils"] + +## Enable ROM function cache. This will store the address of a ROM function when first used, improving performance of subsequent calls. +rom-func-cache = [] +## Enable implementations of some compiler intrinsics using functions in the rp2040 Mask ROM. +## These should be as fast or faster than the implementations in compiler-builtins. They also save code space and reduce memory contention. +## Compiler intrinsics are used automatically, you do not need to change your code to get performance improvements from this feature. +intrinsics = [] +## Enable intrinsics based on extra ROM functions added in the v2 version of the rp2040 Mask ROM. +## This version added a lot more floating point operations - many f64 functions and a few f32 functions were added in ROM v2. +rom-v2-intrinsics = [] + +## Allow using QSPI pins as GPIO pins. This is mostly not what you want (because your flash is attached via QSPI pins) +## and adds code and memory overhead when this feature is enabled. +qspi-as-gpio = [] + +## Indicate code is running from RAM. +## Set this if all code is in RAM, and the cores never access memory-mapped flash memory through XIP. +## This allows the flash driver to not force pausing execution on both cores when doing flash operations. +run-from-ram = [] + +#! ### boot2 flash chip support +#! RP2040's internal bootloader is only able to run code from the first 256 bytes of flash. +#! A 2nd stage bootloader (boot2) is required to run larger programs from external flash. +#! Select from existing boot2 implementations via the following features. If none are selected, +#! boot2-w25q080 will be used (w25q080 is the flash chip used on the pico). +#! Each implementation uses flash commands and timings specific to a QSPI flash chip family for better performance. +## Use boot2 with support for Renesas/Dialog AT25SF128a SPI flash. +boot2-at25sf128a = [] +## Use boot2 with support for Gigadevice GD25Q64C SPI flash. +boot2-gd25q64cs = [] +## Use boot2 that only uses generic flash commands - these are supported by all SPI flash, but are slower. +boot2-generic-03h = [] +## Use boot2 with support for ISSI IS25LP080 SPI flash. +boot2-is25lp080 = [] +## Use boot2 that copies the entire program to RAM before booting. This uses generic flash commands to perform the copy. +boot2-ram-memcpy = [] +## Use boot2 with support for Winbond W25Q080 SPI flash. +boot2-w25q080 = [] +## Use boot2 with support for Winbond W25X10CL SPI flash. +boot2-w25x10cl = [] +## Have embassy-rp not provide the boot2 so you can use your own. +## Place your own in the ".boot2" section like: +## ``` +## #[unsafe(link_section = ".boot2")] +## #[used] +## static BOOT2: [u8; 256] = [0; 256]; // Provide your own with e.g. include_bytes! +## ``` +boot2-none = [] + +#! ### Image Definition support +#! RP2350's internal bootloader will only execute firmware if it has a valid Image Definition. +#! There are other tags that can be used (for example, you can tag an image as "DATA") +#! but programs will want to have an exe header. "SECURE_EXE" is usually what you want. +#! Use imagedef-none if you want to configure the Image Definition manually +#! If none are selected, imagedef-secure-exe will be used + +## Image is executable and starts in Secure mode +imagedef-secure-exe = [] +## Image is executable and starts in Non-secure mode +imagedef-nonsecure-exe = [] + +## Have embassy-rp not provide the Image Definition so you can use your own. +## Place your own in the ".start_block" section like: +## ```ignore +## use embassy_rp::block::ImageDef; +## +## #[unsafe(link_section = ".start_block")] +## #[used] +## static IMAGE_DEF: ImageDef = ImageDef::secure_exe(); // Update this with your own implementation. +## ``` +imagedef-none = [] + +## Configure the hal for use with the rp2040 +rp2040 = ["rp-pac/rp2040"] +_rp235x = ["rp-pac/rp235x"] +## Configure the hal for use with the rp235xA +rp235xa = ["_rp235x"] +## Configure the hal for use with the rp235xB +rp235xb = ["_rp235x"] + +# hack around cortex-m peripherals being wrong when running tests. +_test = [] + +## Add a binary-info header block containing picotool-compatible metadata. +## +## Takes up a little flash space, but picotool can then report the name of your +## program and other details. +binary-info = ["rt", "dep:rp-binary-info", "rp-binary-info?/binary-info"] + +[dependencies] +embassy-sync = { version = "0.7.2", path = "../embassy-sync" } +embassy-time-driver = { version = "0.2.1", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.3.0", path = "../embassy-time-queue-utils", optional = true } +embassy-time = { version = "0.5.0", path = "../embassy-time" } +embassy-futures = { version = "0.1.2", path = "../embassy-futures" } +embassy-hal-internal = { version = "0.3.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = { version = "0.5.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" } +defmt = { version = "1.0.1", optional = true } +log = { version = "0.4.14", optional = true } +nb = "1.1.0" +cfg-if = "1.0.0" +cortex-m-rt = ">=0.6.15,<0.8" +cortex-m = "0.7.6" +critical-section = "1.2.0" +chrono = { version = "0.4", default-features = false, optional = true } +embedded-io = { version = "0.6.1" } +embedded-io-async = { version = "0.6.1" } +embedded-storage = { version = "0.3" } +embedded-storage-async = { version = "0.4.1" } +fixed = "1.28.0" + +rp-pac = { version = "7.0.0" } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } +embedded-hal-nb = { version = "1.0" } + +rand-core-06 = { package = "rand_core", version = "0.6" } +rand-core-09 = { package = "rand_core", version = "0.9" } + +pio = { version = "0.3" } +rp2040-boot2 = "0.3" +document-features = "0.2.10" +sha2-const-stable = "0.1" +rp-binary-info = { version = "0.1.0", optional = true } +smart-leds = "0.4.0" + +[dev-dependencies] +embassy-executor = { version = "0.9.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +static_cell = { version = "2" } diff --git a/embassy-rp-fork/LICENSE-APACHE b/embassy-rp-fork/LICENSE-APACHE new file mode 100644 index 0000000..48be126 --- /dev/null +++ b/embassy-rp-fork/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/embassy-rp-fork/LICENSE-MIT b/embassy-rp-fork/LICENSE-MIT new file mode 100644 index 0000000..f1cfbd5 --- /dev/null +++ b/embassy-rp-fork/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) Embassy project contributors +Portions copyright (c) rp-rs organization + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/embassy-rp-fork/README.md b/embassy-rp-fork/README.md new file mode 100644 index 0000000..8e16184 --- /dev/null +++ b/embassy-rp-fork/README.md @@ -0,0 +1,27 @@ +# Embassy RP HAL + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. + +The embassy-rp HAL targets the Raspberry Pi RP2040 as well as RP235x microcontroller. The HAL implements both blocking and async APIs +for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to +complete operations in low power mode and handling interrupts, so that applications can focus on more important matters. + +* [embassy-rp on crates.io](https://crates.io/crates/embassy-rp) +* [Documentation](https://docs.embassy.dev/embassy-rp/) +* [Source](https://github.com/embassy-rs/embassy/tree/main/embassy-rp) +* [Examples](https://github.com/embassy-rs/embassy/tree/main/examples/rp/src/bin) + +## `embassy-time` time driver + +If the `time-driver` feature is enabled, the HAL uses the TIMER peripheral as a global time driver for [embassy-time](https://crates.io/crates/embassy-time), with a tick rate of 1MHz. + +## Embedded-hal + +The `embassy-rp` HAL implements the traits from [embedded-hal](https://crates.io/crates/embedded-hal) (v0.2 and 1.0) and [embedded-hal-async](https://crates.io/crates/embedded-hal-async), as well as [embedded-io](https://crates.io/crates/embedded-io) and [embedded-io-async](https://crates.io/crates/embedded-io-async). + +## Interoperability + +This crate can run on any executor. + +Optionally, some features requiring [`embassy-time`](https://crates.io/crates/embassy-time) can be activated with the `time-driver` feature. If you enable it, +you must link an `embassy-time` driver in your project. diff --git a/embassy-rp-fork/build.rs b/embassy-rp-fork/build.rs new file mode 100644 index 0000000..a8d3876 --- /dev/null +++ b/embassy-rp-fork/build.rs @@ -0,0 +1,39 @@ +use std::env; +use std::ffi::OsStr; +use std::fs::File; +use std::io::Write; +use std::path::{Path, PathBuf}; + +fn main() { + if env::var("CARGO_FEATURE_RP2040").is_ok() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let link_x = include_bytes!("link-rp.x.in"); + let mut f = File::create(out.join("link-rp.x")).unwrap(); + f.write_all(link_x).unwrap(); + + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=link-rp.x.in"); + } + + // code below taken from https://github.com/rust-embedded/cortex-m/blob/master/cortex-m-rt/build.rs + + let mut target = env::var("TARGET").unwrap(); + + // When using a custom target JSON, `$TARGET` contains the path to that JSON file. By + // convention, these files are named after the actual target triple, eg. + // `thumbv7m-customos-elf.json`, so we extract the file stem here to allow custom target specs. + let path = Path::new(&target); + if path.extension() == Some(OsStr::new("json")) { + target = path + .file_stem() + .map_or(target.clone(), |stem| stem.to_str().unwrap().to_string()); + } + + println!("cargo::rustc-check-cfg=cfg(has_fpu)"); + if target.ends_with("-eabihf") { + println!("cargo:rustc-cfg=has_fpu"); + } +} diff --git a/embassy-rp-fork/funcsel.txt b/embassy-rp-fork/funcsel.txt new file mode 100644 index 0000000..4dc8939 --- /dev/null +++ b/embassy-rp-fork/funcsel.txt @@ -0,0 +1,30 @@ +0 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB OVCUR DET +1 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS DET +2 SPI0 SCK UART0 CTS I2C1 SDA PWM1 A SIO PIO0 PIO1 USB VBUS EN +3 SPI0 TX UART0 RTS I2C1 SCL PWM1 B SIO PIO0 PIO1 USB OVCUR DET +4 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 USB VBUS DET +5 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 USB VBUS EN +6 SPI0 SCK UART1 CTS I2C1 SDA PWM3 A SIO PIO0 PIO1 USB OVCUR DET +7 SPI0 TX UART1 RTS I2C1 SCL PWM3 B SIO PIO0 PIO1 USB VBUS DET +8 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 USB VBUS EN +9 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 USB OVCUR DET +10 SPI1 SCK UART1 CTS I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS DET +11 SPI1 TX UART1 RTS I2C1 SCL PWM5 B SIO PIO0 PIO1 USB VBUS EN +12 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB OVCUR DET +13 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS DET +14 SPI1 SCK UART0 CTS I2C1 SDA PWM7 A SIO PIO0 PIO1 USB VBUS EN +15 SPI1 TX UART0 RTS I2C1 SCL PWM7 B SIO PIO0 PIO1 USB OVCUR DET +16 SPI0 RX UART0 TX I2C0 SDA PWM0 A SIO PIO0 PIO1 USB VBUS DET +17 SPI0 CSn UART0 RX I2C0 SCL PWM0 B SIO PIO0 PIO1 USB VBUS EN +18 SPI0 SCK UART0 CTS I2C1 SDA PWM1 A SIO PIO0 PIO1 USB OVCUR DET +19 SPI0 TX UART0 RTS I2C1 SCL PWM1 B SIO PIO0 PIO1 USB VBUS DET +20 SPI0 RX UART1 TX I2C0 SDA PWM2 A SIO PIO0 PIO1 CLOCK GPIN0 USB VBUS EN +21 SPI0 CSn UART1 RX I2C0 SCL PWM2 B SIO PIO0 PIO1 CLOCK GPOUT0 USB OVCUR DET +22 SPI0 SCK UART1 CTS I2C1 SDA PWM3 A SIO PIO0 PIO1 CLOCK GPIN1 USB VBUS DET +23 SPI0 TX UART1 RTS I2C1 SCL PWM3 B SIO PIO0 PIO1 CLOCK GPOUT1 USB VBUS EN +24 SPI1 RX UART1 TX I2C0 SDA PWM4 A SIO PIO0 PIO1 CLOCK GPOUT2 USB OVCUR DET +25 SPI1 CSn UART1 RX I2C0 SCL PWM4 B SIO PIO0 PIO1 CLOCK GPOUT3 USB VBUS DET +26 SPI1 SCK UART1 CTS I2C1 SDA PWM5 A SIO PIO0 PIO1 USB VBUS EN +27 SPI1 TX UART1 RTS I2C1 SCL PWM5 B SIO PIO0 PIO1 USB OVCUR DET +28 SPI1 RX UART0 TX I2C0 SDA PWM6 A SIO PIO0 PIO1 USB VBUS DET +29 SPI1 CSn UART0 RX I2C0 SCL PWM6 B SIO PIO0 PIO1 USB VBUS EN \ No newline at end of file diff --git a/embassy-rp-fork/link-rp.x.in b/embassy-rp-fork/link-rp.x.in new file mode 100644 index 0000000..1839dda --- /dev/null +++ b/embassy-rp-fork/link-rp.x.in @@ -0,0 +1,8 @@ + +SECTIONS { + /* ### Boot loader */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} diff --git a/embassy-rp-fork/src/adc.rs b/embassy-rp-fork/src/adc.rs new file mode 100644 index 0000000..d16779e --- /dev/null +++ b/embassy-rp-fork/src/adc.rs @@ -0,0 +1,471 @@ +//! ADC driver. +use core::future::{Future, poll_fn}; +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::{Ordering, compiler_fence}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin}; +use crate::interrupt::InterruptExt; +use crate::interrupt::typelevel::Binding; +use crate::pac::dma::vals::TreqSel; +use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; +use crate::{Peri, RegExt, dma, interrupt, pac, peripherals}; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// ADC config. +#[non_exhaustive] +#[derive(Default)] +pub struct Config {} + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Source<'p> { + Pin(Peri<'p, AnyPin>), + TempSensor(Peri<'p, ADC_TEMP_SENSOR>), +} + +/// ADC channel. +pub struct Channel<'p>(Source<'p>); + +impl<'p> Channel<'p> { + /// Create a new ADC channel from pin with the provided [Pull] configuration. + pub fn new_pin(pin: Peri<'p, impl AdcPin + 'p>, pull: Pull) -> Self { + pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + // manual says: + // + // > When using an ADC input shared with a GPIO pin, the pin’s + // > digital functions must be disabled by setting IE low and OD + // > high in the pin’s pad control register + w.set_ie(false); + w.set_od(true); + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + Self(Source::Pin(pin.into())) + } + + /// Create a new ADC channel for the internal temperature sensor. + pub fn new_temp_sensor(s: Peri<'p, ADC_TEMP_SENSOR>) -> Self { + let r = pac::ADC; + r.cs().write_set(|w| w.set_ts_en(true)); + Self(Source::TempSensor(s)) + } + + fn channel(&self) -> u8 { + #[cfg(any(feature = "rp2040", feature = "rp235xa"))] + const CH_OFFSET: u8 = 26; + #[cfg(feature = "rp235xb")] + const CH_OFFSET: u8 = 40; + + #[cfg(any(feature = "rp2040", feature = "rp235xa"))] + const TS_CHAN: u8 = 4; + #[cfg(feature = "rp235xb")] + const TS_CHAN: u8 = 8; + + match &self.0 { + // this requires adc pins to be sequential and matching the adc channels, + // which is the case for rp2040/rp235xy + Source::Pin(p) => p._pin() - CH_OFFSET, + Source::TempSensor(_) => TS_CHAN, + } + } +} + +impl<'p> Drop for Source<'p> { + fn drop(&mut self) { + match self { + Source::Pin(p) => { + p.pad_ctrl().modify(|w| { + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(true); + }); + } + Source::TempSensor(_) => { + pac::ADC.cs().write_clear(|w| w.set_ts_en(true)); + } + } + } +} + +/// ADC sample. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct Sample(u16); + +impl Sample { + /// Sample is valid. + pub fn good(&self) -> bool { + self.0 < 0x8000 + } + + /// Sample value. + pub fn value(&self) -> u16 { + self.0 & !0x8000 + } +} + +/// ADC error. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Error converting value. + ConversionFailed, +} + +/// ADC mode. +pub trait Mode {} + +/// ADC async mode. +pub struct Async; +impl Mode for Async {} + +/// ADC blocking mode. +pub struct Blocking; +impl Mode for Blocking {} + +/// ADC driver. +pub struct Adc<'d, M: Mode> { + phantom: PhantomData<(&'d ADC, M)>, +} + +impl<'d, M: Mode> Drop for Adc<'d, M> { + fn drop(&mut self) { + let r = Self::regs(); + // disable ADC. leaving it enabled comes with a ~150µA static + // current draw. the temperature sensor has already been disabled + // by the temperature-reading methods, so we don't need to touch that. + r.cs().write(|w| w.set_en(false)); + } +} + +impl<'d, M: Mode> Adc<'d, M> { + #[inline] + fn regs() -> pac::adc::Adc { + pac::ADC + } + + #[inline] + fn reset() -> pac::resets::regs::Peripherals { + let mut ret = pac::resets::regs::Peripherals::default(); + ret.set_adc(true); + ret + } + + fn setup() { + let reset = Self::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + let r = Self::regs(); + // Enable ADC + r.cs().write(|w| w.set_en(true)); + // Wait for ADC ready + while !r.cs().read().ready() {} + } + + /// Sample a value from a channel in blocking mode. + pub fn blocking_read(&mut self, ch: &mut Channel) -> Result { + let r = Self::regs(); + r.cs().modify(|w| { + w.set_ainsel(ch.channel()); + w.set_start_once(true); + w.set_err(true); + }); + while !r.cs().read().ready() {} + match r.cs().read().err() { + true => Err(Error::ConversionFailed), + false => Ok(r.result().read().result()), + } + } +} + +impl<'d> Adc<'d, Async> { + /// Create ADC driver in async mode. + pub fn new( + _inner: Peri<'d, ADC>, + _irq: impl Binding, + _config: Config, + ) -> Self { + Self::setup(); + + // Setup IRQ + interrupt::ADC_IRQ_FIFO.unpend(); + unsafe { interrupt::ADC_IRQ_FIFO.enable() }; + + Self { phantom: PhantomData } + } + + fn wait_for_ready() -> impl Future { + let r = Self::regs(); + + poll_fn(move |cx| { + WAKER.register(cx.waker()); + + r.inte().write(|w| w.set_fifo(true)); + compiler_fence(Ordering::SeqCst); + + if r.cs().read().ready() { + return Poll::Ready(()); + } + Poll::Pending + }) + } + + /// Sample a value from a channel until completed. + pub async fn read(&mut self, ch: &mut Channel<'_>) -> Result { + let r = Self::regs(); + r.cs().modify(|w| { + w.set_ainsel(ch.channel()); + w.set_start_once(true); + w.set_err(true); + }); + Self::wait_for_ready().await; + match r.cs().read().err() { + true => Err(Error::ConversionFailed), + false => Ok(r.result().read().result()), + } + } + + // Note for refactoring: we don't require the actual Channels here, just the channel numbers. + // The public api is responsible for asserting ownership of the actual Channels. + async fn read_many_inner( + &mut self, + channels: impl Iterator, + buf: &mut [W], + fcs_err: bool, + div: u16, + dma: Peri<'_, impl dma::Channel>, + ) -> Result<(), Error> { + #[cfg(feature = "rp2040")] + let mut rrobin = 0_u8; + #[cfg(feature = "_rp235x")] + let mut rrobin = 0_u16; + for c in channels { + rrobin |= 1 << c; + } + let first_ch = rrobin.trailing_zeros() as u8; + if rrobin.count_ones() == 1 { + rrobin = 0; + } + + let r = Self::regs(); + // clear previous errors and set channel + r.cs().modify(|w| { + w.set_ainsel(first_ch); + w.set_rrobin(rrobin); + w.set_err_sticky(true); // clear previous errors + w.set_start_many(false); + }); + // wait for previous conversions and drain fifo. an earlier batch read may have + // been cancelled, leaving the adc running. + while !r.cs().read().ready() {} + while !r.fcs().read().empty() { + r.fifo().read(); + } + + // set up fifo for dma + r.fcs().write(|w| { + w.set_thresh(1); + w.set_dreq_en(true); + w.set_shift(mem::size_of::() == 1); + w.set_en(true); + w.set_err(fcs_err); + }); + + // reset dma config on drop, regardless of whether it was a future being cancelled + // or the method returning normally. + struct ResetDmaConfig; + impl Drop for ResetDmaConfig { + fn drop(&mut self) { + pac::ADC.cs().write_clear(|w| w.set_start_many(true)); + while !pac::ADC.cs().read().ready() {} + pac::ADC.fcs().write_clear(|w| { + w.set_dreq_en(true); + w.set_shift(true); + w.set_en(true); + }); + } + } + let auto_reset = ResetDmaConfig; + + let dma = unsafe { dma::read(dma, r.fifo().as_ptr() as *const W, buf as *mut [W], TreqSel::ADC) }; + // start conversions and wait for dma to finish. we can't report errors early + // because there's no interrupt to signal them, and inspecting every element + // of the fifo is too costly to do here. + r.div().write_set(|w| w.set_int(div)); + r.cs().write_set(|w| w.set_start_many(true)); + dma.await; + mem::drop(auto_reset); + // we can't report errors before the conversions have ended since no interrupt + // exists to report them early, and since they're exceedingly rare we probably don't + // want to anyway. + match r.cs().read().err_sticky() { + false => Ok(()), + true => Err(Error::ConversionFailed), + } + } + + /// Sample multiple values from multiple channels using DMA. + /// Samples are stored in an interleaved fashion inside the buffer. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_multichannel( + &mut self, + ch: &mut [Channel<'_>], + buf: &mut [S], + div: u16, + dma: Peri<'_, impl dma::Channel>, + ) -> Result<(), Error> { + self.read_many_inner(ch.iter().map(|c| c.channel()), buf, false, div, dma) + .await + } + + /// Sample multiple values from multiple channels using DMA, with errors inlined in samples. + /// Samples are stored in an interleaved fashion inside the buffer. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate * num_channels - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_multichannel_raw( + &mut self, + ch: &mut [Channel<'_>], + buf: &mut [Sample], + div: u16, + dma: Peri<'_, impl dma::Channel>, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner( + ch.iter().map(|c| c.channel()), + unsafe { mem::transmute::<_, &mut [u16]>(buf) }, + true, + div, + dma, + ) + .await; + } + + /// Sample multiple values from a channel using DMA. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [S], + div: u16, + dma: Peri<'_, impl dma::Channel>, + ) -> Result<(), Error> { + self.read_many_inner([ch.channel()].into_iter(), buf, false, div, dma) + .await + } + + /// Sample multiple values from a channel using DMA, with errors inlined in samples. + /// `div` is the integer part of the clock divider and can be calculated with `floor(48MHz / sample_rate - 1)` + /// Any `div` value of less than 96 will have the same effect as setting it to 0 + #[inline] + pub async fn read_many_raw( + &mut self, + ch: &mut Channel<'_>, + buf: &mut [Sample], + div: u16, + dma: Peri<'_, impl dma::Channel>, + ) { + // errors are reported in individual samples + let _ = self + .read_many_inner( + [ch.channel()].into_iter(), + unsafe { mem::transmute::<_, &mut [u16]>(buf) }, + true, + div, + dma, + ) + .await; + } +} + +impl<'d> Adc<'d, Blocking> { + /// Create ADC driver in blocking mode. + pub fn new_blocking(_inner: Peri<'d, ADC>, _config: Config) -> Self { + Self::setup(); + + Self { phantom: PhantomData } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _empty: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = Adc::::regs(); + r.inte().write(|w| w.set_fifo(false)); + WAKER.wake(); + } +} + +trait SealedAdcSample: crate::dma::Word {} +trait SealedAdcChannel {} + +/// ADC sample. +#[allow(private_bounds)] +pub trait AdcSample: SealedAdcSample {} + +impl SealedAdcSample for u16 {} +impl AdcSample for u16 {} + +impl SealedAdcSample for u8 {} +impl AdcSample for u8 {} + +/// ADC channel. +#[allow(private_bounds)] +pub trait AdcChannel: SealedAdcChannel {} +/// ADC pin. +pub trait AdcPin: AdcChannel + gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:expr) => { + impl SealedAdcChannel for peripherals::$pin {} + impl AdcChannel for peripherals::$pin {} + impl AdcPin for peripherals::$pin {} + }; +} + +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_26, 0); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_27, 1); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_28, 2); +#[cfg(any(feature = "rp235xa", feature = "rp2040"))] +impl_pin!(PIN_29, 3); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, 0); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, 1); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, 2); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, 3); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, 4); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, 5); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, 6); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, 7); + +impl SealedAdcChannel for peripherals::ADC_TEMP_SENSOR {} +impl AdcChannel for peripherals::ADC_TEMP_SENSOR {} diff --git a/embassy-rp-fork/src/block.rs b/embassy-rp-fork/src/block.rs new file mode 100644 index 0000000..745883b --- /dev/null +++ b/embassy-rp-fork/src/block.rs @@ -0,0 +1,1079 @@ +//! Support for the RP235x Boot ROM's "Block" structures +//! +//! Blocks contain pointers, to form Block Loops. +//! +//! The `IMAGE_DEF` Block (here the `ImageDef` type) tells the ROM how to boot a +//! firmware image. The `PARTITION_TABLE` Block (here the `PartitionTable` type) +//! tells the ROM how to divide the flash space up into partitions. + +// Credit: Taken from https://github.com/thejpster/rp-hal-rp2350-public (also licensed Apache 2.0 + MIT). +// Copyright (c) rp-rs organization + +// These all have a 1 byte size + +/// An item ID for encoding a Vector Table address +pub const ITEM_1BS_VECTOR_TABLE: u8 = 0x03; + +/// An item ID for encoding a Rolling Window Delta +pub const ITEM_1BS_ROLLING_WINDOW_DELTA: u8 = 0x05; + +/// An item ID for encoding a Signature +pub const ITEM_1BS_SIGNATURE: u8 = 0x09; + +/// An item ID for encoding a Salt +pub const ITEM_1BS_SALT: u8 = 0x0c; + +/// An item ID for encoding an Image Type +pub const ITEM_1BS_IMAGE_TYPE: u8 = 0x42; + +/// An item ID for encoding the image's Entry Point +pub const ITEM_1BS_ENTRY_POINT: u8 = 0x44; + +/// An item ID for encoding the definition of a Hash +pub const ITEM_2BS_HASH_DEF: u8 = 0x47; + +/// An item ID for encoding a Version +pub const ITEM_1BS_VERSION: u8 = 0x48; + +/// An item ID for encoding a Hash +pub const ITEM_1BS_HASH_VALUE: u8 = 0x4b; + +// These all have a 2-byte size + +/// An item ID for encoding a Load Map +pub const ITEM_2BS_LOAD_MAP: u8 = 0x06; + +/// An item ID for encoding a Partition Table +pub const ITEM_2BS_PARTITION_TABLE: u8 = 0x0a; + +/// An item ID for encoding a placeholder entry that is ignored +/// +/// Allows a Block to not be empty. +pub const ITEM_2BS_IGNORED: u8 = 0xfe; + +/// An item ID for encoding the special last item in a Block +/// +/// It records how long the Block is. +pub const ITEM_2BS_LAST: u8 = 0xff; + +// Options for ITEM_1BS_IMAGE_TYPE + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as invalid +pub const IMAGE_TYPE_INVALID: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as an executable +pub const IMAGE_TYPE_EXE: u16 = 0x0001; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark an image as data +pub const IMAGE_TYPE_DATA: u16 = 0x0002; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as unspecified +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_NS: u16 = 0x0010; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU security mode as Non Secure +pub const IMAGE_TYPE_EXE_TYPE_SECURITY_S: u16 = 0x0020; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as Arm +pub const IMAGE_TYPE_EXE_CPU_ARM: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU type as RISC-V +pub const IMAGE_TYPE_EXE_CPU_RISCV: u16 = 0x0100; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2040 +pub const IMAGE_TYPE_EXE_CHIP_RP2040: u16 = 0x0000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the CPU as an RP2350 +pub const IMAGE_TYPE_EXE_CHIP_RP2350: u16 = 0x1000; + +/// A [`ITEM_1BS_IMAGE_TYPE`] value bitmask to mark the image as Try Before You Buy. +/// +/// This means the image must be marked as 'Bought' with the ROM before the +/// watchdog times out the trial period, otherwise it is erased and the previous +/// image will be booted. +pub const IMAGE_TYPE_TBYB: u16 = 0x8000; + +/// This is the magic Block Start value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_START` +const BLOCK_MARKER_START: u32 = 0xffffded3; + +/// This is the magic Block END value. +/// +/// The Pico-SDK calls it `PICOBIN_BLOCK_MARKER_END` +const BLOCK_MARKER_END: u32 = 0xab123579; + +/// An Image Definition has one item in it - an [`ITEM_1BS_IMAGE_TYPE`] +pub type ImageDef = Block<1>; + +/// A Block as understood by the Boot ROM. +/// +/// This could be an Image Definition, or a Partition Table, or maybe some other +/// kind of block. +/// +/// It contains within the special start and end markers the Boot ROM is looking +/// for. +#[derive(Debug)] +#[repr(C)] +pub struct Block { + marker_start: u32, + items: [u32; N], + length: u32, + offset: *const u32, + marker_end: u32, +} + +unsafe impl Sync for Block {} + +impl Block { + /// Construct a new Binary Block, with the given items. + /// + /// The length, and the Start and End markers are added automatically. The + /// Block Loop pointer initially points to itself. + pub const fn new(items: [u32; N]) -> Block { + Block { + marker_start: BLOCK_MARKER_START, + items, + length: item_last(N as u16), + // offset from this block to next block in loop. By default + // we form a Block Loop with a single Block in it. + offset: core::ptr::null(), + marker_end: BLOCK_MARKER_END, + } + } + + /// Change the Block Loop offset value. + /// + /// This method isn't that useful because you can't evaluate the difference + /// between two pointers in a const context as the addresses aren't assigned + /// until long after the const evaluator has run. + /// + /// If you think you need this method, you might want to set a unique random + /// value here and swap it for the real offset as a post-processing step. + pub const fn with_offset(self, offset: *const u32) -> Block { + Block { offset, ..self } + } +} + +impl Block<0> { + /// Construct an empty block. + pub const fn empty() -> Block<0> { + Block::new([]) + } + + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<1> { + Block::new([word]) + } +} + +impl Block<1> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<2> { + Block::new([self.items[0], word]) + } +} + +impl Block<2> { + /// Make the block one word larger + pub const fn extend(self, word: u32) -> Block<3> { + Block::new([self.items[0], self.items[1], word]) + } +} + +impl ImageDef { + /// Construct a new IMAGE_DEF Block, for an EXE with the given security and + /// architecture. + pub const fn arch_exe(security: Security, architecture: Architecture) -> Self { + Self::new([item_image_type_exe(security, architecture)]) + } + + /// Construct a new IMAGE_DEF Block, for an EXE with the given security. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn exe(security: Security) -> Self { + if cfg!(all(target_arch = "riscv32", target_os = "none")) { + Self::arch_exe(security, Architecture::Riscv) + } else { + Self::arch_exe(security, Architecture::Arm) + } + } + + /// Construct a new IMAGE_DEF Block, for a Non-Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn non_secure_exe() -> Self { + Self::exe(Security::NonSecure) + } + + /// Construct a new IMAGE_DEF Block, for a Secure EXE. + /// + /// The target architecture is taken from the current build target (i.e. Arm + /// or RISC-V). + pub const fn secure_exe() -> Self { + Self::exe(Security::Secure) + } +} + +/// We make our partition table this fixed size. +pub const PARTITION_TABLE_MAX_ITEMS: usize = 128; + +/// Describes a unpartitioned space +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct UnpartitionedSpace { + permissions_and_location: u32, + permissions_and_flags: u32, +} + +impl UnpartitionedSpace { + /// Create a new unpartitioned space. + /// + /// It defaults to no permissions. + pub const fn new() -> Self { + Self { + permissions_and_location: 0, + permissions_and_flags: 0, + } + } + + /// Create a new unpartitioned space from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PT_INFO`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | permission as u32, + permissions_and_location: self.permissions_and_location | permission as u32, + } + } + + /// Set a flag + pub const fn with_flag(self, flag: UnpartitionedFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: UnpartitionedFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for UnpartitionedSpace { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a Partition +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Partition { + permissions_and_location: u32, + permissions_and_flags: u32, + id: Option, + extra_families: [u32; 4], + extra_families_len: usize, + name: [u8; 128], +} + +impl Partition { + const FLAGS_HAS_ID: u32 = 0b1; + const FLAGS_LINK_TYPE_A_PARTITION: u32 = 0b01 << 1; + const FLAGS_LINK_TYPE_OWNER: u32 = 0b10 << 1; + const FLAGS_LINK_MASK: u32 = 0b111111 << 1; + const FLAGS_HAS_NAME: u32 = 0b1 << 12; + const FLAGS_HAS_EXTRA_FAMILIES_SHIFT: u8 = 7; + const FLAGS_HAS_EXTRA_FAMILIES_MASK: u32 = 0b11 << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT; + + /// Create a new partition, with the given start and end sectors. + /// + /// It defaults to no permissions. + pub const fn new(first_sector: u16, last_sector: u16) -> Self { + // 0x2000 sectors of 4 KiB is 32 MiB, which is the total XIP area + core::assert!(first_sector < 0x2000); + core::assert!(last_sector < 0x2000); + core::assert!(first_sector <= last_sector); + Self { + permissions_and_location: (last_sector as u32) << 13 | first_sector as u32, + permissions_and_flags: 0, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Create a new partition from run-time values. + /// + /// Get these from the ROM function `get_partition_table_info` with an argument of `PARTITION_LOCATION_AND_FLAGS`. + pub const fn from_raw(permissions_and_location: u32, permissions_and_flags: u32) -> Self { + Self { + permissions_and_location, + permissions_and_flags, + id: None, + extra_families: [0; 4], + extra_families_len: 0, + name: [0; 128], + } + } + + /// Add a permission + pub const fn with_permission(self, permission: Permission) -> Self { + Self { + permissions_and_location: self.permissions_and_location | permission as u32, + permissions_and_flags: self.permissions_and_flags | permission as u32, + ..self + } + } + + /// Set the name of the partition + pub const fn with_name(self, name: &str) -> Self { + let mut new_name = [0u8; 128]; + let name = name.as_bytes(); + let mut idx = 0; + new_name[0] = name.len() as u8; + while idx < name.len() { + new_name[idx + 1] = name[idx]; + idx += 1; + } + Self { + name: new_name, + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_NAME, + ..self + } + } + + /// Set the extra families for the partition. + /// + /// You can supply up to four. + pub const fn with_extra_families(self, extra_families: &[u32]) -> Self { + core::assert!(extra_families.len() <= 4); + let mut new_extra_families = [0u32; 4]; + let mut idx = 0; + while idx < extra_families.len() { + new_extra_families[idx] = extra_families[idx]; + idx += 1; + } + Self { + extra_families: new_extra_families, + extra_families_len: extra_families.len(), + permissions_and_flags: (self.permissions_and_flags & !Self::FLAGS_HAS_EXTRA_FAMILIES_MASK) + | (extra_families.len() as u32) << Self::FLAGS_HAS_EXTRA_FAMILIES_SHIFT, + ..self + } + } + + /// Set the ID + pub const fn with_id(self, id: u64) -> Self { + Self { + id: Some(id), + permissions_and_flags: self.permissions_and_flags | Self::FLAGS_HAS_ID, + ..self + } + } + + /// Add a link + pub const fn with_link(self, link: Link) -> Self { + let mut new_flags = self.permissions_and_flags & !Self::FLAGS_LINK_MASK; + match link { + Link::Nothing => {} + Link::ToA { partition_idx } => { + core::assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_A_PARTITION; + new_flags |= (partition_idx as u32) << 3; + } + Link::ToOwner { partition_idx } => { + core::assert!(partition_idx < 16); + new_flags |= Self::FLAGS_LINK_TYPE_OWNER; + new_flags |= (partition_idx as u32) << 3; + } + } + Self { + permissions_and_flags: new_flags, + ..self + } + } + + /// Set a flag + pub const fn with_flag(self, flag: PartitionFlag) -> Self { + Self { + permissions_and_flags: self.permissions_and_flags | flag as u32, + ..self + } + } + + /// Get the partition start and end + /// + /// The offsets are in 4 KiB sectors, inclusive. + pub fn get_first_last_sectors(&self) -> (u16, u16) { + ( + (self.permissions_and_location & 0x0000_1FFF) as u16, + ((self.permissions_and_location >> 13) & 0x0000_1FFF) as u16, + ) + } + + /// Get the partition start and end + /// + /// The offsets are in bytes, inclusive. + pub fn get_first_last_bytes(&self) -> (u32, u32) { + let (first, last) = self.get_first_last_sectors(); + (u32::from(first) * 4096, (u32::from(last) * 4096) + 4095) + } + + /// Check if it has a permission + pub fn has_permission(&self, permission: Permission) -> bool { + let mask = permission as u32; + (self.permissions_and_flags & mask) != 0 + } + + /// Get which extra families are allowed in this partition + pub fn get_extra_families(&self) -> &[u32] { + &self.extra_families[0..self.extra_families_len] + } + + /// Get the name of the partition + /// + /// Returns `None` if there's no name, or the name is not valid UTF-8. + pub fn get_name(&self) -> Option<&str> { + let len = self.name[0] as usize; + if len == 0 { + None + } else { + core::str::from_utf8(&self.name[1..=len]).ok() + } + } + + /// Get the ID + pub fn get_id(&self) -> Option { + self.id + } + + /// Check if this partition is linked + pub fn get_link(&self) -> Link { + if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_A_PARTITION) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToA { partition_idx } + } else if (self.permissions_and_flags & Self::FLAGS_LINK_TYPE_OWNER) != 0 { + let partition_idx = ((self.permissions_and_flags >> 3) & 0x0F) as u8; + Link::ToOwner { partition_idx } + } else { + Link::Nothing + } + } + + /// Check if the partition has a flag set + pub fn has_flag(&self, flag: PartitionFlag) -> bool { + let mask = flag as u32; + (self.permissions_and_flags & mask) != 0 + } +} + +impl core::fmt::Display for Partition { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let (first, last) = self.get_first_last_bytes(); + write!( + f, + "{:#010x}..{:#010x} S:{}{} NS:{}{} B:{}{}", + first, + last, + if self.has_permission(Permission::SecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::SecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::NonSecureWrite) { + 'W' + } else { + '_' + }, + if self.has_permission(Permission::BootRead) { + 'R' + } else { + '_' + }, + if self.has_permission(Permission::BootWrite) { + 'W' + } else { + '_' + } + ) + } +} + +/// Describes a partition table. +/// +/// Don't store this as a static - make sure you convert it to a block. +#[derive(Clone)] +pub struct PartitionTableBlock { + /// This must look like a block, including the 1 word header and the 3 word footer. + contents: [u32; PARTITION_TABLE_MAX_ITEMS], + /// This value doesn't include the 1 word header or the 3 word footer + num_items: usize, +} + +impl PartitionTableBlock { + /// Create an empty Block, big enough for a partition table. + /// + /// At a minimum you need to call [`Self::add_partition_item`]. + pub const fn new() -> PartitionTableBlock { + let mut contents = [0; PARTITION_TABLE_MAX_ITEMS]; + contents[0] = BLOCK_MARKER_START; + contents[1] = item_last(0); + contents[2] = 0; + contents[3] = BLOCK_MARKER_END; + PartitionTableBlock { contents, num_items: 0 } + } + + /// Add a partition to the partition table + pub const fn add_partition_item(self, unpartitioned: UnpartitionedSpace, partitions: &[Partition]) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item header space (we fill this in later) + let header_idx = idx; + new_table.contents[idx] = 0; + idx += 1; + + // 2. unpartitioned space flags + // + // (the location of unpartition space is not recorded here - it is + // inferred because the unpartitioned space is where the partitions are + // not) + new_table.contents[idx] = unpartitioned.permissions_and_flags; + idx += 1; + + // 3. partition info + + let mut partition_no = 0; + while partition_no < partitions.len() { + // a. permissions_and_location (4K units) + new_table.contents[idx] = partitions[partition_no].permissions_and_location; + idx += 1; + + // b. permissions_and_flags + new_table.contents[idx] = partitions[partition_no].permissions_and_flags; + idx += 1; + + // c. ID + if let Some(id) = partitions[partition_no].id { + new_table.contents[idx] = id as u32; + new_table.contents[idx + 1] = (id >> 32) as u32; + idx += 2; + } + + // d. Extra Families + let mut extra_families_idx = 0; + while extra_families_idx < partitions[partition_no].extra_families_len { + new_table.contents[idx] = partitions[partition_no].extra_families[extra_families_idx]; + idx += 1; + extra_families_idx += 1; + } + + // e. Name + let mut name_idx = 0; + while name_idx < partitions[partition_no].name[0] as usize { + let name_chunk = [ + partitions[partition_no].name[name_idx], + partitions[partition_no].name[name_idx + 1], + partitions[partition_no].name[name_idx + 2], + partitions[partition_no].name[name_idx + 3], + ]; + new_table.contents[idx] = u32::from_le_bytes(name_chunk); + name_idx += 4; + idx += 1; + } + + partition_no += 1; + } + + let len = idx - header_idx; + new_table.contents[header_idx] = item_generic_2bs(partitions.len() as u8, len as u16, ITEM_2BS_PARTITION_TABLE); + + // 7. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a version number to the partition table + pub const fn with_version(self, major: u16, minor: u16) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. add item + new_table.contents[idx] = item_generic_2bs(0, 2, ITEM_1BS_VERSION); + idx += 1; + new_table.contents[idx] = (major as u32) << 16 | minor as u32; + idx += 1; + + // 2. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } + + /// Add a SHA256 hash of the Block + /// + /// Adds a `HASH_DEF` covering all the previous items in the Block, and a + /// `HASH_VALUE` with a SHA-256 hash of them. + pub const fn with_sha256(self) -> Self { + let mut new_table = PartitionTableBlock::new(); + let mut idx = 0; + // copy over old table, with the header but not the footer + while idx < self.num_items + 1 { + new_table.contents[idx] = self.contents[idx]; + idx += 1; + } + + // 1. HASH_DEF says what is hashed + new_table.contents[idx] = item_generic_2bs(1, 2, ITEM_2BS_HASH_DEF); + idx += 1; + // we're hashing all the previous contents - including this line. + new_table.contents[idx] = (idx + 1) as u32; + idx += 1; + + // calculate hash over prior contents + let input = unsafe { core::slice::from_raw_parts(new_table.contents.as_ptr() as *const u8, idx * 4) }; + let hash: [u8; 32] = sha2_const_stable::Sha256::new().update(input).finalize(); + + // 2. HASH_VALUE contains the hash + new_table.contents[idx] = item_generic_2bs(0, 9, ITEM_1BS_HASH_VALUE); + idx += 1; + + let mut hash_idx = 0; + while hash_idx < hash.len() { + new_table.contents[idx] = u32::from_le_bytes([ + hash[hash_idx], + hash[hash_idx + 1], + hash[hash_idx + 2], + hash[hash_idx + 3], + ]); + idx += 1; + hash_idx += 4; + } + + // 3. New Footer + new_table.contents[idx] = item_last(idx as u16 - 1); + new_table.contents[idx + 1] = 0; + new_table.contents[idx + 2] = BLOCK_MARKER_END; + + // ignore the header + new_table.num_items = idx - 1; + new_table + } +} + +impl Default for PartitionTableBlock { + fn default() -> Self { + Self::new() + } +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum PartitionFlag { + NotBootableArm = 1 << 9, + NotBootableRiscv = 1 << 10, + Uf2DownloadAbNonBootableOwnerAffinity = 1 << 11, + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Flags that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +#[allow(missing_docs)] +pub enum UnpartitionedFlag { + Uf2DownloadNoReboot = 1 << 13, + AcceptsDefaultFamilyRp2040 = 1 << 14, + AcceptsDefaultFamilyAbsolute = 1 << 15, + AcceptsDefaultFamilyData = 1 << 16, + AcceptsDefaultFamilyRp2350ArmS = 1 << 17, + AcceptsDefaultFamilyRp2350Riscv = 1 << 18, + AcceptsDefaultFamilyRp2350ArmNs = 1 << 19, +} + +/// Kinds of linked partition +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Link { + /// Not linked to anything + Nothing, + /// This is a B partition - link to our A partition. + ToA { + /// The index of our matching A partition. + partition_idx: u8, + }, + /// Link to the partition that owns this one. + ToOwner { + /// The idx of our owner + partition_idx: u8, + }, +} + +/// Permissions that a Partition can have +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum Permission { + /// Can be read in Secure Mode + /// + /// Corresponds to `PERMISSION_S_R_BITS` in the Pico SDK + SecureRead = 1 << 26, + /// Can be written in Secure Mode + /// + /// Corresponds to `PERMISSION_S_W_BITS` in the Pico SDK + SecureWrite = 1 << 27, + /// Can be read in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_R_BITS` in the Pico SDK + NonSecureRead = 1 << 28, + /// Can be written in Non-Secure Mode + /// + /// Corresponds to `PERMISSION_NS_W_BITS` in the Pico SDK + NonSecureWrite = 1 << 29, + /// Can be read in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_R_BITS` in the Pico SDK + BootRead = 1 << 30, + /// Can be written in Non-Secure Bootloader mode + /// + /// Corresponds to `PERMISSION_NSBOOT_W_BITS` in the Pico SDK + BootWrite = 1 << 31, +} + +impl Permission { + /// Is this permission bit set this in this bitmask? + pub const fn is_in(self, mask: u32) -> bool { + (mask & (self as u32)) != 0 + } +} + +/// The supported RP2350 CPU architectures +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Architecture { + /// Core is in Arm Cortex-M33 mode + Arm, + /// Core is in RISC-V / Hazard3 mode + Riscv, +} + +/// The kinds of Secure Boot we support +#[derive(Debug, Copy, Clone)] +pub enum Security { + /// Security mode not given + Unspecified, + /// Start in Non-Secure mode + NonSecure, + /// Start in Secure mode + Secure, +} + +/// Make an item containing a tag, 1 byte length and two extra bytes. +/// +/// The `command` arg should contain `1BS` +pub const fn item_generic_1bs(value: u16, length: u8, command: u8) -> u32 { + ((value as u32) << 16) | ((length as u32) << 8) | (command as u32) +} + +/// Make an item containing a tag, 2 byte length and one extra byte. +/// +/// The `command` arg should contain `2BS` +pub const fn item_generic_2bs(value: u8, length: u16, command: u8) -> u32 { + ((value as u32) << 24) | ((length as u32) << 8) | (command as u32) +} + +/// Create Image Type item, of type IGNORED. +pub const fn item_ignored() -> u32 { + item_generic_2bs(0, 1, ITEM_2BS_IGNORED) +} + +/// Create Image Type item, of type INVALID. +pub const fn item_image_type_invalid() -> u32 { + let value = IMAGE_TYPE_INVALID; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type DATA. +pub const fn item_image_type_data() -> u32 { + let value = IMAGE_TYPE_DATA; + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create Image Type item, of type EXE. +pub const fn item_image_type_exe(security: Security, arch: Architecture) -> u32 { + let mut value = IMAGE_TYPE_EXE | IMAGE_TYPE_EXE_CHIP_RP2350; + + match arch { + Architecture::Arm => { + value |= IMAGE_TYPE_EXE_CPU_ARM; + } + Architecture::Riscv => { + value |= IMAGE_TYPE_EXE_CPU_RISCV; + } + } + + match security { + Security::Unspecified => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_UNSPECIFIED, + Security::NonSecure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_NS, + Security::Secure => value |= IMAGE_TYPE_EXE_TYPE_SECURITY_S, + } + + item_generic_1bs(value, 1, ITEM_1BS_IMAGE_TYPE) +} + +/// Create a Block Last item. +pub const fn item_last(length: u16) -> u32 { + item_generic_2bs(0, length, ITEM_2BS_LAST) +} + +/// Create a Vector Table item. +/// +/// This is only allowed on Arm systems. +pub const fn item_vector_table(table_ptr: u32) -> [u32; 2] { + [item_generic_1bs(0, 2, ITEM_1BS_VECTOR_TABLE), table_ptr] +} + +/// Create an Entry Point item. +pub const fn item_entry_point(entry_point: u32, initial_sp: u32) -> [u32; 3] { + [item_generic_1bs(0, 3, ITEM_1BS_ENTRY_POINT), entry_point, initial_sp] +} + +/// Create an Rolling Window item. +/// +/// The delta is the number of bytes into the image that 0x10000000 should +/// be mapped. +pub const fn item_rolling_window(delta: u32) -> [u32; 2] { + [item_generic_1bs(0, 3, ITEM_1BS_ROLLING_WINDOW_DELTA), delta] +} + +#[cfg(test)] +mod test { + use super::*; + + /// I used this JSON, with `picotool partition create`: + /// + /// ```json + /// { + /// "version": [1, 0], + /// "unpartitioned": { + /// "families": ["absolute"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// "partitions": [ + /// { + /// "name": "A", + /// "id": 0, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// } + /// }, + /// { + /// "name": "B", + /// "id": 1, + /// "size": "2044K", + /// "families": ["rp2350-arm-s", "rp2350-riscv"], + /// "permissions": { + /// "secure": "rw", + /// "nonsecure": "rw", + /// "bootloader": "rw" + /// }, + /// "link": ["a", 0] + /// } + /// ] + /// } + /// ``` + #[test] + fn make_hashed_partition_table() { + let table = PartitionTableBlock::new() + .add_partition_item( + UnpartitionedSpace::new() + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_flag(UnpartitionedFlag::AcceptsDefaultFamilyAbsolute), + &[ + Partition::new(2, 512) + .with_id(0) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("A"), + Partition::new(513, 1023) + .with_id(1) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350ArmS) + .with_flag(PartitionFlag::AcceptsDefaultFamilyRp2350Riscv) + .with_link(Link::ToA { partition_idx: 0 }) + .with_permission(Permission::SecureRead) + .with_permission(Permission::SecureWrite) + .with_permission(Permission::NonSecureRead) + .with_permission(Permission::NonSecureWrite) + .with_permission(Permission::BootRead) + .with_permission(Permission::BootWrite) + .with_name("B"), + ], + ) + .with_version(1, 0) + .with_sha256(); + let expected = &[ + 0xffffded3, // start + 0x02000c0a, // Item = PARTITION_TABLE + 0xfc008000, // Unpartitioned Space - permissions_and_flags + 0xfc400002, // Partition 0 - permissions_and_location (512 * 4096, 2 * 4096) + 0xfc061001, // permissions_and_flags HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000000, // ID + 0x00000000, // ID + 0x00004101, // Name ("A") + 0xfc7fe201, // Partition 1 - permissions_and_location (1023 * 4096, 513 * 4096) + 0xfc061003, // permissions_and_flags LINKA(0) | HAS_ID | HAS_NAME | ARM-S | RISC-V + 0x00000001, // ID + 0x00000000, // ID + 0x00004201, // Name ("B") + 0x00000248, // Item = Version + 0x00010000, // 0, 1 + 0x01000247, // HASH_DEF with 2 words, and SHA256 hash + 0x00000011, // 17 words hashed + 0x0000094b, // HASH_VALUE with 9 words + 0x1945cdad, // Hash word 0 + 0x6b5f9773, // Hash word 1 + 0xe2bf39bd, // Hash word 2 + 0xb243e599, // Hash word 3 + 0xab2f0e9a, // Hash word 4 + 0x4d5d6d0b, // Hash word 5 + 0xf973050f, // Hash word 6 + 0x5ab6dadb, // Hash word 7 + 0x000019ff, // Last Item + 0x00000000, // Block Loop Next Offset + 0xab123579, // End + ]; + core::assert_eq!( + &table.contents[..29], + expected, + "{:#010x?}\n != \n{:#010x?}", + &table.contents[0..29], + expected, + ); + } +} diff --git a/embassy-rp-fork/src/bootsel.rs b/embassy-rp-fork/src/bootsel.rs new file mode 100644 index 0000000..b24b98c --- /dev/null +++ b/embassy-rp-fork/src/bootsel.rs @@ -0,0 +1,82 @@ +//! Boot Select button +//! +//! The RP2040 rom supports a BOOTSEL button that is used to enter the USB bootloader +//! if held during reset. To avoid wasting GPIO pins, the button is multiplexed onto +//! the CS pin of the QSPI flash, but that makes it somewhat expensive and complicated +//! to utilize outside of the rom's bootloader. +//! +//! This module provides functionality to poll BOOTSEL from an embassy application. + +use crate::Peri; +use crate::flash::in_ram; + +/// Reads the BOOTSEL button. Returns true if the button is pressed. +/// +/// Reading isn't cheap, as this function waits for core 1 to finish it's current +/// task and for any DMAs from flash to complete +pub fn is_bootsel_pressed(_p: Peri<'_, crate::peripherals::BOOTSEL>) -> bool { + let mut cs_status = Default::default(); + + unsafe { in_ram(|| cs_status = ram_helpers::read_cs_status()) }.expect("Must be called from Core 0"); + + // bootsel is active low, so invert + !cs_status.infrompad() +} + +mod ram_helpers { + use rp_pac::io::regs::GpioStatus; + + /// Temporally reconfigures the CS gpio and returns the GpioStatus. + + /// This function runs from RAM so it can disable flash XIP. + /// + /// # Safety + /// + /// The caller must ensure flash is idle and will remain idle. + /// This function must live in ram. It uses inline asm to avoid any + /// potential calls to ABI functions that might be in flash. + #[inline(never)] + #[unsafe(link_section = ".data.ram_func")] + #[cfg(target_arch = "arm")] + pub unsafe fn read_cs_status() -> GpioStatus { + let result: u32; + + // Magic value, used as both OEOVER::DISABLE and delay loop counter + let magic = 0x2000; + + core::arch::asm!( + ".equiv GPIO_STATUS, 0x0", + ".equiv GPIO_CTRL, 0x4", + + "ldr {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + // The BOOTSEL pulls the flash's CS line low though a 1K resistor. + // this is weak enough to avoid disrupting normal operation. + // But, if we disable CS's output drive and allow it to float... + "str {val}, [{cs_gpio}, $GPIO_CTRL]", + + // ...then wait for the state to settle... + "2:", // ~4000 cycle delay loop + "subs {val}, #8", + "bne 2b", + + // ...we can read the current state of bootsel + "ldr {val}, [{cs_gpio}, $GPIO_STATUS]", + + // Finally, restore CS to normal operation so XIP can continue + "str {orig_ctrl}, [{cs_gpio}, $GPIO_CTRL]", + + cs_gpio = in(reg) rp_pac::IO_QSPI.gpio(1).as_ptr(), + orig_ctrl = out(reg) _, + val = inout(reg) magic => result, + options(nostack), + ); + + core::mem::transmute(result) + } + + #[cfg(not(target_arch = "arm"))] + pub unsafe fn read_cs_status() -> GpioStatus { + unimplemented!() + } +} diff --git a/embassy-rp-fork/src/clocks.rs b/embassy-rp-fork/src/clocks.rs new file mode 100644 index 0000000..6e544ca --- /dev/null +++ b/embassy-rp-fork/src/clocks.rs @@ -0,0 +1,2186 @@ +//! # Clock configuration for the RP2040 and RP235x microcontrollers. +//! +//! # Clock Configuration API +//! +//! This module provides both high-level convenience functions and low-level manual +//! configuration options for the RP2040 clock system. +//! +//! ## High-Level Convenience Functions +//! +//! For most users, these functions provide an easy way to configure clocks: +//! +//! - `ClockConfig::system_freq(125_000_000)` - Set system clock to a specific frequency with automatic voltage scaling +//! - `ClockConfig::crystal(12_000_000)` - Default configuration with 12MHz crystal giving 125MHz system clock +//! +//! ## Manual Configuration +//! +//! For advanced users who need precise control: +//! +//! ```rust,ignore +//! // Start with default configuration and customize it +//! let mut config = ClockConfig::default(); +//! +//! // Set custom PLL parameters +//! config.xosc = Some(XoscConfig { +//! hz: 12_000_000, +//! sys_pll: Some(PllConfig { +//! refdiv: 1, +//! fbdiv: 200, +//! post_div1: 6, +//! post_div2: 2, +//! }), +//! // ... other fields +//! }); +//! +//! // Set voltage for overclocking +//! config.core_voltage = CoreVoltage::V1_15; +//! ``` +//! +//! ## Examples +//! +//! ### Standard 125MHz (rp2040) or 150Mhz (rp235x) configuration +//! ```rust,ignore +//! let config = ClockConfig::crystal(12_000_000); +//! ``` +//! +//! Or using the default configuration: +//! ```rust,ignore +//! let config = ClockConfig::default(); +//! ``` +//! +//! ### Overclock to 200MHz +//! ```rust,ignore +//! let config = ClockConfig::system_freq(200_000_000); +//! ``` +//! +//! ### Manual configuration for advanced scenarios +//! ```rust,ignore +//! use embassy_rp::clocks::{ClockConfig, XoscConfig, PllConfig, CoreVoltage}; +//! +//! // Start with defaults and customize +//! let mut config = ClockConfig::default(); +//! config.core_voltage = CoreVoltage::V1_15; +//! // Set other parameters as needed... +//! ``` + +use core::arch::asm; +use core::marker::PhantomData; +#[cfg(feature = "rp2040")] +use core::sync::atomic::AtomicU16; +use core::sync::atomic::{AtomicU32, Ordering}; + +use pac::clocks::vals::*; + +use crate::gpio::{AnyPin, SealedPin}; +use crate::pac::common::{RW, Reg}; +use crate::{Peri, pac, reset}; + +// NOTE: all gpin handling is commented out for future reference. +// gpin is not usually safe to use during the boot init() call, so it won't +// be very useful until we have runtime clock reconfiguration. once this +// happens we can resurrect the commented-out gpin bits. + +/// Clock error types. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ClockError { + /// PLL failed to lock within the timeout period. + PllLockTimedOut, + /// Could not find valid PLL parameters for system clock. + InvalidPllParameters, + /// Reading the core voltage failed due to an unexpected value in the register. + UnexpectedCoreVoltageRead, +} + +struct Clocks { + xosc: AtomicU32, + sys: AtomicU32, + reference: AtomicU32, + pll_sys: AtomicU32, + pll_usb: AtomicU32, + usb: AtomicU32, + adc: AtomicU32, + // See above re gpin handling being commented out + // gpin0: AtomicU32, + // gpin1: AtomicU32, + rosc: AtomicU32, + peri: AtomicU32, + #[cfg(feature = "rp2040")] + rtc: AtomicU16, +} + +static CLOCKS: Clocks = Clocks { + xosc: AtomicU32::new(0), + sys: AtomicU32::new(0), + reference: AtomicU32::new(0), + pll_sys: AtomicU32::new(0), + pll_usb: AtomicU32::new(0), + usb: AtomicU32::new(0), + adc: AtomicU32::new(0), + // See above re gpin handling being commented out + // gpin0: AtomicU32::new(0), + // gpin1: AtomicU32::new(0), + rosc: AtomicU32::new(0), + peri: AtomicU32::new(0), + #[cfg(feature = "rp2040")] + rtc: AtomicU16::new(0), +}; + +/// Peripheral clock sources. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PeriClkSrc { + /// SYS. + Sys = ClkPeriCtrlAuxsrc::CLK_SYS as _, + /// PLL SYS. + PllSys = ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// PLL USB. + PllUsb = ClkPeriCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// ROSC. + Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out + // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// Core voltage regulator settings. +/// +/// The voltage regulator can be configured for different output voltages. +/// Higher voltages allow for higher clock frequencies but increase power consumption and heat. +#[cfg(feature = "rp2040")] +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreVoltage { + /// 0.80V + V0_80 = 0b0000, + /// 0.85V + V0_85 = 0b0110, + /// 0.90V + V0_90 = 0b0111, + /// 0.95V + V0_95 = 0b1000, + /// 1.00V + V1_00 = 0b1001, + /// 1.05V + V1_05 = 0b1010, + /// 1.10V - Default voltage level + V1_10 = 0b1011, + /// 1.15V - Required for overclocking to 133-200MHz + V1_15 = 0b1100, + /// 1.20V + V1_20 = 0b1101, + /// 1.25V + V1_25 = 0b1110, + /// 1.30V + V1_30 = 0b1111, +} + +/// Core voltage regulator settings. +/// +/// The voltage regulator can be configured for different output voltages. +/// Higher voltages allow for higher clock frequencies but increase power consumption and heat. +/// +/// **Note**: The maximum voltage is 1.30V, unless unlocked by setting unless the voltage limit +/// is disabled using the disable_voltage_limit field in the vreg_ctrl register. For lack of practical use at this +/// point in time, this is not implemented here. So the maximum voltage in this enum is 1.30V for now. +#[cfg(feature = "_rp235x")] +#[repr(u8)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CoreVoltage { + /// 0.55V + V0_55 = 0b00000, + /// 0.60V + V0_60 = 0b00001, + /// 0.65V + V0_65 = 0b00010, + /// 0.70V + V0_70 = 0b00011, + /// 0.75V + V0_75 = 0b00100, + /// 0.80V + V0_80 = 0b00101, + /// 0.85V + V0_85 = 0b00110, + /// 0.90V + V0_90 = 0b00111, + /// 0.95V + V0_95 = 0b01000, + /// 1.00V + V1_00 = 0b01001, + /// 1.05V + V1_05 = 0b01010, + /// 1.10V - Default voltage level + V1_10 = 0b01011, + /// 1.15V + V1_15 = 0b01100, + /// 1.20V + V1_20 = 0b01101, + /// 1.25V + V1_25 = 0b01110, + /// 1.30V + V1_30 = 0b01111, +} + +impl CoreVoltage { + /// Get the recommended Brown-Out Detection (BOD) setting for this voltage. + /// Sets the BOD threshold to approximately 80% of the core voltage. + fn recommended_bod(self) -> u8 { + #[cfg(feature = "rp2040")] + match self { + CoreVoltage::V0_80 => 0b0100, // 0.645V (~81% of 0.80V) + CoreVoltage::V0_85 => 0b0101, // 0.688V (~81% of 0.85V) + CoreVoltage::V0_90 => 0b0110, // 0.731V (~81% of 0.90V) + CoreVoltage::V0_95 => 0b0111, // 0.774V (~81% of 0.95V) + CoreVoltage::V1_00 => 0b1000, // 0.817V (~82% of 1.00V) + CoreVoltage::V1_05 => 0b1000, // 0.817V (~78% of 1.05V) + CoreVoltage::V1_10 => 0b1001, // 0.860V (~78% of 1.10V), the default + CoreVoltage::V1_15 => 0b1010, // 0.903V (~79% of 1.15V) + CoreVoltage::V1_20 => 0b1011, // 0.946V (~79% of 1.20V) + CoreVoltage::V1_25 => 0b1100, // 0.989V (~79% of 1.25V) + CoreVoltage::V1_30 => 0b1101, // 1.032V (~79% of 1.30V) + } + #[cfg(feature = "_rp235x")] + match self { + CoreVoltage::V0_55 => 0b00001, // 0.516V (~94% of 0.55V) + CoreVoltage::V0_60 => 0b00010, // 0.559V (~93% of 0.60V) + CoreVoltage::V0_65 => 0b00011, // 0.602V (~93% of 0.65V) + CoreVoltage::V0_70 => 0b00011, // 0.602V (~86% of 0.70V) + CoreVoltage::V0_75 => 0b00100, // 0.645V (~86% of 0.75V) + CoreVoltage::V0_80 => 0b00101, // 0.688V (~86% of 0.80V) + CoreVoltage::V0_85 => 0b00110, // 0.731V (~86% of 0.85V) + CoreVoltage::V0_90 => 0b00110, // 0.731V (~81% of 0.90V) + CoreVoltage::V0_95 => 0b00111, // 0.774V (~81% of 0.95V) + CoreVoltage::V1_00 => 0b01000, // 0.817V (~82% of 1.00V) + CoreVoltage::V1_05 => 0b01000, // 0.817V (~78% of 1.05V) + CoreVoltage::V1_10 => 0b01001, // 0.860V (~78% of 1.10V), the default + CoreVoltage::V1_15 => 0b01001, // 0.860V (~75% of 1.15V) + CoreVoltage::V1_20 => 0b01010, // 0.903V (~75% of 1.20V) + CoreVoltage::V1_25 => 0b01010, // 0.903V (~72% of 1.25V) + CoreVoltage::V1_30 => 0b01011, // 0.946V (~73% of 1.30V) + // all others: 0.946V (see CoreVoltage: we do not support setting Voltages higher than 1.30V at this point) + } + } +} + +/// Clock configuration. +#[non_exhaustive] +pub struct ClockConfig { + /// Ring oscillator configuration. + pub rosc: Option, + /// External oscillator configuration. + pub xosc: Option, + /// Reference clock configuration. + pub ref_clk: RefClkConfig, + /// System clock configuration. + pub sys_clk: SysClkConfig, + /// Peripheral clock source configuration. + pub peri_clk_src: Option, + /// USB clock configuration. + pub usb_clk: Option, + /// ADC clock configuration. + pub adc_clk: Option, + /// RTC clock configuration. + #[cfg(feature = "rp2040")] + pub rtc_clk: Option, + /// Core voltage scaling. Defaults to 1.10V. + pub core_voltage: CoreVoltage, + /// Voltage stabilization delay in microseconds. + /// If not set, defaults will be used based on voltage level. + pub voltage_stabilization_delay_us: Option, + // See above re gpin handling being commented out + // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, + // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, +} + +impl Default for ClockConfig { + /// Creates a minimal default configuration with safe values. + /// + /// This configuration uses the ring oscillator (ROSC) as the clock source + /// and sets minimal defaults that guarantee a working system. It's intended + /// as a starting point for manual configuration. + /// + /// Most users should use one of the more specific configuration functions: + /// - `ClockConfig::crystal()` - Standard configuration with external crystal + /// - `ClockConfig::rosc()` - Configuration using only the internal oscillator + /// - `ClockConfig::system_freq()` - Configuration for a specific system frequency + fn default() -> Self { + Self { + rosc: None, + xosc: None, + ref_clk: RefClkConfig { + src: RefClkSrc::Rosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::Rosc, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: None, + usb_clk: None, + adc_clk: None, + #[cfg(feature = "rp2040")] + rtc_clk: None, + core_voltage: CoreVoltage::V1_10, + voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out + // gpin0: None, + // gpin1: None, + } + } +} + +impl ClockConfig { + /// Clock configuration derived from external crystal. + /// + /// This uses default settings for most parameters, suitable for typical use cases. + /// For manual control of PLL parameters, use `new_manual()` or modify the struct fields directly. + pub fn crystal(crystal_hz: u32) -> Self { + Self { + rosc: Some(RoscConfig { + hz: 6_500_000, + range: RoscRange::Medium, + drive_strength: [0; 8], + div: 16, + }), + xosc: Some(XoscConfig { + hz: crystal_hz, + sys_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 125, + #[cfg(feature = "rp2040")] + post_div1: 6, + #[cfg(feature = "_rp235x")] + post_div1: 5, + post_div2: 2, + }), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + delay_multiplier: 128, + }), + ref_clk: RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Sys), + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + usb_clk: Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }), + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }), + // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + #[cfg(feature = "rp2040")] + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }), + core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) + voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out + // gpin0: None, + // gpin1: None, + } + } + + /// Clock configuration from internal oscillator. + pub fn rosc() -> Self { + Self { + rosc: Some(RoscConfig { + hz: 140_000_000, + range: RoscRange::High, + drive_strength: [0; 8], + div: 1, + }), + xosc: None, + ref_clk: RefClkConfig { + src: RefClkSrc::Rosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::Rosc, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Rosc), + usb_clk: None, + // CLK ADC = ROSC (140MHz) / 3 ≅ 48MHz + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::Rosc, + div: 3, + phase: 0, + }), + // CLK RTC = ROSC (140MHz) / 2986.667969 ≅ 46875Hz + #[cfg(feature = "rp2040")] + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::Rosc, + div_int: 2986, + div_frac: 171, + phase: 0, + }), + core_voltage: CoreVoltage::V1_10, // Use hardware default (1.10V) + voltage_stabilization_delay_us: None, + // See above re gpin handling being commented out + // gpin0: None, + // gpin1: None, + } + } + + /// Configure clocks derived from an external crystal with specific system frequency. + /// + /// This function calculates optimal PLL parameters to achieve the requested system + /// frequency. This only works for the usual 12MHz crystal. In case a different crystal is used, + /// You will have to set the PLL parameters manually. + /// + /// # Arguments + /// + /// * `sys_freq_hz` - The desired system clock frequency in Hz + /// + /// # Returns + /// + /// A ClockConfig configured to achieve the requested system frequency using the + /// the usual 12Mhz crystal, or an error if no valid parameters can be found. + /// + /// # Note on core voltage: + /// + /// **For RP2040**: + /// To date the only officially documented core voltages (see Datasheet section 2.15.3.1. Instances) are: + /// - Up to 133MHz: V1_10 (default) + /// - Above 133MHz: V1_15, but in the context of the datasheet covering reaching up to 200Mhz + /// That way all other frequencies below 133MHz or above 200MHz are not explicitly documented and not covered here. + /// In case You want to go below 133MHz or above 200MHz and want a different voltage, You will have to set that manually and with caution. + /// + /// **For RP235x**: + /// At this point in time there is no official manufacturer endorsement for running the chip on other core voltages and/or other clock speeds than the defaults. + /// Using this function is experimental and may not work as expected or even damage the chip. + /// + /// # Returns + /// + /// A Result containing either the configured ClockConfig or a ClockError. + pub fn system_freq(hz: u32) -> Result { + // Start with the standard configuration from crystal() + const DEFAULT_CRYSTAL_HZ: u32 = 12_000_000; + let mut config = Self::crystal(DEFAULT_CRYSTAL_HZ); + + // No need to modify anything if target frequency is already 125MHz + // (which is what crystal() configures by default) + #[cfg(feature = "rp2040")] + if hz == 125_000_000 { + return Ok(config); + } + #[cfg(feature = "_rp235x")] + if hz == 150_000_000 { + return Ok(config); + } + + // Find optimal PLL parameters for the requested frequency + let sys_pll_params = find_pll_params(DEFAULT_CRYSTAL_HZ, hz).ok_or(ClockError::InvalidPllParameters)?; + + // Replace the sys_pll configuration with our custom parameters + if let Some(xosc) = &mut config.xosc { + xosc.sys_pll = Some(sys_pll_params); + } + + // Set the voltage scale based on the target frequency + // Higher frequencies require higher voltage + #[cfg(feature = "rp2040")] + { + config.core_voltage = match hz { + freq if freq > 133_000_000 => CoreVoltage::V1_15, + _ => CoreVoltage::V1_10, // Use default voltage (V1_10) + }; + } + #[cfg(feature = "_rp235x")] + { + config.core_voltage = match hz { + // There is no official support for running the chip on other core voltages and/or other clock speeds than the defaults. + // So for now we have not way of knowing what the voltage should be. Change this if the manufacturer provides more information. + _ => CoreVoltage::V1_10, // Use default voltage (V1_10) + }; + } + + Ok(config) + } + + /// Configure with manual PLL settings for full control over system clock + /// + /// This method provides a simple way to configure the system with custom PLL parameters + /// without needing to understand the full nested configuration structure. + /// + /// # Arguments + /// + /// * `xosc_hz` - The frequency of the external crystal in Hz + /// * `pll_config` - The PLL configuration parameters to achieve desired frequency + /// * `core_voltage` - Voltage scaling for overclocking (required for >133MHz) + /// + /// # Returns + /// + /// A ClockConfig configured with the specified PLL parameters + /// + /// # Example + /// + /// ```rust,ignore + /// // Configure for 200MHz operation + /// let config = Config::default(); + /// config.clocks = ClockConfig::manual_pll( + /// 12_000_000, + /// PllConfig { + /// refdiv: 1, // Reference divider (12 MHz / 1 = 12 MHz) + /// fbdiv: 100, // Feedback divider (12 MHz * 100 = 1200 MHz VCO) + /// post_div1: 3, // First post divider (1200 MHz / 3 = 400 MHz) + /// post_div2: 2, // Second post divider (400 MHz / 2 = 200 MHz) + /// }, + /// CoreVoltage::V1_15 + /// ); + /// ``` + #[cfg(feature = "rp2040")] + pub fn manual_pll(xosc_hz: u32, pll_config: PllConfig, core_voltage: CoreVoltage) -> Self { + // Validate PLL parameters + assert!(pll_config.is_valid(xosc_hz), "Invalid PLL parameters"); + + let mut config = Self::default(); + + config.xosc = Some(XoscConfig { + hz: xosc_hz, + sys_pll: Some(pll_config), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + delay_multiplier: 128, + }); + + config.ref_clk = RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }; + + config.sys_clk = SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }; + + config.core_voltage = core_voltage; + config.peri_clk_src = Some(PeriClkSrc::Sys); + + // Set reasonable defaults for other clocks + config.usb_clk = Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }); + + config.adc_clk = Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }); + + config.rtc_clk = Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }); + + config + } +} + +/// ROSC freq range. +#[repr(u16)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RoscRange { + /// Low range. + Low = pac::rosc::vals::FreqRange::LOW.0, + /// Medium range (1.33x low) + Medium = pac::rosc::vals::FreqRange::MEDIUM.0, + /// High range (2x low) + High = pac::rosc::vals::FreqRange::HIGH.0, + /// Too high. Should not be used. + TooHigh = pac::rosc::vals::FreqRange::TOOHIGH.0, +} + +/// On-chip ring oscillator configuration. +pub struct RoscConfig { + /// Final frequency of the oscillator, after the divider has been applied. + /// The oscillator has a nominal frequency of 6.5MHz at medium range with + /// divider 16 and all drive strengths set to 0, other values should be + /// measured in situ. + pub hz: u32, + /// Oscillator range. + pub range: RoscRange, + /// Drive strength for oscillator. + pub drive_strength: [u8; 8], + /// Output divider. + pub div: u16, +} + +/// Crystal oscillator configuration. +pub struct XoscConfig { + /// Final frequency of the oscillator. + pub hz: u32, + /// Configuring PLL for the system clock. + pub sys_pll: Option, + /// Configuring PLL for the USB clock. + pub usb_pll: Option, + /// Multiplier for the startup delay. + pub delay_multiplier: u32, +} + +/// PLL configuration. +#[derive(Clone, Copy, Debug)] +pub struct PllConfig { + /// Reference divisor. + pub refdiv: u8, + /// Feedback divisor. + pub fbdiv: u16, + /// Output divisor 1. + pub post_div1: u8, + /// Output divisor 2. + pub post_div2: u8, +} + +impl PllConfig { + /// Calculate the output frequency for this PLL configuration + /// given an input frequency. + pub fn output_frequency(&self, input_hz: u32) -> u32 { + let ref_freq = input_hz / self.refdiv as u32; + let vco_freq = ref_freq * self.fbdiv as u32; + vco_freq / ((self.post_div1 * self.post_div2) as u32) + } + + /// Check if this PLL configuration is valid for the given input frequency. + pub fn is_valid(&self, input_hz: u32) -> bool { + // Check divisor constraints + if self.refdiv < 1 || self.refdiv > 63 { + return false; + } + if self.fbdiv < 16 || self.fbdiv > 320 { + return false; + } + if self.post_div1 < 1 || self.post_div1 > 7 { + return false; + } + if self.post_div2 < 1 || self.post_div2 > 7 { + return false; + } + if self.post_div2 > self.post_div1 { + return false; + } + + // Calculate reference frequency + let ref_freq = input_hz / self.refdiv as u32; + + // Check reference frequency range + if ref_freq < 5_000_000 || ref_freq > 800_000_000 { + return false; + } + + // Calculate VCO frequency + let vco_freq = ref_freq * self.fbdiv as u32; + + // Check VCO frequency range + vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000 + } +} + +/// Reference clock config. +pub struct RefClkConfig { + /// Reference clock source. + pub src: RefClkSrc, + /// Reference clock divider. + pub div: u8, +} + +/// Reference clock source. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RefClkSrc { + /// XOSC. + Xosc, + /// ROSC. + Rosc, + /// PLL USB. + PllUsb, + // See above re gpin handling being commented out + // Gpin0, + // Gpin1, +} + +/// SYS clock source. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SysClkSrc { + /// REF. + Ref, + /// PLL SYS. + PllSys, + /// PLL USB. + PllUsb, + /// ROSC. + Rosc, + /// XOSC. + Xosc, + // See above re gpin handling being commented out + // Gpin0, + // Gpin1, +} + +/// SYS clock config. +pub struct SysClkConfig { + /// SYS clock source. + pub src: SysClkSrc, + /// SYS clock divider. + #[cfg(feature = "rp2040")] + pub div_int: u32, + /// SYS clock fraction. + #[cfg(feature = "rp2040")] + pub div_frac: u8, + /// SYS clock divider. + #[cfg(feature = "_rp235x")] + pub div_int: u16, + /// SYS clock fraction. + #[cfg(feature = "_rp235x")] + pub div_frac: u16, +} + +/// USB clock source. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsbClkSrc { + /// PLL USB. + PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// PLL SYS. + PllSys = ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// ROSC. + Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out + // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// USB clock config. +pub struct UsbClkConfig { + /// USB clock source. + pub src: UsbClkSrc, + /// USB clock divider. + pub div: u8, + /// USB clock phase. + pub phase: u8, +} + +/// ADC clock source. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AdcClkSrc { + /// PLL USB. + PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// PLL SYS. + PllSys = ClkAdcCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// ROSC. + Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out + // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// ADC clock config. +pub struct AdcClkConfig { + /// ADC clock source. + pub src: AdcClkSrc, + /// ADC clock divider. + pub div: u8, + /// ADC clock phase. + pub phase: u8, +} + +/// RTC clock source. +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "rp2040")] +pub enum RtcClkSrc { + /// PLL USB. + PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// PLL SYS. + PllSys = ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS as _, + /// ROSC. + Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, + /// XOSC. + Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, + // See above re gpin handling being commented out + // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// RTC clock config. +#[cfg(feature = "rp2040")] +pub struct RtcClkConfig { + /// RTC clock source. + pub src: RtcClkSrc, + /// RTC clock divider. + pub div_int: u32, + /// RTC clock divider fraction. + pub div_frac: u8, + /// RTC clock phase. + pub phase: u8, +} + +/// Find valid PLL parameters (refdiv, fbdiv, post_div1, post_div2) for a target output frequency +/// based on the input frequency. +/// +/// This function searches for the best PLL configuration to achieve the requested target frequency +/// while staying within the VCO frequency range of 750MHz to 1800MHz. It prioritizes stability +/// over exact frequency matching by using larger divisors where possible. +/// +/// # Parameters +/// +/// * `input_hz`: The input frequency in Hz (typically the crystal frequency, e.g. 12MHz for th most common one used on rp2040 boards) +/// * `target_hz`: The desired output frequency in Hz (e.g. 125MHz for standard RP2040 operation) +/// +/// # Returns +/// +/// * `Some(PllConfig)` if valid parameters were found +/// * `None` if no valid parameters could be found for the requested combination +/// +/// # Example +/// +/// ```rust,ignore +/// // Find parameters for 133MHz system clock from 12MHz crystal +/// let pll_params = find_pll_params(12_000_000, 133_000_000).unwrap(); +/// ``` +fn find_pll_params(input_hz: u32, target_hz: u32) -> Option { + // Fixed reference divider for system PLL + const PLL_SYS_REFDIV: u8 = 1; + + // Calculate reference frequency + let reference_freq = input_hz as u64 / PLL_SYS_REFDIV as u64; + + // Start from highest fbdiv for better stability (like SDK does) + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; + + // Check VCO frequency is within valid range + if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { + continue; + } + + // Try all possible postdiv combinations starting from larger values + // (more conservative/stable approach) + for post_div1 in (1..=7).rev() { + for post_div2 in (1..=post_div1).rev() { + let out_freq = vco_freq / (post_div1 * post_div2); + + // Check if we get the exact target frequency without remainder + if out_freq == target_hz as u64 && (vco_freq % (post_div1 * post_div2) == 0) { + return Some(PllConfig { + refdiv: PLL_SYS_REFDIV, + fbdiv: fbdiv as u16, + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); + } + } + } + } + + // If we couldn't find an exact match, find the closest match + let mut best_config = None; + let mut min_diff = u32::MAX; + + for fbdiv in (16..=320).rev() { + let vco_freq = reference_freq * fbdiv; + + if vco_freq < 750_000_000 || vco_freq > 1_800_000_000 { + continue; + } + + for post_div1 in (1..=7).rev() { + for post_div2 in (1..=post_div1).rev() { + let out_freq = (vco_freq / (post_div1 * post_div2) as u64) as u32; + let diff = if out_freq > target_hz { + out_freq - target_hz + } else { + target_hz - out_freq + }; + + // If this is closer to the target, save it + if diff < min_diff { + min_diff = diff; + best_config = Some(PllConfig { + refdiv: PLL_SYS_REFDIV, + fbdiv: fbdiv as u16, + post_div1: post_div1 as u8, + post_div2: post_div2 as u8, + }); + } + } + } + } + + // Return the closest match if we found one + best_config +} + +/// safety: must be called exactly once at bootup +pub(crate) unsafe fn init(config: ClockConfig) { + // Reset everything except: + // - QSPI (we're using it to run this code!) + // - PLLs (it may be suicide if that's what's clocking us) + // - USB, SYSCFG (breaks usb-to-swd on core1) + // - RTC (else there would be no more time...) + let mut peris = reset::ALL_PERIPHERALS; + peris.set_io_qspi(false); + // peris.set_io_bank0(false); // might be suicide if we're clocked from gpin + peris.set_pads_qspi(false); + peris.set_pll_sys(false); + peris.set_pll_usb(false); + peris.set_usbctrl(false); + peris.set_syscfg(false); + //peris.set_rtc(false); + reset::reset(peris); + + // Disable resus that may be enabled from previous software + let c = pac::CLOCKS; + c.clk_sys_resus_ctrl() + .write_value(pac::clocks::regs::ClkSysResusCtrl(0)); + + // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. + c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != 1 {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1) {} + c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH)); + #[cfg(feature = "rp2040")] + while c.clk_ref_selected().read() != 1 {} + #[cfg(feature = "_rp235x")] + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1) {} + + // Reset the PLLs + let mut peris = reset::Peripherals(0); + peris.set_pll_sys(true); + peris.set_pll_usb(true); + reset::reset(peris); + reset::unreset_wait(peris); + + // See above re gpin handling being commented out + // let gpin0_freq = config.gpin0.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin0.store(gpin0_freq, Ordering::Relaxed); + // let gpin1_freq = config.gpin1.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin1.store(gpin1_freq, Ordering::Relaxed); + + let rosc_freq = match config.rosc { + Some(config) => configure_rosc(config), + None => 0, + }; + CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); + + // Set Core Voltage, if we have config for it and we're not using the default + { + let voltage = config.core_voltage; + + #[cfg(feature = "rp2040")] + let vreg = pac::VREG_AND_CHIP_RESET; + #[cfg(feature = "_rp235x")] + let vreg = pac::POWMAN; + + let current_vsel = vreg.vreg().read().vsel(); + let target_vsel = voltage as u8; + + // If the target voltage is different from the current one, we need to change it + if target_vsel != current_vsel { + // Set the voltage regulator to the target voltage + #[cfg(feature = "rp2040")] + vreg.vreg().modify(|w| w.set_vsel(target_vsel)); + #[cfg(feature = "_rp235x")] + // For rp235x changes to the voltage regulator are protected by a password, see datasheet section 6.4 Power Management (POWMAN) Registers + // The password is "5AFE" (0x5AFE), it must be set in the top 16 bits of the register + vreg.vreg().modify(|w| { + w.0 = (w.0 & 0x0000FFFF) | (0x5AFE << 16); // Set the password + w.set_vsel(target_vsel); + *w + }); + + // Wait for the voltage to stabilize. Use the provided delay or default based on voltage + let settling_time_us = config.voltage_stabilization_delay_us.unwrap_or_else(|| { + match voltage { + CoreVoltage::V1_15 => 1000, // 1ms for 1.15V + CoreVoltage::V1_20 | CoreVoltage::V1_25 | CoreVoltage::V1_30 => 2000, // 2ms for higher voltages + _ => 0, // no delay for all others + } + }); + + if settling_time_us != 0 { + // Delay in microseconds, using the ROSC frequency to calculate cycles + let cycles_per_us = rosc_freq / 1_000_000; + let delay_cycles = settling_time_us * cycles_per_us; + cortex_m::asm::delay(delay_cycles); + } + + // Only now set the BOD level. At this point the voltage is considered stable. + #[cfg(feature = "rp2040")] + vreg.bod().write(|w| { + w.set_vsel(voltage.recommended_bod()); + w.set_en(true); // Enable brownout detection + }); + #[cfg(feature = "_rp235x")] + vreg.bod().write(|w| { + w.0 = (w.0 & 0x0000FFFF) | (0x5AFE << 16); // Set the password + w.set_vsel(voltage.recommended_bod()); + w.set_en(true); // Enable brownout detection + }); + } + } + + let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { + Some(config) => { + // start XOSC + start_xosc(config.hz, config.delay_multiplier); + + let pll_sys_freq = match config.sys_pll { + Some(sys_pll_config) => match configure_pll(pac::PLL_SYS, config.hz, sys_pll_config) { + Ok(freq) => freq, + Err(e) => panic!("Failed to configure PLL_SYS: {:?}", e), + }, + None => 0, + }; + let pll_usb_freq = match config.usb_pll { + Some(usb_pll_config) => match configure_pll(pac::PLL_USB, config.hz, usb_pll_config) { + Ok(freq) => freq, + Err(e) => panic!("Failed to configure PLL_USB: {:?}", e), + }, + None => 0, + }; + + (config.hz, pll_sys_freq, pll_usb_freq) + } + None => (0, 0, 0), + }; + + CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); + CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); + CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); + + let (ref_src, ref_aux, clk_ref_freq) = { + use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; + let div = config.ref_clk.div as u32; + assert!(div >= 1 && div <= 4); + match config.ref_clk.src { + RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), + RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), + RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), + // See above re gpin handling being commented out + // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), + // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), + } + }; + assert!(clk_ref_freq != 0); + CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); + c.clk_ref_ctrl().write(|w| { + w.set_src(ref_src); + w.set_auxsrc(ref_aux); + }); + #[cfg(feature = "rp2040")] + while c.clk_ref_selected().read() != (1 << ref_src as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_ref_selected().read() != pac::clocks::regs::ClkRefSelected(1 << ref_src as u32) {} + c.clk_ref_div().write(|w| { + w.set_int(config.ref_clk.div); + }); + + // Configure tick generation on the 2040. + #[cfg(feature = "rp2040")] + pac::WATCHDOG.tick().write(|w| { + w.set_cycles((clk_ref_freq / 1_000_000) as u16); + w.set_enable(true); + }); + // Configure tick generator on the 2350 + #[cfg(feature = "_rp235x")] + { + let cycle_count = clk_ref_freq / 1_000_000; + + pac::TICKS.timer0_cycles().write(|w| w.0 = cycle_count); + pac::TICKS.timer0_ctrl().write(|w| w.set_enable(true)); + + pac::TICKS.watchdog_cycles().write(|w| w.0 = cycle_count); + pac::TICKS.watchdog_ctrl().write(|w| w.set_enable(true)); + } + + let (sys_src, sys_aux, clk_sys_freq) = { + use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; + let (src, aux, freq) = match config.sys_clk.src { + SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), + SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), + SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), + SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), + SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), + // See above re gpin handling being commented out + // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), + // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), + }; + let div = config.sys_clk.div_int as u64 * 256 + config.sys_clk.div_frac as u64; + (src, aux, ((freq as u64 * 256) / div) as u32) + }; + assert!(clk_sys_freq != 0); + CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); + if sys_src != ClkSysCtrlSrc::CLK_REF { + c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << ClkSysCtrlSrc::CLK_REF as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << ClkSysCtrlSrc::CLK_REF as u32) {} + } + c.clk_sys_ctrl().write(|w| { + w.set_auxsrc(sys_aux); + w.set_src(sys_src); + }); + + #[cfg(feature = "rp2040")] + while c.clk_sys_selected().read() != (1 << sys_src as u32) {} + #[cfg(feature = "_rp235x")] + while c.clk_sys_selected().read() != pac::clocks::regs::ClkSysSelected(1 << sys_src as u32) {} + + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); + }); + + let mut peris = reset::ALL_PERIPHERALS; + + if let Some(src) = config.peri_clk_src { + c.clk_peri_ctrl().write(|w| { + w.set_enable(true); + w.set_auxsrc(ClkPeriCtrlAuxsrc::from_bits(src as _)); + }); + let peri_freq = match src { + PeriClkSrc::Sys => clk_sys_freq, + PeriClkSrc::PllSys => pll_sys_freq, + PeriClkSrc::PllUsb => pll_usb_freq, + PeriClkSrc::Rosc => rosc_freq, + PeriClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out + // PeriClkSrc::Gpin0 => gpin0_freq, + // PeriClkSrc::Gpin1 => gpin1_freq, + }; + assert!(peri_freq != 0); + CLOCKS.peri.store(peri_freq, Ordering::Relaxed); + } else { + peris.set_spi0(false); + peris.set_spi1(false); + peris.set_uart0(false); + peris.set_uart1(false); + CLOCKS.peri.store(0, Ordering::Relaxed); + } + + if let Some(conf) = config.usb_clk { + c.clk_usb_div().write(|w| w.set_int(conf.div)); + c.clk_usb_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); + }); + let usb_freq = match conf.src { + UsbClkSrc::PllUsb => pll_usb_freq, + UsbClkSrc::PllSys => pll_sys_freq, + UsbClkSrc::Rosc => rosc_freq, + UsbClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out + // UsbClkSrc::Gpin0 => gpin0_freq, + // UsbClkSrc::Gpin1 => gpin1_freq, + }; + assert!(usb_freq != 0); + assert!(conf.div >= 1 && conf.div <= 4); + CLOCKS.usb.store(usb_freq / conf.div as u32, Ordering::Relaxed); + } else { + peris.set_usbctrl(false); + CLOCKS.usb.store(0, Ordering::Relaxed); + } + + if let Some(conf) = config.adc_clk { + c.clk_adc_div().write(|w| w.set_int(conf.div)); + c.clk_adc_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); + }); + let adc_in_freq = match conf.src { + AdcClkSrc::PllUsb => pll_usb_freq, + AdcClkSrc::PllSys => pll_sys_freq, + AdcClkSrc::Rosc => rosc_freq, + AdcClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out + // AdcClkSrc::Gpin0 => gpin0_freq, + // AdcClkSrc::Gpin1 => gpin1_freq, + }; + assert!(adc_in_freq != 0); + assert!(conf.div >= 1 && conf.div <= 4); + CLOCKS.adc.store(adc_in_freq / conf.div as u32, Ordering::Relaxed); + } else { + peris.set_adc(false); + CLOCKS.adc.store(0, Ordering::Relaxed); + } + + // rp2040 specific clocks + #[cfg(feature = "rp2040")] + if let Some(conf) = config.rtc_clk { + c.clk_rtc_div().write(|w| { + w.set_int(conf.div_int); + w.set_frac(conf.div_frac); + }); + c.clk_rtc_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); + }); + let rtc_in_freq = match conf.src { + RtcClkSrc::PllUsb => pll_usb_freq, + RtcClkSrc::PllSys => pll_sys_freq, + RtcClkSrc::Rosc => rosc_freq, + RtcClkSrc::Xosc => xosc_freq, + // See above re gpin handling being commented out + // RtcClkSrc::Gpin0 => gpin0_freq, + // RtcClkSrc::Gpin1 => gpin1_freq, + }; + assert!(rtc_in_freq != 0); + assert!(config.sys_clk.div_int <= 0x1000000); + CLOCKS.rtc.store( + ((rtc_in_freq as u64 * 256) / (conf.div_int as u64 * 256 + conf.div_frac as u64)) as u16, + Ordering::Relaxed, + ); + } else { + peris.set_rtc(false); + CLOCKS.rtc.store(0, Ordering::Relaxed); + } + + // rp235x specific clocks + #[cfg(feature = "_rp235x")] + { + // TODO hstx clock + peris.set_hstx(false); + } + + // Peripheral clocks should now all be running + reset::unreset_wait(peris); +} + +fn configure_rosc(config: RoscConfig) -> u32 { + let p = pac::ROSC; + + p.freqa().write(|w| { + w.set_passwd(pac::rosc::vals::Passwd::PASS); + w.set_ds0(config.drive_strength[0]); + w.set_ds1(config.drive_strength[1]); + w.set_ds2(config.drive_strength[2]); + w.set_ds3(config.drive_strength[3]); + }); + + p.freqb().write(|w| { + w.set_passwd(pac::rosc::vals::Passwd::PASS); + w.set_ds4(config.drive_strength[4]); + w.set_ds5(config.drive_strength[5]); + w.set_ds6(config.drive_strength[6]); + w.set_ds7(config.drive_strength[7]); + }); + + p.div().write(|w| { + w.set_div(pac::rosc::vals::Div(config.div + pac::rosc::vals::Div::PASS.0)); + }); + + p.ctrl().write(|w| { + w.set_enable(pac::rosc::vals::Enable::ENABLE); + w.set_freq_range(pac::rosc::vals::FreqRange(config.range as u16)); + }); + + config.hz +} + +/// ROSC clock frequency. +pub fn rosc_freq() -> u32 { + CLOCKS.rosc.load(Ordering::Relaxed) +} + +/// XOSC clock frequency. +pub fn xosc_freq() -> u32 { + CLOCKS.xosc.load(Ordering::Relaxed) +} + +// See above re gpin handling being commented out +// pub fn gpin0_freq() -> u32 { +// CLOCKS.gpin0.load(Ordering::Relaxed) +// } +// pub fn gpin1_freq() -> u32 { +// CLOCKS.gpin1.load(Ordering::Relaxed) +// } + +/// PLL SYS clock frequency. +pub fn pll_sys_freq() -> u32 { + CLOCKS.pll_sys.load(Ordering::Relaxed) +} + +/// PLL USB clock frequency. +pub fn pll_usb_freq() -> u32 { + CLOCKS.pll_usb.load(Ordering::Relaxed) +} + +/// Set clock frequencies for Non-Secure TrustZone init. +/// +/// Called by `init_ns()` to populate the CLOCKS static without touching hardware. +/// The Secure world must have already configured the actual clock tree. +pub(crate) fn set_frequencies( + xosc_hz: u32, + sys_hz: u32, + ref_hz: u32, + pll_sys_hz: u32, + pll_usb_hz: u32, + usb_hz: u32, + adc_hz: u32, + peri_hz: u32, +) { + CLOCKS.xosc.store(xosc_hz, Ordering::Relaxed); + CLOCKS.sys.store(sys_hz, Ordering::Relaxed); + CLOCKS.reference.store(ref_hz, Ordering::Relaxed); + CLOCKS.pll_sys.store(pll_sys_hz, Ordering::Relaxed); + CLOCKS.pll_usb.store(pll_usb_hz, Ordering::Relaxed); + CLOCKS.usb.store(usb_hz, Ordering::Relaxed); + CLOCKS.adc.store(adc_hz, Ordering::Relaxed); + CLOCKS.peri.store(peri_hz, Ordering::Relaxed); + CLOCKS.rosc.store(0, Ordering::Relaxed); +} + +/// SYS clock frequency. +pub fn clk_sys_freq() -> u32 { + CLOCKS.sys.load(Ordering::Relaxed) +} + +/// REF clock frequency. +pub fn clk_ref_freq() -> u32 { + CLOCKS.reference.load(Ordering::Relaxed) +} + +/// Peripheral clock frequency. +pub fn clk_peri_freq() -> u32 { + CLOCKS.peri.load(Ordering::Relaxed) +} + +/// USB clock frequency. +pub fn clk_usb_freq() -> u32 { + CLOCKS.usb.load(Ordering::Relaxed) +} + +/// ADC clock frequency. +pub fn clk_adc_freq() -> u32 { + CLOCKS.adc.load(Ordering::Relaxed) +} + +/// RTC clock frequency. +#[cfg(feature = "rp2040")] +pub fn clk_rtc_freq() -> u16 { + CLOCKS.rtc.load(Ordering::Relaxed) +} + +/// The core voltage of the chip. +/// +/// Returns the current core voltage or an error if the voltage register +/// contains an unknown value. +pub fn core_voltage() -> Result { + #[cfg(feature = "rp2040")] + { + let vreg = pac::VREG_AND_CHIP_RESET; + let vsel = vreg.vreg().read().vsel(); + match vsel { + 0b0000 => Ok(CoreVoltage::V0_80), + 0b0110 => Ok(CoreVoltage::V0_85), + 0b0111 => Ok(CoreVoltage::V0_90), + 0b1000 => Ok(CoreVoltage::V0_95), + 0b1001 => Ok(CoreVoltage::V1_00), + 0b1010 => Ok(CoreVoltage::V1_05), + 0b1011 => Ok(CoreVoltage::V1_10), + 0b1100 => Ok(CoreVoltage::V1_15), + 0b1101 => Ok(CoreVoltage::V1_20), + 0b1110 => Ok(CoreVoltage::V1_25), + 0b1111 => Ok(CoreVoltage::V1_30), + _ => Err(ClockError::UnexpectedCoreVoltageRead), + } + } + + #[cfg(feature = "_rp235x")] + { + let vreg = pac::POWMAN; + let vsel = vreg.vreg().read().vsel(); + match vsel { + 0b00000 => Ok(CoreVoltage::V0_55), + 0b00001 => Ok(CoreVoltage::V0_60), + 0b00010 => Ok(CoreVoltage::V0_65), + 0b00011 => Ok(CoreVoltage::V0_70), + 0b00100 => Ok(CoreVoltage::V0_75), + 0b00101 => Ok(CoreVoltage::V0_80), + 0b00110 => Ok(CoreVoltage::V0_85), + 0b00111 => Ok(CoreVoltage::V0_90), + 0b01000 => Ok(CoreVoltage::V0_95), + 0b01001 => Ok(CoreVoltage::V1_00), + 0b01010 => Ok(CoreVoltage::V1_05), + 0b01011 => Ok(CoreVoltage::V1_10), + 0b01100 => Ok(CoreVoltage::V1_15), + 0b01101 => Ok(CoreVoltage::V1_20), + 0b01110 => Ok(CoreVoltage::V1_25), + 0b01111 => Ok(CoreVoltage::V1_30), + _ => Err(ClockError::UnexpectedCoreVoltageRead), + // see CoreVoltage: we do not support setting Voltages higher than 1.30V at this point + } + } +} + +fn start_xosc(crystal_hz: u32, delay_multiplier: u32) { + let startup_delay = (((crystal_hz / 1000) * delay_multiplier) + 128) / 256; + pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); + pac::XOSC.ctrl().write(|w| { + w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ); + w.set_enable(pac::xosc::vals::Enable::ENABLE); + }); + while !pac::XOSC.status().read().stable() {} +} + +/// PLL (Phase-Locked Loop) configuration +#[inline(always)] +fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> Result { + // Calculate reference frequency + let ref_freq = input_freq / config.refdiv as u32; + + // Validate PLL parameters + // Feedback divider (FBDIV) must be between 16 and 320 + assert!(config.fbdiv >= 16 && config.fbdiv <= 320); + + // Post divider 1 (POSTDIV1) must be between 1 and 7 + assert!(config.post_div1 >= 1 && config.post_div1 <= 7); + + // Post divider 2 (POSTDIV2) must be between 1 and 7 + assert!(config.post_div2 >= 1 && config.post_div2 <= 7); + + // Post divider 2 (POSTDIV2) must be less than or equal to post divider 1 (POSTDIV1) + assert!(config.post_div2 <= config.post_div1); + + // Reference divider (REFDIV) must be between 1 and 63 + assert!(config.refdiv >= 1 && config.refdiv <= 63); + + // Reference frequency (REF_FREQ) must be between 5MHz and 800MHz + assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); + + // Calculate VCO frequency + let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); + + // VCO (Voltage Controlled Oscillator) frequency must be between 750MHz and 1800MHz + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + + // We follow the SDK's approach to PLL configuration which is: + // 1. Power down PLL + // 2. Configure the reference divider + // 3. Configure the feedback divider + // 4. Power up PLL and VCO + // 5. Wait for PLL to lock + // 6. Configure post-dividers + // 7. Enable post-divider output + + // 1. Power down PLL before configuration + p.pwr().write(|w| { + w.set_pd(true); // Power down the PLL + w.set_vcopd(true); // Power down the VCO + w.set_postdivpd(true); // Power down the post divider + w.set_dsmpd(true); // Disable fractional mode + *w + }); + + // Short delay after powering down + cortex_m::asm::delay(10); + + // 2. Configure reference divider first + p.cs().write(|w| w.set_refdiv(config.refdiv as _)); + + // 3. Configure feedback divider + p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); + + // 4. Power up PLL and VCO, but keep post divider powered down during initial lock + p.pwr().write(|w| { + w.set_pd(false); // Power up the PLL + w.set_vcopd(false); // Power up the VCO + w.set_postdivpd(true); // Keep post divider powered down during initial lock + w.set_dsmpd(true); // Disable fractional mode (simpler configuration) + *w + }); + + // 5. Wait for PLL to lock with a timeout + let mut timeout = 1_000_000; + while !p.cs().read().lock() { + timeout -= 1; + if timeout == 0 { + // PLL failed to lock, return 0 to indicate failure + return Err(ClockError::PllLockTimedOut); + } + } + + // 6. Configure post dividers after PLL is locked + p.prim().write(|w| { + w.set_postdiv1(config.post_div1); + w.set_postdiv2(config.post_div2); + }); + + // 7. Enable the post divider output + p.pwr().modify(|w| { + w.set_postdivpd(false); // Power up post divider + *w + }); + + // Final delay to ensure everything is stable + cortex_m::asm::delay(100); + + // Calculate and return actual output frequency + Ok(vco_freq / ((config.post_div1 * config.post_div2) as u32)) +} + +/// General purpose input clock pin. +pub trait GpinPin: crate::gpio::Pin { + /// Pin number. + const NR: usize; +} + +macro_rules! impl_gpinpin { + ($name:ident, $pin_num:expr, $gpin_num:expr) => { + impl GpinPin for crate::peripherals::$name { + const NR: usize = $gpin_num; + } + }; +} + +impl_gpinpin!(PIN_20, 20, 0); +impl_gpinpin!(PIN_22, 22, 1); + +/// General purpose clock input driver. +pub struct Gpin<'d, T: GpinPin> { + gpin: Peri<'d, AnyPin>, + _phantom: PhantomData, +} + +impl<'d, T: GpinPin> Gpin<'d, T> { + /// Create new gpin driver. + pub fn new(gpin: Peri<'d, T>) -> Self { + #[cfg(feature = "rp2040")] + gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + + // On RP2350 GPIN changed from F8 toF9 + #[cfg(feature = "_rp235x")] + gpin.gpio().ctrl().write(|w| w.set_funcsel(0x09)); + + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); + + Gpin { + gpin: gpin.into(), + _phantom: PhantomData, + } + } +} + +impl<'d, T: GpinPin> Drop for Gpin<'d, T> { + fn drop(&mut self) { + self.gpin.pad_ctrl().write(|_| {}); + self.gpin + .gpio() + .ctrl() + .write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _)); + } +} + +/// General purpose clock output pin. +pub trait GpoutPin: crate::gpio::Pin { + /// Pin number. + fn number(&self) -> usize; +} + +macro_rules! impl_gpoutpin { + ($name:ident, $gpout_num:expr) => { + impl GpoutPin for crate::peripherals::$name { + fn number(&self) -> usize { + $gpout_num + } + } + }; +} + +impl_gpoutpin!(PIN_21, 0); +impl_gpoutpin!(PIN_23, 1); +impl_gpoutpin!(PIN_24, 2); +impl_gpoutpin!(PIN_25, 3); + +/// Gpout clock source. +#[repr(u8)] +pub enum GpoutSrc { + /// Sys PLL. + PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, + // See above re gpin handling being commented out + // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , + /// USB PLL. + PllUsb = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB as _, + /// ROSC. + Rosc = ClkGpoutCtrlAuxsrc::ROSC_CLKSRC as _, + /// XOSC. + Xosc = ClkGpoutCtrlAuxsrc::XOSC_CLKSRC as _, + /// SYS. + Sys = ClkGpoutCtrlAuxsrc::CLK_SYS as _, + /// USB. + Usb = ClkGpoutCtrlAuxsrc::CLK_USB as _, + /// ADC. + Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _, + /// RTC. + #[cfg(feature = "rp2040")] + Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _, + /// REF. + Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _, +} + +/// General purpose clock output driver. +pub struct Gpout<'d, T: GpoutPin> { + gpout: Peri<'d, T>, +} + +impl<'d, T: GpoutPin> Gpout<'d, T> { + /// Create new general purpose clock output. + pub fn new(gpout: Peri<'d, T>) -> Self { + #[cfg(feature = "rp2040")] + gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + + // On RP2350 GPOUT changed from F8 toF9 + #[cfg(feature = "_rp235x")] + gpout.gpio().ctrl().write(|w| w.set_funcsel(0x09)); + + #[cfg(feature = "_rp235x")] + gpout.pad_ctrl().write(|w| { + w.set_iso(false); + }); + + Self { gpout } + } + + /// Set clock divider. + #[cfg(feature = "rp2040")] + pub fn set_div(&self, int: u32, frac: u8) { + let c = pac::CLOCKS; + c.clk_gpout_div(self.gpout.number()).write(|w| { + w.set_int(int); + w.set_frac(frac); + }); + } + + /// Set clock divider. + #[cfg(feature = "_rp235x")] + pub fn set_div(&self, int: u16, frac: u16) { + let c = pac::CLOCKS; + c.clk_gpout_div(self.gpout.number()).write(|w| { + w.set_int(int); + w.set_frac(frac); + }); + } + + /// Set clock source. + pub fn set_src(&self, src: GpoutSrc) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_auxsrc(ClkGpoutCtrlAuxsrc::from_bits(src as _)); + }); + } + + /// Enable clock. + pub fn enable(&self) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_enable(true); + }); + } + + /// Disable clock. + pub fn disable(&self) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_enable(false); + }); + } + + /// Clock frequency. + pub fn get_freq(&self) -> u32 { + let c = pac::CLOCKS; + let src = c.clk_gpout_ctrl(self.gpout.number()).read().auxsrc(); + + let base = match src { + ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), + // See above re gpin handling being commented out + // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), + // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), + ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), + ClkGpoutCtrlAuxsrc::ROSC_CLKSRC => rosc_freq(), + ClkGpoutCtrlAuxsrc::XOSC_CLKSRC => xosc_freq(), + ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), + ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), + ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), + ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), + _ => unreachable!(), + }; + + let div = c.clk_gpout_div(self.gpout.number()).read(); + let int = if div.int() == 0 { 0xFFFF } else { div.int() } as u64; + let frac = div.frac() as u64; + + ((base as u64 * 256) / (int * 256 + frac)) as u32 + } +} + +impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { + fn drop(&mut self) { + self.disable(); + self.gpout.pad_ctrl().write(|_| {}); + self.gpout + .gpio() + .ctrl() + .write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _)); + } +} + +/// Random number generator based on the ROSC RANDOMBIT register. +/// +/// This will not produce random values if the ROSC is stopped or run at some +/// harmonic of the bus frequency. With default clock settings these are not +/// issues. +pub struct RoscRng; + +impl RoscRng { + /// Get a random u8 + pub fn next_u8() -> u8 { + let random_reg = pac::ROSC.randombit(); + let mut acc = 0; + for _ in 0..u8::BITS { + acc <<= 1; + acc |= random_reg.read().randombit() as u8; + } + acc + } + + /// Get a random u32 + pub fn next_u32(&mut self) -> u32 { + rand_core_09::impls::next_u32_via_fill(self) + } + + /// Get a random u64 + pub fn next_u64(&mut self) -> u64 { + rand_core_09::impls::next_u64_via_fill(self) + } + + /// Fill a slice with random bytes + pub fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill_with(Self::next_u8) + } +} + +impl rand_core_06::RngCore for RoscRng { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +impl rand_core_06::CryptoRng for RoscRng {} + +impl rand_core_09::RngCore for RoscRng { + fn next_u32(&mut self) -> u32 { + self.next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.fill_bytes(dest); + } +} + +impl rand_core_09::CryptoRng for RoscRng {} + +/// Enter the `DORMANT` sleep state. This will stop *all* internal clocks +/// and can only be exited through resets, dormant-wake GPIO interrupts, +/// and RTC interrupts. If RTC is clocked from an internal clock source +/// it will be stopped and not function as a wakeup source. +#[cfg(all(target_arch = "arm"))] +pub fn dormant_sleep() { + struct Set(Reg, T, F); + + impl Drop for Set { + fn drop(&mut self) { + self.0.write_value(self.1); + self.2(); + } + } + + fn set_with_post_restore After>( + reg: Reg, + f: F, + ) -> Set { + reg.modify(|w| { + let old = *w; + let after = f(w); + Set(reg, old, after) + }) + } + + fn set(reg: Reg, f: F) -> Set { + set_with_post_restore(reg, |r| { + f(r); + || () + }) + } + + // disable all clocks that are not vital in preparation for disabling clock sources. + // we'll keep gpout and rtc clocks untouched, gpout because we don't care about them + // and rtc because it's a possible wakeup source. if clk_rtc is not configured for + // gpin we'll never wake from rtc, but that's what the user asked for then. + let _stop_adc = set(pac::CLOCKS.clk_adc_ctrl(), |w| w.set_enable(false)); + let _stop_usb = set(pac::CLOCKS.clk_usb_ctrl(), |w| w.set_enable(false)); + let _stop_peri = set(pac::CLOCKS.clk_peri_ctrl(), |w| w.set_enable(false)); + // set up rosc. we could ask the user to tell us which clock source to wake from like + // the C SDK does, but that seems rather unfriendly. we *may* disturb rtc by changing + // rosc configuration if it's currently the rtc clock source, so we'll configure rosc + // to the slowest frequency to minimize that impact. + let _configure_rosc = ( + set(pac::ROSC.ctrl(), |w| { + w.set_enable(pac::rosc::vals::Enable::ENABLE); + w.set_freq_range(pac::rosc::vals::FreqRange::LOW); + }), + // div=32 + set(pac::ROSC.div(), |w| w.set_div(pac::rosc::vals::Div(0xaa0))), + ); + while !pac::ROSC.status().read().stable() {} + // switch over to rosc as the system clock source. this will change clock sources for + // watchdog and timer clocks, but timers won't be a concern and the watchdog won't + // speed up by enough to worry about (unless it's clocked from gpin, which we don't + // support anyway). + let _switch_clk_ref = set(pac::CLOCKS.clk_ref_ctrl(), |w| { + w.set_src(pac::clocks::vals::ClkRefCtrlSrc::ROSC_CLKSRC_PH); + }); + let _switch_clk_sys = set(pac::CLOCKS.clk_sys_ctrl(), |w| { + w.set_src(pac::clocks::vals::ClkSysCtrlSrc::CLK_REF); + }); + // oscillator dormancy does not power down plls, we have to do that ourselves. we'll + // restore them to their prior glory when woken though since the system may be clocked + // from either (and usb/adc will probably need the USB PLL anyway) + let _stop_pll_sys = set_with_post_restore(pac::PLL_SYS.pwr(), |w| { + let wake = !w.pd() && !w.vcopd(); + w.set_pd(true); + w.set_vcopd(true); + move || while wake && !pac::PLL_SYS.cs().read().lock() {} + }); + let _stop_pll_usb = set_with_post_restore(pac::PLL_USB.pwr(), |w| { + let wake = !w.pd() && !w.vcopd(); + w.set_pd(true); + w.set_vcopd(true); + move || while wake && !pac::PLL_USB.cs().read().lock() {} + }); + // dormancy only stops the oscillator we're telling to go dormant, the other remains + // running. nothing can use xosc at this point any more. not doing this costs an 200µA. + let _stop_xosc = set_with_post_restore(pac::XOSC.ctrl(), |w| { + let wake = w.enable() == pac::xosc::vals::Enable::ENABLE; + if wake { + w.set_enable(pac::xosc::vals::Enable::DISABLE); + } + move || while wake && !pac::XOSC.status().read().stable() {} + }); + let _power_down_xip_cache = set(pac::XIP_CTRL.ctrl(), |w| w.set_power_down(true)); + + // only power down memory if we're running from XIP (or ROM? how?). + // powering down memory otherwise would require a lot of exacting checks that + // are better done by the user in a local copy of this function. + // powering down memories saves ~100µA, so it's well worth doing. + unsafe { + let is_in_flash = { + // we can't rely on the address of this function as rust sees it since linker + // magic or even boot2 may place it into ram. + let pc: usize; + asm!( + "mov {pc}, pc", + pc = out (reg) pc + ); + pc < 0x20000000 + }; + if is_in_flash { + // we will be powering down memories, so we must be *absolutely* + // certain that we're running entirely from XIP and registers until + // memories are powered back up again. accessing memory that's powered + // down may corrupt memory contents (see section 2.11.4 of the manual). + // additionally a 20ns wait time is needed after powering up memories + // again. rosc is likely to run at only a few MHz at most, so the + // inter-instruction delay alone will be enough to satisfy this bound. + asm!( + "ldr {old_mem}, [{mempowerdown}]", + "str {power_down_mems}, [{mempowerdown}]", + "str {coma}, [{dormant}]", + "str {old_mem}, [{mempowerdown}]", + old_mem = out (reg) _, + mempowerdown = in (reg) pac::SYSCFG.mempowerdown().as_ptr(), + power_down_mems = in (reg) 0b11111111, + dormant = in (reg) pac::ROSC.dormant().as_ptr(), + coma = in (reg) 0x636f6d61, + ); + } else { + pac::ROSC.dormant().write_value(rp_pac::rosc::regs::Dormant(0x636f6d61)); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "rp2040")] + #[test] + fn test_find_pll_params() { + #[cfg(feature = "rp2040")] + { + // Test standard 125 MHz configuration with 12 MHz crystal + let params = find_pll_params(12_000_000, 125_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + assert_eq!(params.fbdiv, 125); + + // Test USB PLL configuration for 48MHz + // The algorithm may find different valid parameters than the SDK defaults + // We'll check that it generates a valid configuration that produces 48MHz + let params = find_pll_params(12_000_000, 48_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + + // Calculate the actual output frequency + let ref_freq = 12_000_000 / params.refdiv as u32; + let vco_freq = ref_freq as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // Verify the output frequency is correct + assert_eq!(output_freq, 48_000_000); + + // Verify VCO frequency is in valid range + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + + // Test overclocked configuration for 200 MHz + let params = find_pll_params(12_000_000, 200_000_000).unwrap(); + assert_eq!(params.refdiv, 1); + let vco_freq = 12_000_000 as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + assert_eq!(output_freq, 200_000_000); + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); // VCO in valid range + + // Test non-standard crystal with 16 MHz + let params = find_pll_params(16_000_000, 125_000_000).unwrap(); + let vco_freq = (16_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // Test non-standard crystal with 15 MHz + let params = find_pll_params(15_000_000, 125_000_000).unwrap(); + let vco_freq = (15_000_000 / params.refdiv as u32) as u64 * params.fbdiv as u64; + let output_freq = (vco_freq / ((params.post_div1 * params.post_div2) as u64)) as u32; + + // With a 15 MHz crystal, we might not get exactly 125 MHz + // Check that it's close enough (within 0.2% margin) + let freq_diff = if output_freq > 125_000_000 { + output_freq - 125_000_000 + } else { + 125_000_000 - output_freq + }; + let error_percentage = (freq_diff as f64 / 125_000_000.0) * 100.0; + assert!( + error_percentage < 0.2, + "Output frequency {} is not close enough to target 125 MHz. Error: {:.2}%", + output_freq, + error_percentage + ); + + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_pll_config_validation() { + // Test PLL configuration validation logic + let valid_config = PllConfig { + refdiv: 1, + fbdiv: 125, + post_div1: 6, + post_div2: 2, + }; + + // Valid configuration should pass validation + assert!(valid_config.is_valid(12_000_000)); + + // Test fbdiv constraints + let mut invalid_config = valid_config; + invalid_config.fbdiv = 15; // Below minimum of 16 + assert!(!invalid_config.is_valid(12_000_000)); + + invalid_config.fbdiv = 321; // Above maximum of 320 + assert!(!invalid_config.is_valid(12_000_000)); + + // Test post_div constraints + invalid_config = valid_config; + invalid_config.post_div1 = 0; // Below minimum of 1 + assert!(!invalid_config.is_valid(12_000_000)); + + invalid_config = valid_config; + invalid_config.post_div1 = 8; // Above maximum of 7 + assert!(!invalid_config.is_valid(12_000_000)); + + // Test post_div2 must be <= post_div1 + invalid_config = valid_config; + invalid_config.post_div2 = 7; + invalid_config.post_div1 = 3; + assert!(!invalid_config.is_valid(12_000_000)); + + // Test reference frequency constraints + invalid_config = valid_config; + assert!(!invalid_config.is_valid(4_000_000)); // Below minimum of 5 MHz + assert!(!invalid_config.is_valid(900_000_000)); // Above maximum of 800 MHz + + // Test VCO frequency constraints + invalid_config = valid_config; + invalid_config.fbdiv = 16; + assert!(!invalid_config.is_valid(12_000_000)); // VCO too low: 12MHz * 16 = 192MHz + + // Test VCO frequency constraints - too high + invalid_config = valid_config; + invalid_config.fbdiv = 200; + invalid_config.refdiv = 1; + // This should be INVALID: 12MHz * 200 = 2400MHz exceeds max VCO of 1800MHz + assert!(!invalid_config.is_valid(12_000_000)); + + // Test a valid high VCO configuration + invalid_config.fbdiv = 150; // 12MHz * 150 = 1800MHz, exactly at the limit + assert!(invalid_config.is_valid(12_000_000)); + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_manual_pll_helper() { + { + // Test the new manual_pll helper method + let config = ClockConfig::manual_pll( + 12_000_000, + PllConfig { + refdiv: 1, + fbdiv: 100, + post_div1: 3, + post_div2: 2, + }, + CoreVoltage::V1_15, + ); + + // Check voltage scale was set correctly + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Check PLL config was set correctly + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().refdiv, 1); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().fbdiv, 100); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div1, 3); + assert_eq!(config.xosc.as_ref().unwrap().sys_pll.as_ref().unwrap().post_div2, 2); + + // Check we get the expected frequency + assert_eq!( + config + .xosc + .as_ref() + .unwrap() + .sys_pll + .as_ref() + .unwrap() + .output_frequency(12_000_000), + 200_000_000 + ); + } + } + + #[cfg(feature = "rp2040")] + #[test] + fn test_auto_voltage_scaling() { + { + // Test automatic voltage scaling based on frequency + // Under 133 MHz should use default voltage (V1_10) + let config = ClockConfig::system_freq(125_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_10); + + // 133-200 MHz should use V1_15 + let config = ClockConfig::system_freq(150_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + let config = ClockConfig::system_freq(200_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Above 200 MHz should use V1_15 + let config = ClockConfig::system_freq(250_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_15); + + // Below 125 MHz should use V1_10 + let config = ClockConfig::system_freq(100_000_000).unwrap(); + assert_eq!(config.core_voltage, CoreVoltage::V1_10); + } + } +} diff --git a/embassy-rp-fork/src/critical_section_impl.rs b/embassy-rp-fork/src/critical_section_impl.rs new file mode 100644 index 0000000..2e4e8f7 --- /dev/null +++ b/embassy-rp-fork/src/critical_section_impl.rs @@ -0,0 +1,96 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +use crate::pac; +use crate::spinlock::Spinlock; + +struct RpSpinlockCs; +critical_section::set_impl!(RpSpinlockCs); + +/// Marker value to indicate no-one has the lock. +/// +/// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice +const LOCK_UNOWNED: u8 = 0; + +/// Indicates which core owns the lock so that we can call critical_section recursively. +/// +/// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock +static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED); + +/// Marker value to indicate that we already owned the lock when we started the `critical_section`. +/// +/// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership. +/// `critical_section` provides a token for communicating between `acquire` and `release` so we use that. +/// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively. +/// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release` +const LOCK_ALREADY_OWNED: u8 = 2; + +unsafe impl critical_section::Impl for RpSpinlockCs { + unsafe fn acquire() -> u8 { + RpSpinlockCs::acquire() + } + + unsafe fn release(token: u8) { + RpSpinlockCs::release(token); + } +} + +impl RpSpinlockCs { + unsafe fn acquire() -> u8 { + // Store the initial interrupt state and current core id in stack variables + let interrupts_active = cortex_m::register::primask::read().is_active(); + // We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1. + let core = pac::SIO.cpuid().read() as u8 + 1; + // Do we already own the spinlock? + if LOCK_OWNER.load(Ordering::Acquire) == core { + // We already own the lock, so we must have called acquire within a critical_section. + // Return the magic inner-loop value so that we know not to re-enable interrupts in release() + LOCK_ALREADY_OWNED + } else { + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt enters critical_section::Impl after we acquire the lock + cortex_m::interrupt::disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Read the spinlock reserved for `critical_section` + if let Some(lock) = Spinlock31::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + // 2. Store which core we are so we can tell if we're called recursively + LOCK_OWNER.store(core, Ordering::Relaxed); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + cortex_m::interrupt::enable(); + } + } + // If we broke out of the loop we have just acquired the lock + // As the outermost loop, we want to return the interrupt status to restore later + interrupts_active as _ + } + } + + unsafe fn release(token: u8) { + // Did we already own the lock at the start of the `critical_section`? + if token != LOCK_ALREADY_OWNED { + // No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it. + // Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead + LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to enter critical_section again + Spinlock31::release(); + // Re-enable interrupts if they were enabled when we first called acquire() + // We only do this on the outermost `critical_section` to ensure interrupts stay disabled + // for the whole time that we have the lock + if token != 0 { + cortex_m::interrupt::enable(); + } + } + } +} + +pub(crate) type Spinlock31 = Spinlock<31>; diff --git a/embassy-rp-fork/src/dma.rs b/embassy-rp-fork/src/dma.rs new file mode 100644 index 0000000..18aec60 --- /dev/null +++ b/embassy-rp-fork/src/dma.rs @@ -0,0 +1,302 @@ +//! Direct Memory Access (DMA) +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{Ordering, compiler_fence}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::dma::vals::DataSize; + +use crate::interrupt::InterruptExt; +use crate::pac::dma::vals; +use crate::{interrupt, pac, peripherals}; + +#[cfg(feature = "rt")] +#[interrupt] +fn DMA_IRQ_0() { + let ints0 = pac::DMA.ints(0).read(); + for channel in 0..CHANNEL_COUNT { + let ctrl_trig = pac::DMA.ch(channel).ctrl_trig().read(); + if ctrl_trig.ahb_error() { + panic!("DMA: error on DMA_0 channel {}", channel); + } + + if ints0 & (1 << channel) == (1 << channel) { + CHANNEL_WAKERS[channel].wake(); + } + } + pac::DMA.ints(0).write_value(ints0); +} + +pub(crate) unsafe fn init() { + interrupt::DMA_IRQ_0.disable(); + interrupt::DMA_IRQ_0.set_priority(interrupt::Priority::P3); + + pac::DMA.inte(0).write_value(0xFFFF); + + interrupt::DMA_IRQ_0.enable(); +} + +/// DMA read. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn read<'a, C: Channel, W: Word>( + ch: Peri<'a, C>, + from: *const W, + to: *mut [W], + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + copy_inner( + ch, + from as *const u32, + to as *mut W as *mut u32, + to.len(), + W::size(), + false, + true, + dreq, + ) +} + +/// DMA write. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn write<'a, C: Channel, W: Word>( + ch: Peri<'a, C>, + from: *const [W], + to: *mut W, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + copy_inner( + ch, + from as *const W as *const u32, + to as *mut u32, + from.len(), + W::size(), + true, + false, + dreq, + ) +} + +// static mut so that this is allocated in RAM. +static mut DUMMY: u32 = 0; + +/// DMA repeated write. +/// +/// SAFETY: Slice must point to a valid location reachable by DMA. +pub unsafe fn write_repeated<'a, C: Channel, W: Word>( + ch: Peri<'a, C>, + to: *mut W, + len: usize, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + copy_inner( + ch, + core::ptr::addr_of_mut!(DUMMY) as *const u32, + to as *mut u32, + len, + W::size(), + false, + false, + dreq, + ) +} + +/// DMA copy between slices. +/// +/// SAFETY: Slices must point to locations reachable by DMA. +pub unsafe fn copy<'a, C: Channel, W: Word>(ch: Peri<'a, C>, from: &[W], to: &mut [W]) -> Transfer<'a, C> { + let from_len = from.len(); + let to_len = to.len(); + assert_eq!(from_len, to_len); + copy_inner( + ch, + from.as_ptr() as *const u32, + to.as_mut_ptr() as *mut u32, + from_len, + W::size(), + true, + true, + vals::TreqSel::PERMANENT, + ) +} + +fn copy_inner<'a, C: Channel>( + ch: Peri<'a, C>, + from: *const u32, + to: *mut u32, + len: usize, + data_size: DataSize, + incr_read: bool, + incr_write: bool, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + let p = ch.regs(); + + p.read_addr().write_value(from as u32); + p.write_addr().write_value(to as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| { + *w = len as u32; + }); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| { + w.set_mode(0.into()); + w.set_count(len as u32); + }); + + compiler_fence(Ordering::SeqCst); + + p.ctrl_trig().write(|w| { + w.set_treq_sel(dreq); + w.set_data_size(data_size); + w.set_incr_read(incr_read); + w.set_incr_write(incr_write); + w.set_chain_to(ch.number()); + w.set_en(true); + }); + + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) +} + +/// DMA transfer driver. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: Peri<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub(crate) fn new(channel: Peri<'a, C>) -> Self { + Self { channel } + } +} + +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + let p = self.channel.regs(); + pac::DMA + .chan_abort() + .modify(|m| m.set_chan_abort(1 << self.channel.number())); + while p.ctrl_trig().read().busy() {} + } +} + +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker()); + + if self.channel.regs().ctrl_trig().read().busy() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +#[cfg(feature = "rp2040")] +pub(crate) const CHANNEL_COUNT: usize = 12; +#[cfg(feature = "_rp235x")] +pub(crate) const CHANNEL_COUNT: usize = 16; +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [const { AtomicWaker::new() }; CHANNEL_COUNT]; + +trait SealedChannel {} +trait SealedWord {} + +/// DMA channel interface. +#[allow(private_bounds)] +pub trait Channel: PeripheralType + SealedChannel + Into + Sized + 'static { + /// Channel number. + fn number(&self) -> u8; + + /// Channel registry block. + fn regs(&self) -> pac::dma::Channel { + pac::DMA.ch(self.number() as _) + } +} + +/// DMA word. +#[allow(private_bounds)] +pub trait Word: SealedWord { + /// Word size. + fn size() -> vals::DataSize; +} + +impl SealedWord for u8 {} +impl Word for u8 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_BYTE + } +} + +impl SealedWord for u16 {} +impl Word for u16 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_HALFWORD + } +} + +impl SealedWord for u32 {} +impl Word for u32 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_WORD + } +} + +/// Type erased DMA channel. +pub struct AnyChannel { + number: u8, +} + +impl_peripheral!(AnyChannel); + +impl SealedChannel for AnyChannel {} +impl Channel for AnyChannel { + fn number(&self) -> u8 { + self.number + } +} + +macro_rules! channel { + ($name:ident, $num:expr) => { + impl SealedChannel for peripherals::$name {} + impl Channel for peripherals::$name { + fn number(&self) -> u8 { + $num + } + } + + impl From for crate::dma::AnyChannel { + fn from(val: peripherals::$name) -> Self { + Self { number: val.number() } + } + } + }; +} + +channel!(DMA_CH0, 0); +channel!(DMA_CH1, 1); +channel!(DMA_CH2, 2); +channel!(DMA_CH3, 3); +channel!(DMA_CH4, 4); +channel!(DMA_CH5, 5); +channel!(DMA_CH6, 6); +channel!(DMA_CH7, 7); +channel!(DMA_CH8, 8); +channel!(DMA_CH9, 9); +channel!(DMA_CH10, 10); +channel!(DMA_CH11, 11); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH12, 12); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH13, 13); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH14, 14); +#[cfg(feature = "_rp235x")] +channel!(DMA_CH15, 15); diff --git a/embassy-rp-fork/src/flash.rs b/embassy-rp-fork/src/flash.rs new file mode 100644 index 0000000..7cc8f0c --- /dev/null +++ b/embassy-rp-fork/src/flash.rs @@ -0,0 +1,992 @@ +//! Flash driver. +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embedded_storage::nor_flash::{ + ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash, check_erase, check_read, + check_write, +}; + +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::pac; +use crate::peripherals::FLASH; + +/// Flash base address. +pub const FLASH_BASE: *const u32 = 0x10000000 as _; + +/// Address for xip setup function set up by the 235x bootrom. +#[cfg(feature = "_rp235x")] +pub const BOOTRAM_BASE: *const u32 = 0x400e0000 as _; + +/// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead. +// TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance. +pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram") | cfg!(feature = "_rp235x"); + +// **NOTE**: +// +// These limitations are currently enforced because of using the +// RP2040 boot-rom flash functions, that are optimized for flash compatibility +// rather than performance. +/// Flash page size. +pub const PAGE_SIZE: usize = 256; +/// Flash write size. +pub const WRITE_SIZE: usize = 1; +/// Flash read size. +pub const READ_SIZE: usize = 1; +/// Flash erase size. +pub const ERASE_SIZE: usize = 4096; +/// Flash DMA read size. +pub const ASYNC_READ_SIZE: usize = 4; + +/// Error type for NVMC operations. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation using a location not in flash. + OutOfBounds, + /// Unaligned operation or using unaligned buffers. + Unaligned, + /// Accessed from the wrong core. + InvalidCore, + /// Other error + Other, +} + +impl From for Error { + fn from(e: NorFlashErrorKind) -> Self { + match e { + NorFlashErrorKind::NotAligned => Self::Unaligned, + NorFlashErrorKind::OutOfBounds => Self::OutOfBounds, + _ => Self::Other, + } + } +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Self::Unaligned => NorFlashErrorKind::NotAligned, + _ => NorFlashErrorKind::Other, + } + } +} + +/// Future that waits for completion of a background read +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct BackgroundRead<'a, 'd, T: Instance, const FLASH_SIZE: usize> { + flash: PhantomData<&'a mut Flash<'d, T, Async, FLASH_SIZE>>, + transfer: Transfer<'a, AnyChannel>, +} + +impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Future for BackgroundRead<'a, 'd, T, FLASH_SIZE> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.transfer).poll(cx) + } +} + +impl<'a, 'd, T: Instance, const FLASH_SIZE: usize> Drop for BackgroundRead<'a, 'd, T, FLASH_SIZE> { + fn drop(&mut self) { + if pac::XIP_CTRL.stream_ctr().read().0 == 0 { + return; + } + pac::XIP_CTRL + .stream_ctr() + .write_value(pac::xip_ctrl::regs::StreamCtr(0)); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + // Errata RP2040-E8: Perform an uncached read to make sure there's not a transfer in + // flight that might effect an address written to start a new transfer. This stalls + // until after any transfer is complete, so the address will not change anymore. + #[cfg(feature = "rp2040")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x13000000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_NOCACHE_NOALLOC_BASE: *const u32 = 0x14000000 as *const _; + unsafe { + core::ptr::read_volatile(XIP_NOCACHE_NOALLOC_BASE); + } + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + } +} + +/// Flash driver. +pub struct Flash<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> { + dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> Flash<'d, T, M, FLASH_SIZE> { + /// Blocking read. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + trace!( + "Reading from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + offset, + FLASH_BASE as u32 + offset + bytes.len() as u32 + ); + check_read(self, offset, bytes.len())?; + + let flash_data = unsafe { core::slice::from_raw_parts((FLASH_BASE as u32 + offset) as *const u8, bytes.len()) }; + + bytes.copy_from_slice(flash_data); + Ok(()) + } + + /// Flash capacity. + pub fn capacity(&self) -> usize { + FLASH_SIZE + } + + /// Blocking erase. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + check_erase(self, from, to)?; + + trace!( + "Erasing from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + from, + FLASH_BASE as u32 + to + ); + + let len = to - from; + + unsafe { in_ram(|| ram_helpers::flash_range_erase(from, len))? }; + + Ok(()) + } + + /// Blocking write. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + check_write(self, offset, bytes.len())?; + + trace!("Writing {:?} bytes to 0x{:x}", bytes.len(), FLASH_BASE as u32 + offset); + + let end_offset = offset as usize + bytes.len(); + + let padded_offset = (offset as *const u8).align_offset(PAGE_SIZE); + let start_padding = core::cmp::min(padded_offset, bytes.len()); + + // Pad in the beginning + if start_padding > 0 { + let start = PAGE_SIZE - padded_offset; + let end = start + start_padding; + + let mut pad_buf = [0xFF_u8; PAGE_SIZE]; + pad_buf[start..end].copy_from_slice(&bytes[..start_padding]); + + let unaligned_offset = offset as usize - start; + + unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + } + + let remaining_len = bytes.len() - start_padding; + let end_padding = start_padding + PAGE_SIZE * (remaining_len / PAGE_SIZE); + + // Write aligned slice of length in multiples of 256 bytes + // If the remaining bytes to be written is more than a full page. + if remaining_len >= PAGE_SIZE { + let mut aligned_offset = if start_padding > 0 { + offset as usize + padded_offset + } else { + offset as usize + }; + + if bytes.as_ptr() as usize >= 0x2000_0000 { + let aligned_data = &bytes[start_padding..end_padding]; + + unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } + } else { + for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { + let mut ram_buf = [0xFF_u8; PAGE_SIZE]; + ram_buf.copy_from_slice(chunk); + unsafe { in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } + aligned_offset += PAGE_SIZE; + } + } + } + + // Pad in the end + let rem_offset = (end_offset as *const u8).align_offset(PAGE_SIZE); + let rem_padding = remaining_len % PAGE_SIZE; + if rem_padding > 0 { + let mut pad_buf = [0xFF_u8; PAGE_SIZE]; + pad_buf[..rem_padding].copy_from_slice(&bytes[end_padding..]); + + let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); + + unsafe { in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + } + + Ok(()) + } + + /// Read SPI flash unique ID + #[cfg(feature = "rp2040")] + pub fn blocking_unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { + unsafe { in_ram(|| ram_helpers::flash_unique_id(uid))? }; + Ok(()) + } + + /// Read SPI flash JEDEC ID + #[cfg(feature = "rp2040")] + pub fn blocking_jedec_id(&mut self) -> Result { + let mut jedec = None; + unsafe { + in_ram(|| { + jedec.replace(ram_helpers::flash_jedec_id()); + })?; + }; + Ok(jedec.unwrap()) + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Blocking, FLASH_SIZE> { + /// Create a new flash driver in blocking mode. + pub fn new_blocking(_flash: Peri<'d, T>) -> Self { + Self { + dma: None, + phantom: PhantomData, + } + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, Async, FLASH_SIZE> { + /// Create a new flash driver in async mode. + pub fn new(_flash: Peri<'d, T>, dma: Peri<'d, impl Channel>) -> Self { + Self { + dma: Some(dma.into()), + phantom: PhantomData, + } + } + + /// Start a background read operation. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub fn background_read<'a>( + &'a mut self, + offset: u32, + data: &'a mut [u32], + ) -> Result, Error> { + trace!( + "Reading in background from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + offset, + FLASH_BASE as u32 + offset + (data.len() * 4) as u32 + ); + // Can't use check_read because we need to enforce 4-byte alignment + let offset = offset as usize; + let length = data.len() * 4; + if length > self.capacity() || offset > self.capacity() - length { + return Err(Error::OutOfBounds); + } + if offset % 4 != 0 { + return Err(Error::Unaligned); + } + + while !pac::XIP_CTRL.stat().read().fifo_empty() { + pac::XIP_CTRL.stream_fifo().read(); + } + + pac::XIP_CTRL + .stream_addr() + .write_value(pac::xip_ctrl::regs::StreamAddr(FLASH_BASE as u32 + offset as u32)); + pac::XIP_CTRL + .stream_ctr() + .write_value(pac::xip_ctrl::regs::StreamCtr(data.len() as u32)); + + // Use the XIP AUX bus port, rather than the FIFO register access (e.x. + // pac::XIP_CTRL.stream_fifo().as_ptr()) to avoid DMA stalling on + // general XIP access. + #[cfg(feature = "rp2040")] + const XIP_AUX_BASE: *const u32 = 0x50400000 as *const _; + #[cfg(feature = "_rp235x")] + const XIP_AUX_BASE: *const u32 = 0x50500000 as *const _; + let transfer = unsafe { + crate::dma::read( + self.dma.as_mut().unwrap().reborrow(), + XIP_AUX_BASE, + data, + pac::dma::vals::TreqSel::XIP_STREAM, + ) + }; + + Ok(BackgroundRead { + flash: PhantomData, + transfer, + }) + } + + /// Async read. + /// + /// The offset and buffer must be aligned. + /// + /// NOTE: `offset` is an offset from the flash start, NOT an absolute address. + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + use core::mem::MaybeUninit; + + // Checked early to simplify address validity checks + if bytes.len() % 4 != 0 { + return Err(Error::Unaligned); + } + + // If the destination address is already aligned, then we can just DMA directly + if (bytes.as_ptr() as u32) % 4 == 0 { + // Safety: alignment and size have been checked for compatibility + let buf: &mut [u32] = + unsafe { core::slice::from_raw_parts_mut(bytes.as_mut_ptr() as *mut u32, bytes.len() / 4) }; + self.background_read(offset, buf)?.await; + return Ok(()); + } + + // Destination address is unaligned, so use an intermediate buffer + const REALIGN_CHUNK: usize = PAGE_SIZE; + // Safety: MaybeUninit requires no initialization + let mut buf: [MaybeUninit; REALIGN_CHUNK / 4] = unsafe { MaybeUninit::uninit().assume_init() }; + let mut chunk_offset: usize = 0; + while chunk_offset < bytes.len() { + let chunk_size = (bytes.len() - chunk_offset).min(REALIGN_CHUNK); + let buf = &mut buf[..(chunk_size / 4)]; + + // Safety: this is written to completely by DMA before any reads + let buf = unsafe { &mut *(buf as *mut [MaybeUninit] as *mut [u32]) }; + self.background_read(offset + chunk_offset as u32, buf)?.await; + + // Safety: [u8] has more relaxed alignment and size requirements than [u32], so this is just aliasing + let buf = unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const _, buf.len() * 4) }; + bytes[chunk_offset..(chunk_offset + chunk_size)].copy_from_slice(&buf[..chunk_size]); + + chunk_offset += chunk_size; + } + + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, M, FLASH_SIZE> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, M, FLASH_SIZE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, M, FLASH_SIZE> {} + +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::MultiwriteNorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ +} + +impl<'d, T: Instance, M: Mode, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, M, FLASH_SIZE> { + const WRITE_SIZE: usize = WRITE_SIZE; + + const ERASE_SIZE: usize = ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::ReadNorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ + const READ_SIZE: usize = ASYNC_READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> embedded_storage_async::nor_flash::NorFlash + for Flash<'d, T, Async, FLASH_SIZE> +{ + const WRITE_SIZE: usize = WRITE_SIZE; + + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } +} + +#[allow(dead_code)] +mod ram_helpers { + use super::*; + use crate::rom_data; + + #[repr(C)] + struct FlashFunctionPointers<'a> { + connect_internal_flash: unsafe extern "C" fn() -> (), + flash_exit_xip: unsafe extern "C" fn() -> (), + flash_range_erase: Option ()>, + flash_range_program: Option ()>, + flash_flush_cache: unsafe extern "C" fn() -> (), + flash_enter_cmd_xip: unsafe extern "C" fn() -> (), + phantom: PhantomData<&'a ()>, + } + + #[allow(unused)] + fn flash_function_pointers(erase: bool, write: bool) -> FlashFunctionPointers<'static> { + FlashFunctionPointers { + connect_internal_flash: rom_data::connect_internal_flash::ptr(), + flash_exit_xip: rom_data::flash_exit_xip::ptr(), + flash_range_erase: if erase { + Some(rom_data::flash_range_erase::ptr()) + } else { + None + }, + flash_range_program: if write { + Some(rom_data::flash_range_program::ptr()) + } else { + None + }, + flash_flush_cache: rom_data::flash_flush_cache::ptr(), + flash_enter_cmd_xip: rom_data::flash_enter_cmd_xip::ptr(), + phantom: PhantomData, + } + } + + #[allow(unused)] + /// # Safety + /// + /// `boot2` must contain a valid 2nd stage boot loader which can be called to re-initialize XIP mode + unsafe fn flash_function_pointers_with_boot2( + erase: bool, + write: bool, + boot2: &[u32; 64], + ) -> FlashFunctionPointers<'_> { + let boot2_fn_ptr = (boot2 as *const u32 as *const u8).offset(1); + let boot2_fn: unsafe extern "C" fn() -> () = core::mem::transmute(boot2_fn_ptr); + FlashFunctionPointers { + connect_internal_flash: rom_data::connect_internal_flash::ptr(), + flash_exit_xip: rom_data::flash_exit_xip::ptr(), + flash_range_erase: if erase { + Some(rom_data::flash_range_erase::ptr()) + } else { + None + }, + flash_range_program: if write { + Some(rom_data::flash_range_program::ptr()) + } else { + None + }, + flash_flush_cache: rom_data::flash_flush_cache::ptr(), + flash_enter_cmd_xip: boot2_fn, + phantom: PhantomData, + } + } + + /// Erase a flash range starting at `addr` with length `len`. + /// + /// `addr` and `len` must be multiples of 4096 + /// + /// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_erase(addr: u32, len: u32) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTRAM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(true, false, &boot2) + } else { + flash_function_pointers(true, false) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner(addr, len, None, &ptrs as *const FlashFunctionPointers); + } + + /// Erase and rewrite a flash range starting at `addr` with data `data`. + /// + /// `addr` and `data.len()` must be multiples of 4096 + /// + /// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTRAM_BASE as *const u8, (boot2).as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(true, true, &boot2) + } else { + flash_function_pointers(true, true) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner( + addr, + data.len() as u32, + Some(data), + &ptrs as *const FlashFunctionPointers, + ); + } + + /// Write a flash range starting at `addr` with data `data`. + /// + /// `addr` and `data.len()` must be multiples of 256 + /// + /// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_program(addr: u32, data: &[u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + #[cfg(feature = "rp2040")] + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + #[cfg(feature = "_rp235x")] + core::ptr::copy_nonoverlapping(BOOTRAM_BASE as *const u8, boot2.as_mut_ptr() as *mut u8, 256); + flash_function_pointers_with_boot2(false, true, &boot2) + } else { + flash_function_pointers(false, true) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner( + addr, + data.len() as u32, + Some(data), + &ptrs as *const FlashFunctionPointers, + ); + } + + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 + #[inline(never)] + #[unsafe(link_section = ".data.ram_func")] + #[cfg(feature = "rp2040")] + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + #[cfg(target_arch = "arm")] + core::arch::asm!( + "mov r8, r0", + "mov r9, r2", + "mov r10, r1", + "ldr r4, [{ptrs}, #0]", + "blx r4", // connect_internal_flash() + + "ldr r4, [{ptrs}, #4]", + "blx r4", // flash_exit_xip() + + "mov r0, r8", // r0 = addr + "mov r1, r10", // r1 = len + "movs r2, #1", + "lsls r2, r2, #31", // r2 = 1 << 31 + "movs r3, #0", // r3 = 0 + "ldr r4, [{ptrs}, #8]", + "cmp r4, #0", + "beq 2f", + "blx r4", // flash_range_erase(addr, len, 1 << 31, 0) + "2:", + + "mov r0, r8", // r0 = addr + "mov r1, r9", // r0 = data + "mov r2, r10", // r2 = len + "ldr r4, [{ptrs}, #12]", + "cmp r4, #0", + "beq 2f", + "blx r4", // flash_range_program(addr, data, len); + "2:", + + "ldr r4, [{ptrs}, #16]", + "blx r4", // flash_flush_cache(); + + "ldr r4, [{ptrs}, #20]", + "blx r4", // flash_enter_cmd_xip(); + ptrs = in(reg) ptrs, + // Registers r8-r15 are not allocated automatically, + // so assign them manually. We need to use them as + // otherwise there are not enough registers available. + in("r0") addr, + in("r2") data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()), + in("r1") len, + out("r3") _, + out("r4") _, + lateout("r8") _, + lateout("r9") _, + lateout("r10") _, + clobber_abi("C"), + ); + } + + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 + #[inline(never)] + #[unsafe(link_section = ".data.ram_func")] + #[cfg(feature = "_rp235x")] + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + let data = data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()); + ((*ptrs).connect_internal_flash)(); + ((*ptrs).flash_exit_xip)(); + if (*ptrs).flash_range_erase.is_some() { + ((*ptrs).flash_range_erase.unwrap())(addr, len as usize, 1 << 31, 0); + } + if (*ptrs).flash_range_program.is_some() { + ((*ptrs).flash_range_program.unwrap())(addr, data as *const _, len as usize); + } + ((*ptrs).flash_flush_cache)(); + ((*ptrs).flash_enter_cmd_xip)(); + } + + #[repr(C)] + struct FlashCommand { + cmd_addr: *const u8, + cmd_addr_len: u32, + dummy_len: u32, + data: *mut u8, + data_len: u32, + } + + /// Return SPI flash unique ID + /// + /// Not all SPI flashes implement this command, so check the JEDEC + /// ID before relying on it. The Winbond parts commonly seen on + /// RP2040 devboards (JEDEC=0xEF7015) support an 8-byte unique ID; + /// https://forums.raspberrypi.com/viewtopic.php?t=331949 suggests + /// that LCSC (Zetta) parts have a 16-byte unique ID (which is + /// *not* unique in just its first 8 bytes), + /// JEDEC=0xBA6015. Macronix and Spansion parts do not have a + /// unique ID. + /// + /// The returned bytes are relatively predictable and should be + /// salted and hashed before use if that is an issue (e.g. for MAC + /// addresses). + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] + pub unsafe fn flash_unique_id(out: &mut [u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + }; + + // 4B - read unique ID + let cmd = [0x4B]; + read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers); + } + + /// Return SPI flash JEDEC ID + /// + /// This is the three-byte manufacturer-and-model identifier + /// commonly used to check before using manufacturer-specific SPI + /// flash features, e.g. 0xEF7015 for Winbond W25Q16JV. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[cfg(feature = "rp2040")] + pub unsafe fn flash_jedec_id() -> u32 { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + }; + + let mut id = [0u8; 4]; + // 9F - read JEDEC ID + let cmd = [0x9F]; + read_flash(&cmd[..], 0, &mut id[1..4], &ptrs as *const FlashFunctionPointers); + u32::from_be_bytes(id) + } + + #[cfg(feature = "rp2040")] + unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) { + read_flash_inner( + FlashCommand { + cmd_addr: cmd_addr.as_ptr(), + cmd_addr_len: cmd_addr.len() as u32, + dummy_len, + data: out.as_mut_ptr(), + data_len: out.len() as u32, + }, + ptrs, + ); + } + + /// Issue a generic SPI flash read command + /// + /// # Arguments + /// + /// * `cmd` - `FlashCommand` structure + /// * `ptrs` - Flash function pointers as per `write_flash_inner` + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[inline(never)] + #[unsafe(link_section = ".data.ram_func")] + #[cfg(feature = "rp2040")] + unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) { + #[cfg(target_arch = "arm")] + core::arch::asm!( + "mov r10, r0", // cmd + "mov r5, r1", // ptrs + + "ldr r4, [r5, #0]", + "blx r4", // connect_internal_flash() + + "ldr r4, [r5, #4]", + "blx r4", // flash_exit_xip() + + + "movs r4, #0x18", + "lsls r4, r4, #24", // 0x18000000, SSI, RP2040 datasheet 4.10.13 + + // Disable, write 0 to SSIENR + "movs r0, #0", + "str r0, [r4, #8]", // SSIENR + + // Write ctrlr0 + "movs r0, #0x3", + "lsls r0, r0, #8", // TMOD=0x300 + "ldr r1, [r4, #0]", // CTRLR0 + "orrs r1, r0", + "str r1, [r4, #0]", + + // Write ctrlr1 with len-1 + "mov r3, r10", // cmd + "ldr r0, [r3, #8]", // dummy_len + "ldr r1, [r3, #16]", // data_len + "add r0, r1", + "subs r0, #1", + "str r0, [r4, #0x04]", // CTRLR1 + + // Enable, write 1 to ssienr + "movs r0, #1", + "str r0, [r4, #8]", // SSIENR + + // Write cmd/addr phase to DR + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldr r0, [r3, #0]", // cmd_addr + "ldr r1, [r3, #4]", // cmd_addr_len + "3:", + "ldrb r3, [r0]", + "strb r3, [r2]", // DR + "adds r0, #1", + "subs r1, #1", + "bne 3b", + + // Skip any dummy cycles + "mov r3, r10", // cmd + "ldr r1, [r3, #8]", // dummy_len + "cmp r1, #0", + "beq 9f", + "4:", + "ldr r3, [r4, #0x28]", // SR + "movs r2, #0x8", + "tst r3, r2", // SR.RFNE + "beq 4b", + + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldrb r3, [r2]", // DR + "subs r1, #1", + "bne 4b", + + // Read RX fifo + "9:", + "mov r2, r10", // cmd + "ldr r0, [r2, #12]", // data + "ldr r1, [r2, #16]", // data_len + + "2:", + "ldr r3, [r4, #0x28]", // SR + "movs r2, #0x8", + "tst r3, r2", // SR.RFNE + "beq 2b", + + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldrb r3, [r2]", // DR + "strb r3, [r0]", + "adds r0, #1", + "subs r1, #1", + "bne 2b", + + // Disable, write 0 to ssienr + "movs r0, #0", + "str r0, [r4, #8]", // SSIENR + + // Write 0 to CTRLR1 (returning to its default value) + // + // flash_enter_cmd_xip does NOT do this, and everything goes + // wrong unless we do it here + "str r0, [r4, #4]", // CTRLR1 + + "ldr r4, [r5, #20]", + "blx r4", // flash_enter_cmd_xip(); + + in("r0") &cmd as *const FlashCommand, + in("r1") ptrs, + out("r2") _, + out("r3") _, + out("r4") _, + out("r5") _, + // Registers r8-r10 are used to store values + // from r0-r2 in registers not clobbered by + // function calls. + // The values can't be passed in using r8-r10 directly + // due to https://github.com/rust-lang/rust/issues/99071 + out("r10") _, + clobber_abi("C"), + ); + } +} + +/// Make sure to uphold the contract points with rp2040-flash. +/// - interrupts must be disabled +/// - DMA must not access flash memory +pub(crate) unsafe fn in_ram(operation: impl FnOnce()) -> Result<(), Error> { + // Make sure we're running on CORE0 + let core_id: u32 = pac::SIO.cpuid().read(); + if core_id != 0 { + return Err(Error::InvalidCore); + } + + // Make sure CORE1 is paused during the entire duration of the RAM function + crate::multicore::pause_core1(); + + critical_section::with(|_| { + // Wait for all DMA channels in flash to finish before ram operation + const SRAM_LOWER: u32 = 0x2000_0000; + for n in 0..crate::dma::CHANNEL_COUNT { + let ch = crate::pac::DMA.ch(n); + while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} + } + // Wait for completion of any background reads + while pac::XIP_CTRL.stream_ctr().read().0 > 0 {} + + // Run our flash operation in RAM + operation(); + }); + + // Resume CORE1 execution + crate::multicore::resume_core1(); + Ok(()) +} + +trait SealedInstance {} +trait SealedMode {} + +/// Flash instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType {} +/// Flash mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +impl SealedInstance for FLASH {} +impl Instance for FLASH {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Flash blocking mode. +pub struct Blocking; +/// Flash async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); diff --git a/embassy-rp-fork/src/float/add_sub.rs b/embassy-rp-fork/src/float/add_sub.rs new file mode 100644 index 0000000..673544c --- /dev/null +++ b/embassy-rp-fork/src/float/add_sub.rs @@ -0,0 +1,92 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/add_sub.rs + +use super::{Float, Int}; +use crate::rom_data; + +trait ROMAdd { + fn rom_add(self, b: Self) -> Self; +} + +impl ROMAdd for f32 { + fn rom_add(self, b: Self) -> Self { + rom_data::float_funcs::fadd(self, b) + } +} + +impl ROMAdd for f64 { + fn rom_add(self, b: Self) -> Self { + rom_data::double_funcs::dadd(self, b) + } +} + +fn add(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + let class_a = a.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + let class_b = b.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + + if class_a == F::Int::ZERO && class_b == F::Int::ZERO { + // inf + inf = inf + return a; + } + if class_a == F::SIGN_MASK && class_b == F::SIGN_MASK { + // -inf + (-inf) = -inf + return a; + } + + // Sign mismatch, or either is NaN already + return F::NAN; + } + + // [-]inf/NaN + X = [-]inf/NaN + return a; + } + + if b.is_not_finite() { + // X + [-]inf/NaN = [-]inf/NaN + return b; + } + + a.rom_add(b) +} + +intrinsics! { + #[alias = __addsf3vfp] + #[aeabi = __aeabi_fadd] + extern "C" fn __addsf3(a: f32, b: f32) -> f32 { + add(a, b) + } + + #[bootrom_v2] + #[alias = __adddf3vfp] + #[aeabi = __aeabi_dadd] + extern "C" fn __adddf3(a: f64, b: f64) -> f64 { + add(a, b) + } + + // The ROM just implements subtraction the same way, so just do it here + // and save the work of implementing more complicated NaN/inf handling. + + #[alias = __subsf3vfp] + #[aeabi = __aeabi_fsub] + extern "C" fn __subsf3(a: f32, b: f32) -> f32 { + add(a, -b) + } + + #[bootrom_v2] + #[alias = __subdf3vfp] + #[aeabi = __aeabi_dsub] + extern "C" fn __subdf3(a: f64, b: f64) -> f64 { + add(a, -b) + } + + extern "aapcs" fn __aeabi_frsub(a: f32, b: f32) -> f32 { + add(b, -a) + } + + #[bootrom_v2] + extern "aapcs" fn __aeabi_drsub(a: f64, b: f64) -> f64 { + add(b, -a) + } +} diff --git a/embassy-rp-fork/src/float/cmp.rs b/embassy-rp-fork/src/float/cmp.rs new file mode 100644 index 0000000..f917eb9 --- /dev/null +++ b/embassy-rp-fork/src/float/cmp.rs @@ -0,0 +1,193 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/cmp.rs + +use super::Float; +use crate::rom_data; + +trait ROMCmp { + fn rom_cmp(self, b: Self) -> i32; +} + +impl ROMCmp for f32 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::float_funcs::fcmp(self, b) + } +} + +impl ROMCmp for f64 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::double_funcs::dcmp(self, b) + } +} + +fn le_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { 1 } else { a.rom_cmp(b) } +} + +fn ge_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { -1 } else { a.rom_cmp(b) } +} + +intrinsics! { + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqsf2, __ltsf2, __nesf2] + extern "C" fn __lesf2(a: f32, b: f32) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqdf2, __ltdf2, __nedf2] + extern "C" fn __ledf2(a: f64, b: f64) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtsf2] + extern "C" fn __gesf2(a: f32, b: f32) -> i32 { + ge_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtdf2] + extern "C" fn __gedf2(a: f64, b: f64) -> i32 { + ge_abi(a, b) + } + + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmple(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpge(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpeq(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmplt(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpgt(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmple(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpge(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpeq(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmplt(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpgt(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gesf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gedf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtsf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtdf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __lesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ledf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nedf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } +} diff --git a/embassy-rp-fork/src/float/conv.rs b/embassy-rp-fork/src/float/conv.rs new file mode 100644 index 0000000..021826e --- /dev/null +++ b/embassy-rp-fork/src/float/conv.rs @@ -0,0 +1,157 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs + +use super::Float; +use crate::rom_data; + +// Some of these are also not connected in the Pico SDK. This is probably +// because the ROM version actually does a fixed point conversion, just with +// the fractional width set to zero. + +intrinsics! { + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2f] + extern "C" fn __floatsisf(i: i32) -> f32 { + rom_data::float_funcs::int_to_float(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2d] + extern "C" fn __floatsidf(i: i32) -> f64 { + rom_data::double_funcs::int_to_double(i) + } + + // Questionable gain + #[aeabi = __aeabi_l2f] + extern "C" fn __floatdisf(i: i64) -> f32 { + rom_data::float_funcs::int64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_l2d] + extern "C" fn __floatdidf(i: i64) -> f64 { + rom_data::double_funcs::int64_to_double(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_ui2f] + extern "C" fn __floatunsisf(i: u32) -> f32 { + rom_data::float_funcs::uint_to_float(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ui2d] + extern "C" fn __floatunsidf(i: u32) -> f64 { + rom_data::double_funcs::uint_to_double(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ul2f] + extern "C" fn __floatundisf(i: u64) -> f32 { + rom_data::float_funcs::uint64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_ul2d] + extern "C" fn __floatundidf(i: u64) -> f64 { + rom_data::double_funcs::uint64_to_double(i) + } + + + // The Pico SDK does some optimization here (e.x. fast paths for zero and + // one), but we can just directly connect it. + #[aeabi = __aeabi_f2iz] + extern "C" fn __fixsfsi(f: f32) -> i32 { + rom_data::float_funcs::float_to_int(f) + } + + #[bootrom_v2] + #[aeabi = __aeabi_f2lz] + extern "C" fn __fixsfdi(f: f32) -> i64 { + rom_data::float_funcs::float_to_int64(f) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2iz] + extern "C" fn __fixdfsi(f: f64) -> i32 { + rom_data::double_funcs::double_to_int(f) + } + + // Like with the 32 bit version, there's optimization that we just + // skip. + #[bootrom_v2] + #[aeabi = __aeabi_d2lz] + extern "C" fn __fixdfdi(f: f64) -> i64 { + rom_data::double_funcs::double_to_int64(f) + } + + #[slower_than_default] + #[aeabi = __aeabi_f2uiz] + extern "C" fn __fixunssfsi(f: f32) -> u32 { + rom_data::float_funcs::float_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_f2ulz] + extern "C" fn __fixunssfdi(f: f32) -> u64 { + rom_data::float_funcs::float_to_uint64(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2uiz] + extern "C" fn __fixunsdfsi(f: f64) -> u32 { + rom_data::double_funcs::double_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2ulz] + extern "C" fn __fixunsdfdi(f: f64) -> u64 { + rom_data::double_funcs::double_to_uint64(f) + } + + #[bootrom_v2] + #[alias = __extendsfdf2vfp] + #[aeabi = __aeabi_f2d] + extern "C" fn __extendsfdf2(f: f32) -> f64 { + if f.is_not_finite() { + return f64::from_repr( + // Not finite + f64::EXPONENT_MASK | + // Preserve NaN or inf + ((f.repr() & f32::SIGNIFICAND_MASK) as u64) | + // Preserve sign + ((f.repr() & f32::SIGN_MASK) as u64) << (f64::BITS-f32::BITS) + ); + } + rom_data::float_funcs::float_to_double(f) + } + + #[bootrom_v2] + #[alias = __truncdfsf2vfp] + #[aeabi = __aeabi_d2f] + extern "C" fn __truncdfsf2(f: f64) -> f32 { + if f.is_not_finite() { + let mut repr: u32 = + // Not finite + f32::EXPONENT_MASK | + // Preserve sign + ((f.repr() & f64::SIGN_MASK) >> (f64::BITS-f32::BITS)) as u32; + // Set NaN + if (f.repr() & f64::SIGNIFICAND_MASK) != 0 { + repr |= 1; + } + return f32::from_repr(repr); + } + rom_data::double_funcs::double_to_float(f) + } +} diff --git a/embassy-rp-fork/src/float/div.rs b/embassy-rp-fork/src/float/div.rs new file mode 100644 index 0000000..87d1e38 --- /dev/null +++ b/embassy-rp-fork/src/float/div.rs @@ -0,0 +1,139 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs + +use super::Float; +use crate::rom_data; + +// Make sure this stays as a separate call, because when it's inlined the +// compiler will move the save of the registers used to contain the divider +// state into the function prologue. That save and restore (push/pop) takes +// longer than the actual division, so doing it in the common case where +// they are not required wastes a lot of time. +#[inline(never)] +#[cold] +fn save_divider_and_call(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = rp_pac::SIO; + + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The Pico SDK ensures this by using a 6 cycle push and two 1 cycle reads. + // Since we can't be sure the Rust implementation will optimize to the same, + // just use an explicit wait. + while !sio.div().csr().read().ready() {} + + // Read the quotient last, since that's what clears the dirty flag + let dividend = sio.div().udividend().read(); + let divisor = sio.div().udivisor().read(); + let remainder = sio.div().remainder().read(); + let quotient = sio.div().quotient().read(); + + // If we get interrupted here (before a write sets the DIRTY flag) its fine, since + // we have the full state, so the interruptor doesn't have to restore it. Once the + // write happens and the DIRTY flag is set, the interruptor becomes responsible for + // restoring our state. + let result = f(); + + // If we are interrupted here, then the interruptor will start an incorrect calculation + // using a wrong divisor, but we'll restore the divisor and result ourselves correctly. + // This sets DIRTY, so any interruptor will save the state. + sio.div().udividend().write_value(dividend); + // If we are interrupted here, the interruptor may start the calculation using + // incorrectly signed inputs, but we'll restore the result ourselves. + // This sets DIRTY, so any interruptor will save the state. + sio.div().udivisor().write_value(divisor); + // If we are interrupted here, the interruptor will have restored everything but the + // quotient may be wrongly signed. If the calculation started by the above writes is + // still ongoing it is stopped, so it won't replace the result we're restoring. + // DIRTY and READY set, but only DIRTY matters to make the interruptor save the state. + sio.div().remainder().write_value(remainder); + // State fully restored after the quotient write. This sets both DIRTY and READY, so + // whatever we may have interrupted can read the result. + sio.div().quotient().write_value(quotient); + + result +} + +fn save_divider(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = rp_pac::SIO; + if !sio.div().csr().read().dirty() { + // Not dirty, so nothing is waiting for the calculation. So we can just + // issue it directly without a save/restore. + f() + } else { + save_divider_and_call(f) + } +} + +trait ROMDiv { + fn rom_div(self, b: Self) -> Self; +} + +impl ROMDiv for f32 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::float_funcs::fdiv(self, b)) + } +} + +impl ROMDiv for f64 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::double_funcs::ddiv(self, b)) + } +} + +fn div(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + // inf/NaN / inf/NaN = NaN + return F::NAN; + } + + if b.is_zero() { + // inf/NaN / 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN / (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN / X = [-]inf/NaN + a + }; + } + + if b.is_nan() { + // X / NaN = NaN + return b; + } + + // ROM handles X / 0 = [-]inf and X / [-]inf = [-]0, so we only + // need to catch 0 / 0 + if b.is_zero() && a.is_zero() { + return F::NAN; + } + + a.rom_div(b) +} + +intrinsics! { + #[alias = __divsf3vfp] + #[aeabi = __aeabi_fdiv] + extern "C" fn __divsf3(a: f32, b: f32) -> f32 { + div(a, b) + } + + #[bootrom_v2] + #[alias = __divdf3vfp] + #[aeabi = __aeabi_ddiv] + extern "C" fn __divdf3(a: f64, b: f64) -> f64 { + div(a, b) + } +} diff --git a/embassy-rp-fork/src/float/functions.rs b/embassy-rp-fork/src/float/functions.rs new file mode 100644 index 0000000..1701682 --- /dev/null +++ b/embassy-rp-fork/src/float/functions.rs @@ -0,0 +1,231 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/functions.rs + +use crate::float::{Float, Int}; +use crate::rom_data; + +trait ROMFunctions { + fn sqrt(self) -> Self; + fn ln(self) -> Self; + fn exp(self) -> Self; + fn sin(self) -> Self; + fn cos(self) -> Self; + fn tan(self) -> Self; + fn atan2(self, y: Self) -> Self; + + fn to_trig_range(self) -> Self; +} + +impl ROMFunctions for f32 { + fn sqrt(self) -> Self { + rom_data::float_funcs::fsqrt(self) + } + + fn ln(self) -> Self { + rom_data::float_funcs::fln(self) + } + + fn exp(self) -> Self { + rom_data::float_funcs::fexp(self) + } + + fn sin(self) -> Self { + rom_data::float_funcs::fsin(self) + } + + fn cos(self) -> Self { + rom_data::float_funcs::fcos(self) + } + + fn tan(self) -> Self { + rom_data::float_funcs::ftan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::float_funcs::fatan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -128 < X < 128, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 134 { + self + } else { + self % (core::f32::consts::PI * 2.0) + } + } +} + +impl ROMFunctions for f64 { + fn sqrt(self) -> Self { + rom_data::double_funcs::dsqrt(self) + } + + fn ln(self) -> Self { + rom_data::double_funcs::dln(self) + } + + fn exp(self) -> Self { + rom_data::double_funcs::dexp(self) + } + + fn sin(self) -> Self { + rom_data::double_funcs::dsin(self) + } + + fn cos(self) -> Self { + rom_data::double_funcs::dcos(self) + } + fn tan(self) -> Self { + rom_data::double_funcs::dtan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::double_funcs::datan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -1024 < X < 1024, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 1033 { + self + } else { + self % (core::f64::consts::PI * 2.0) + } + } +} + +fn is_negative_nonzero_or_nan(f: F) -> bool { + let repr = f.repr(); + if (repr & F::SIGN_MASK) != F::Int::ZERO { + // Negative, so anything other than exactly zero + return (repr & (!F::SIGN_MASK)) != F::Int::ZERO; + } + // NaN + (repr & (F::EXPONENT_MASK | F::SIGNIFICAND_MASK)) > F::EXPONENT_MASK +} + +fn sqrt(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.sqrt() + } +} + +fn ln(f: F) -> F { + if is_negative_nonzero_or_nan(f) { F::NAN } else { f.ln() } +} + +fn exp(f: F) -> F { + if f.is_nan() { F::NAN } else { f.exp() } +} + +fn sin(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().sin() + } +} + +fn cos(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().cos() + } +} + +fn tan(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().tan() + } +} + +fn atan2(x: F, y: F) -> F { + if x.is_nan() || y.is_nan() { + F::NAN + } else { + x.to_trig_range().atan2(y) + } +} + +// Name collisions +mod intrinsics { + intrinsics! { + extern "C" fn sqrtf(f: f32) -> f32 { + super::sqrt(f) + } + + #[bootrom_v2] + extern "C" fn sqrt(f: f64) -> f64 { + super::sqrt(f) + } + + extern "C" fn logf(f: f32) -> f32 { + super::ln(f) + } + + #[bootrom_v2] + extern "C" fn log(f: f64) -> f64 { + super::ln(f) + } + + extern "C" fn expf(f: f32) -> f32 { + super::exp(f) + } + + #[bootrom_v2] + extern "C" fn exp(f: f64) -> f64 { + super::exp(f) + } + + #[slower_than_default] + extern "C" fn sinf(f: f32) -> f32 { + super::sin(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn sin(f: f64) -> f64 { + super::sin(f) + } + + #[slower_than_default] + extern "C" fn cosf(f: f32) -> f32 { + super::cos(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn cos(f: f64) -> f64 { + super::cos(f) + } + + #[slower_than_default] + extern "C" fn tanf(f: f32) -> f32 { + super::tan(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn tan(f: f64) -> f64 { + super::tan(f) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2f(a: f32, b: f32) -> f32 { + super::atan2(a, b) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2(a: f64, b: f64) -> f64 { + super::atan2(a, b) + } + } +} diff --git a/embassy-rp-fork/src/float/mod.rs b/embassy-rp-fork/src/float/mod.rs new file mode 100644 index 0000000..3ad6f1c --- /dev/null +++ b/embassy-rp-fork/src/float/mod.rs @@ -0,0 +1,150 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mod.rs + +use core::ops; + +// Borrowed and simplified from compiler-builtins so we can use bit ops +// on floating point without macro soup. +pub(crate) trait Int: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::SubAssign + + ops::BitAndAssign + + ops::BitOrAssign + + ops::BitXorAssign + + ops::ShlAssign + + ops::ShrAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Shl + + ops::Shr + + ops::BitOr + + ops::BitXor + + ops::BitAnd + + ops::Not +{ + const ZERO: Self; +} + +macro_rules! int_impl { + ($ty:ty) => { + impl Int for $ty { + const ZERO: Self = 0; + } + }; +} + +int_impl!(u32); +int_impl!(u64); + +pub(crate) trait Float: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::MulAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Rem +{ + /// A uint of the same with as the float + type Int: Int; + + /// NaN representation for the float + const NAN: Self; + + /// The bitwidth of the float type + const BITS: u32; + + /// The bitwidth of the significand + const SIGNIFICAND_BITS: u32; + + /// A mask for the sign bit + const SIGN_MASK: Self::Int; + + /// A mask for the significand + const SIGNIFICAND_MASK: Self::Int; + + /// A mask for the exponent + const EXPONENT_MASK: Self::Int; + + /// Returns `self` transmuted to `Self::Int` + fn repr(self) -> Self::Int; + + /// Returns a `Self::Int` transmuted back to `Self` + fn from_repr(a: Self::Int) -> Self; + + /// Return a sign swapped `self` + fn negate(self) -> Self; + + /// Returns true if `self` is either NaN or infinity + fn is_not_finite(self) -> bool { + (self.repr() & Self::EXPONENT_MASK) == Self::EXPONENT_MASK + } + + /// Returns true if `self` is infinity + #[allow(unused)] + fn is_infinity(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) == Self::EXPONENT_MASK + } + + /// Returns true if `self is NaN + fn is_nan(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) > Self::EXPONENT_MASK + } + + /// Returns true if `self` is negative + fn is_sign_negative(self) -> bool { + (self.repr() & Self::SIGN_MASK) != Self::Int::ZERO + } + + /// Returns true if `self` is zero (either sign) + fn is_zero(self) -> bool { + (self.repr() & (Self::SIGNIFICAND_MASK | Self::EXPONENT_MASK)) == Self::Int::ZERO + } +} + +macro_rules! float_impl { + ($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => { + impl Float for $ty { + type Int = $ity; + + const NAN: Self = <$ty>::NAN; + + const BITS: u32 = $bits; + const SIGNIFICAND_BITS: u32 = $significand_bits; + + const SIGN_MASK: Self::Int = 1 << (Self::BITS - 1); + const SIGNIFICAND_MASK: Self::Int = (1 << Self::SIGNIFICAND_BITS) - 1; + const EXPONENT_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIGNIFICAND_MASK); + + fn repr(self) -> Self::Int { + self.to_bits() + } + + fn from_repr(a: Self::Int) -> Self { + Self::from_bits(a) + } + + fn negate(self) -> Self { + -self + } + } + }; +} + +float_impl!(f32, u32, 32, 23); +float_impl!(f64, u64, 64, 52); + +mod add_sub; +mod cmp; +mod conv; +mod div; +mod functions; +mod mul; diff --git a/embassy-rp-fork/src/float/mul.rs b/embassy-rp-fork/src/float/mul.rs new file mode 100644 index 0000000..ceb0210 --- /dev/null +++ b/embassy-rp-fork/src/float/mul.rs @@ -0,0 +1,70 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mul.rs + +use super::Float; +use crate::rom_data; + +trait ROMMul { + fn rom_mul(self, b: Self) -> Self; +} + +impl ROMMul for f32 { + fn rom_mul(self, b: Self) -> Self { + rom_data::float_funcs::fmul(self, b) + } +} + +impl ROMMul for f64 { + fn rom_mul(self, b: Self) -> Self { + rom_data::double_funcs::dmul(self, b) + } +} + +fn mul(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_zero() { + // [-]inf/NaN * 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN * (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN * X = [-]inf/NaN + a + }; + } + + if b.is_not_finite() { + if a.is_zero() { + // 0 * [-]inf/NaN = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // (-X) * [+/-]inf/NaN = [-/+]inf/NaN + b.negate() + } else { + // X * [-]inf/NaN = [-]inf/NaN + b + }; + } + + a.rom_mul(b) +} + +intrinsics! { + #[alias = __mulsf3vfp] + #[aeabi = __aeabi_fmul] + extern "C" fn __mulsf3(a: f32, b: f32) -> f32 { + mul(a, b) + } + + #[bootrom_v2] + #[alias = __muldf3vfp] + #[aeabi = __aeabi_dmul] + extern "C" fn __muldf3(a: f64, b: f64) -> f64 { + mul(a, b) + } +} diff --git a/embassy-rp-fork/src/fmt.rs b/embassy-rp-fork/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-rp-fork/src/fmt.rs @@ -0,0 +1,270 @@ +#![macro_use] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +#[collapse_debuginfo(yes)] +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::unreachable!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::unreachable!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[collapse_debuginfo(yes)] +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-rp-fork/src/gpio.rs b/embassy-rp-fork/src/gpio.rs new file mode 100644 index 0000000..154fc15 --- /dev/null +++ b/embassy-rp-fork/src/gpio.rs @@ -0,0 +1,1437 @@ +//! GPIO driver. +#![macro_use] +use core::convert::Infallible; +use core::future::Future; +use core::pin::Pin as FuturePin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{Peri, PeripheralType, impl_peripheral}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::InterruptExt; +use crate::pac::SIO; +use crate::pac::common::{RW, Reg}; +use crate::{RegExt, interrupt, pac, peripherals}; + +#[cfg(any(feature = "rp2040", feature = "rp235xa"))] +pub(crate) const BANK0_PIN_COUNT: usize = 30; +#[cfg(feature = "rp235xb")] +pub(crate) const BANK0_PIN_COUNT: usize = 48; + +static BANK0_WAKERS: [AtomicWaker; BANK0_PIN_COUNT] = [const { AtomicWaker::new() }; BANK0_PIN_COUNT]; +#[cfg(feature = "qspi-as-gpio")] +const QSPI_PIN_COUNT: usize = 6; +#[cfg(feature = "qspi-as-gpio")] +static QSPI_WAKERS: [AtomicWaker; QSPI_PIN_COUNT] = [const { AtomicWaker::new() }; QSPI_PIN_COUNT]; + +/// Represents a digital input or output level. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Level { + /// Logical low. + Low, + /// Logical high. + High, +} + +impl From for Level { + fn from(val: bool) -> Self { + match val { + true => Self::High, + false => Self::Low, + } + } +} + +impl From for bool { + fn from(level: Level) -> bool { + match level { + Level::Low => false, + Level::High => true, + } + } +} + +/// Represents a pull setting for an input. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Pull { + /// No pull. + None, + /// Internal pull-up resistor. + Up, + /// Internal pull-down resistor. + Down, +} + +/// Drive strength of an output +#[derive(Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Drive { + /// 2 mA drive. + _2mA, + /// 4 mA drive. + _4mA, + /// 8 mA drive. + _8mA, + /// 1 2mA drive. + _12mA, +} +/// Slew rate of an output +#[derive(Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SlewRate { + /// Fast slew rate. + Fast, + /// Slow slew rate. + Slow, +} + +/// A GPIO bank with up to 32 pins. +#[derive(Debug, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Bank { + /// Bank 0. + Bank0 = 0, + /// QSPI. + #[cfg(feature = "qspi-as-gpio")] + Qspi = 1, +} + +/// Dormant mode config. +#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DormantWakeConfig { + /// Wake on edge high. + pub edge_high: bool, + /// Wake on edge low. + pub edge_low: bool, + /// Wake on level high. + pub level_high: bool, + /// Wake on level low. + pub level_low: bool, +} + +/// GPIO input driver. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Input<'d> { + pin: Flex<'d>, +} + +impl<'d> Input<'d> { + /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, pull: Pull) -> Self { + let mut pin = Flex::new(pin); + pin.set_as_input(); + pin.set_pull(pull); + Self { pin } + } + + /// Set the pin's Schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.set_schmitt(enable) + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.pin.get_level() + } + + /// Configure the input logic inversion of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_input_inversion(invert) + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await; + } + + /// Configure dormant wake. + #[inline] + pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { + self.pin.dormant_wake(cfg) + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } +} + +/// Interrupt trigger levels. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InterruptTrigger { + /// Trigger on pin low. + LevelLow, + /// Trigger on pin high. + LevelHigh, + /// Trigger on high to low transition. + EdgeLow, + /// Trigger on low to high transition. + EdgeHigh, + /// Trigger on any transition. + AnyEdge, +} + +pub(crate) unsafe fn init() { + interrupt::IO_IRQ_BANK0.disable(); + interrupt::IO_IRQ_BANK0.set_priority(interrupt::Priority::P3); + interrupt::IO_IRQ_BANK0.enable(); + + #[cfg(feature = "qspi-as-gpio")] + { + interrupt::IO_IRQ_QSPI.disable(); + interrupt::IO_IRQ_QSPI.set_priority(interrupt::Priority::P3); + interrupt::IO_IRQ_QSPI.enable(); + } +} + +#[cfg(feature = "rt")] +fn irq_handler(bank: pac::io::Io, wakers: &[AtomicWaker; N]) { + let cpu = SIO.cpuid().read() as usize; + // There are two sets of interrupt registers, one for cpu0 and one for cpu1 + // and here we are selecting the set that belongs to the currently executing + // cpu. + let proc_intx: pac::io::Int = bank.int_proc(cpu); + for pin in 0..N { + // There are 4 raw interrupt status registers, PROCx_INTS0, PROCx_INTS1, + // PROCx_INTS2, and PROCx_INTS3, and we are selecting the one that the + // current pin belongs to. + let intsx = proc_intx.ints(pin / 8); + // The status register is divided into groups of four, one group for + // each pin. Each group consists of four trigger levels LEVEL_LOW, + // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. + let pin_group = pin % 8; + let event = (intsx.read().0 >> (pin_group * 4)) & 0xf; + + // no more than one event can be awaited per pin at any given time, so + // we can just clear all interrupt enables for that pin without having + // to check which event was signalled. + if event != 0 { + proc_intx.inte(pin / 8).write_clear(|w| { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + w.set_level_high(pin_group, true); + w.set_level_low(pin_group, true); + }); + wakers[pin].wake(); + } + } +} + +#[cfg(feature = "rt")] +#[interrupt] +fn IO_IRQ_BANK0() { + irq_handler(pac::IO_BANK0, &BANK0_WAKERS); +} + +#[cfg(all(feature = "rt", feature = "qspi-as-gpio"))] +#[interrupt] +fn IO_IRQ_QSPI() { + irq_handler(pac::IO_QSPI, &QSPI_WAKERS); +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct InputFuture<'d> { + pin: Peri<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: Peri<'d, AnyPin>, level: InterruptTrigger) -> Self { + let pin_group = (pin.pin() % 8) as usize; + // first, clear the INTR register bits. without this INTR will still + // contain reports of previous edges, causing the IRQ to fire early + // on stale state. clearing these means that we can only detect edges + // that occur *after* the clear happened, but since both this and the + // alternative are fundamentally racy it's probably fine. + // (the alternative being checking the current level and waiting for + // its inverse, but that requires reading the current level and thus + // missing anything that happened before the level was read.) + pin.io().intr(pin.pin() as usize / 8).write(|w| { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + }); + + // Each INTR register is divided into 8 groups, one group for each + // pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW, + // and EDGE_HIGH. + pin.int_proc() + .inte((pin.pin() / 8) as usize) + .write_set(|w| match level { + InterruptTrigger::LevelHigh => { + w.set_level_high(pin_group, true); + } + InterruptTrigger::LevelLow => { + w.set_level_low(pin_group, true); + } + InterruptTrigger::EdgeHigh => { + w.set_edge_high(pin_group, true); + } + InterruptTrigger::EdgeLow => { + w.set_edge_low(pin_group, true); + } + InterruptTrigger::AnyEdge => { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + } + }); + + Self { pin } + } +} + +impl<'d> Future for InputFuture<'d> { + type Output = (); + + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + let waker = match self.pin.bank() { + Bank::Bank0 => &BANK0_WAKERS[self.pin.pin() as usize], + #[cfg(feature = "qspi-as-gpio")] + Bank::Qspi => &QSPI_WAKERS[self.pin.pin() as usize], + }; + waker.register(cx.waker()); + + // self.int_proc() will get the register offset for the current cpu, + // then we want to access the interrupt enable register for our + // pin (there are 4 of these PROC0_INTE0, PROC0_INTE1, PROC0_INTE2, and + // PROC0_INTE3 per cpu). + let inte: pac::io::regs::Int = self.pin.int_proc().inte((self.pin.pin() / 8) as usize).read(); + // The register is divided into groups of four, one group for + // each pin. Each group consists of four trigger levels LEVEL_LOW, + // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. + let pin_group = (self.pin.pin() % 8) as usize; + + // since the interrupt handler clears all INTE flags we'll check that + // all have been cleared and unconditionally return Ready(()) if so. + // we don't need further handshaking since only a single event wait + // is possible for any given pin at any given time. + if !inte.edge_high(pin_group) + && !inte.edge_low(pin_group) + && !inte.level_high(pin_group) + && !inte.level_low(pin_group) + { + return Poll::Ready(()); + } + Poll::Pending + } +} + +/// GPIO output driver. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Output<'d> { + pin: Flex<'d>, +} + +impl<'d> Output<'d> { + /// Create GPIO output driver for a [Pin] with the provided [Level]. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + match initial_output { + Level::High => pin.set_high(), + Level::Low => pin.set_low(), + } + + pin.set_as_output(); + Self { pin } + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.set_drive_strength(strength) + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.set_slew_rate(slew_rate) + } + + /// Configure the output logic inversion of this pin. + #[inline] + pub fn set_inversion(&mut self, invert: bool) { + self.pin.set_output_inversion(invert) + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.set_high() + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.set_low() + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + self.pin.set_level(level) + } + + /// Is the output pin set as high? + #[inline] + pub fn is_set_high(&self) -> bool { + self.pin.is_set_high() + } + + /// Is the output pin set as low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_low() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.pin.get_output_level() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle() + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } +} + +/// GPIO output open-drain. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OutputOpenDrain<'d> { + pin: Flex<'d>, +} + +impl<'d> OutputOpenDrain<'d> { + /// Create GPIO output driver for a [Pin] in open drain mode with the provided [Level]. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>, initial_output: Level) -> Self { + let mut pin = Flex::new(pin); + pin.set_low(); + match initial_output { + Level::High => pin.set_as_input(), + Level::Low => pin.set_as_output(), + } + Self { pin } + } + + /// Set the pin's pull-up. + #[inline] + pub fn set_pullup(&mut self, enable: bool) { + if enable { + self.pin.set_pull(Pull::Up); + } else { + self.pin.set_pull(Pull::None); + } + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.set_drive_strength(strength) + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.set_slew_rate(slew_rate) + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + // For Open Drain High, disable the output pin. + self.pin.set_as_input() + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + // For Open Drain Low, enable the output pin. + self.pin.set_as_output() + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Is the output level high? + #[inline] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Is the output level low? + #[inline] + pub fn is_set_low(&self) -> bool { + self.pin.is_set_as_output() + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.toggle_set_as_output() + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + /// Get whether the pin input level is low. + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await; + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.set_pad_isolation(isolate) + } +} + +/// GPIO flexible pin. +/// +/// This pin can be either an input or output pin. The output level register bit will remain +/// set while not in output mode, so the pin's level will be 'remembered' when it is not in output +/// mode. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Flex<'d> { + pin: Peri<'d, AnyPin>, +} + +impl<'d> Flex<'d> { + /// Wrap the pin in a `Flex`. + /// + /// The pin remains disconnected. The initial output level is unspecified, but can be changed + /// before the pin is put into output mode. + #[inline] + pub fn new(pin: Peri<'d, impl Pin>) -> Self { + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + + pin.gpio().ctrl().write(|w| { + #[cfg(feature = "rp2040")] + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _); + #[cfg(feature = "_rp235x")] + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIOB_PROC_0 as _); + }); + + Self { pin: pin.into() } + } + + #[inline] + fn bit(&self) -> u32 { + 1 << (self.pin.pin() % 32) + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + self.pin.pad_ctrl().modify(|w| { + w.set_ie(true); + let (pu, pd) = match pull { + Pull::Up => (true, false), + Pull::Down => (false, true), + Pull::None => (false, false), + }; + w.set_pue(pu); + w.set_pde(pd); + }); + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2M_A, + Drive::_4mA => pac::pads::vals::Drive::_4M_A, + Drive::_8mA => pac::pads::vals::Drive::_8M_A, + Drive::_12mA => pac::pads::vals::Drive::_12M_A, + }); + }); + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); + } + + /// Set the pin's Schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + + /// Put the pin into input mode. + /// + /// The pull setting is left unchanged. + #[inline] + pub fn set_as_input(&mut self) { + self.pin.sio_oe().value_clr().write_value(self.bit()) + } + + /// Put the pin into output mode. + /// + /// The pin level will be whatever was set before (or low by default). If you want it to begin + /// at a specific level, call `set_high`/`set_low` on the pin first. + #[inline] + pub fn set_as_output(&mut self) { + self.pin.sio_oe().value_set().write_value(self.bit()) + } + + /// Set as output pin. + #[inline] + fn is_set_as_output(&self) -> bool { + (self.pin.sio_oe().value().read() & self.bit()) != 0 + } + + /// Toggle output pin. + #[inline] + pub fn toggle_set_as_output(&mut self) { + self.pin.sio_oe().value_xor().write_value(self.bit()) + } + + /// Configure the input logic inversion of this pin. + #[inline] + pub fn set_input_inversion(&mut self, invert: bool) { + self.pin.gpio().ctrl().modify(|w| { + w.set_inover(if invert { + pac::io::vals::Inover::INVERT + } else { + pac::io::vals::Inover::NORMAL + }) + }); + } + + /// Configure the output logic inversion of this pin. + #[inline] + pub fn set_output_inversion(&mut self, invert: bool) { + self.pin.gpio().ctrl().modify(|w| { + w.set_outover(if invert { + pac::io::vals::Outover::INVERT + } else { + pac::io::vals::Outover::NORMAL + }) + }); + } + + /// Get whether the pin input level is high. + #[inline] + pub fn is_high(&self) -> bool { + !self.is_low() + } + /// Get whether the pin input level is low. + + #[inline] + pub fn is_low(&self) -> bool { + self.pin.sio_in().read() & self.bit() == 0 + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + /// Set the output as high. + #[inline] + pub fn set_high(&mut self) { + self.pin.sio_out().value_set().write_value(self.bit()) + } + + /// Set the output as low. + #[inline] + pub fn set_low(&mut self) { + self.pin.sio_out().value_clr().write_value(self.bit()) + } + + /// Set the output level. + #[inline] + pub fn set_level(&mut self, level: Level) { + match level { + Level::Low => self.set_low(), + Level::High => self.set_high(), + } + } + + /// Is the output level high? + #[inline] + pub fn is_set_high(&self) -> bool { + !self.is_set_low() + } + + /// Is the output level low? + #[inline] + pub fn is_set_low(&self) -> bool { + (self.pin.sio_out().value().read() & self.bit()) == 0 + } + + /// What level output is set to + #[inline] + pub fn get_output_level(&self) -> Level { + self.is_set_high().into() + } + + /// Toggle pin output + #[inline] + pub fn toggle(&mut self) { + self.pin.sio_out().value_xor().write_value(self.bit()) + } + + /// Wait until the pin is high. If it is already high, return immediately. + #[inline] + pub async fn wait_for_high(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::LevelHigh).await; + } + + /// Wait until the pin is low. If it is already low, return immediately. + #[inline] + pub async fn wait_for_low(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::LevelLow).await; + } + + /// Wait for the pin to undergo a transition from low to high. + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::EdgeHigh).await; + } + + /// Wait for the pin to undergo a transition from high to low. + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::EdgeLow).await; + } + + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. + #[inline] + pub async fn wait_for_any_edge(&mut self) { + InputFuture::new(self.pin.reborrow(), InterruptTrigger::AnyEdge).await; + } + + /// Configure dormant wake. + #[inline] + pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { + let idx = self.pin._pin() as usize; + self.pin.io().intr(idx / 8).write(|w| { + w.set_edge_high(idx % 8, cfg.edge_high); + w.set_edge_low(idx % 8, cfg.edge_low); + }); + self.pin.io().int_dormant_wake().inte(idx / 8).write_set(|w| { + w.set_edge_high(idx % 8, cfg.edge_high); + w.set_edge_low(idx % 8, cfg.edge_low); + w.set_level_high(idx % 8, cfg.level_high); + w.set_level_low(idx % 8, cfg.level_low); + }); + DormantWake { + pin: self.pin.reborrow(), + cfg, + } + } + + /// Set the pin's pad isolation + #[cfg(feature = "_rp235x")] + #[inline] + pub fn set_pad_isolation(&mut self, isolate: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_iso(isolate); + }); + } +} + +impl<'d> Drop for Flex<'d> { + #[inline] + fn drop(&mut self) { + let idx = self.pin._pin() as usize; + self.pin.pad_ctrl().write(|_| {}); + self.pin.gpio().ctrl().write(|w| { + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _); + w.set_inover(pac::io::vals::Inover::NORMAL); + w.set_outover(pac::io::vals::Outover::NORMAL); + }); + self.pin.io().int_dormant_wake().inte(idx / 8).write_clear(|w| { + w.set_edge_high(idx % 8, true); + w.set_edge_low(idx % 8, true); + w.set_level_high(idx % 8, true); + w.set_level_low(idx % 8, true); + }); + } +} + +/// Dormant wake driver. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DormantWake<'w> { + pin: Peri<'w, AnyPin>, + cfg: DormantWakeConfig, +} + +impl<'w> Drop for DormantWake<'w> { + fn drop(&mut self) { + let idx = self.pin._pin() as usize; + self.pin.io().intr(idx / 8).write(|w| { + w.set_edge_high(idx % 8, self.cfg.edge_high); + w.set_edge_low(idx % 8, self.cfg.edge_low); + }); + self.pin.io().int_dormant_wake().inte(idx / 8).write_clear(|w| { + w.set_edge_high(idx % 8, true); + w.set_edge_low(idx % 8, true); + w.set_level_high(idx % 8, true); + w.set_level_low(idx % 8, true); + }); + } +} + +pub(crate) trait SealedPin: Sized { + fn pin_bank(&self) -> u8; + + #[inline] + fn _pin(&self) -> u8 { + self.pin_bank() & 0x7f + } + + #[inline] + fn _bank(&self) -> Bank { + match self.pin_bank() >> 7 { + #[cfg(feature = "qspi-as-gpio")] + 1 => Bank::Qspi, + _ => Bank::Bank0, + } + } + + fn io(&self) -> pac::io::Io { + match self._bank() { + Bank::Bank0 => crate::pac::IO_BANK0, + #[cfg(feature = "qspi-as-gpio")] + Bank::Qspi => crate::pac::IO_QSPI, + } + } + + fn gpio(&self) -> pac::io::Gpio { + self.io().gpio(self._pin() as _) + } + + fn pad_ctrl(&self) -> Reg { + let block = match self._bank() { + Bank::Bank0 => crate::pac::PADS_BANK0, + #[cfg(feature = "qspi-as-gpio")] + Bank::Qspi => crate::pac::PADS_QSPI, + }; + block.gpio(self._pin() as _) + } + + fn sio_out(&self) -> pac::sio::Gpio { + if cfg!(feature = "rp2040") { + SIO.gpio_out(self._bank() as _) + } else { + SIO.gpio_out((self._pin() / 32) as _) + } + } + + fn sio_oe(&self) -> pac::sio::Gpio { + if cfg!(feature = "rp2040") { + SIO.gpio_oe(self._bank() as _) + } else { + SIO.gpio_oe((self._pin() / 32) as _) + } + } + + fn sio_in(&self) -> Reg { + if cfg!(feature = "rp2040") { + SIO.gpio_in(self._bank() as _) + } else { + SIO.gpio_in((self._pin() / 32) as _) + } + } + + fn int_proc(&self) -> pac::io::Int { + let proc = SIO.cpuid().read(); + self.io().int_proc(proc as _) + } +} + +/// Interface for a Pin that can be configured by an [Input] or [Output] driver, or converted to an [AnyPin]. +#[allow(private_bounds)] +pub trait Pin: PeripheralType + Into + SealedPin + Sized + 'static { + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self._pin() + } + + /// Returns the bank of this pin + #[inline] + fn bank(&self) -> Bank { + self._bank() + } +} + +/// Type-erased GPIO pin +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AnyPin { + pin_bank: u8, +} + +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that you’re only using one instance of this type at a time. + pub unsafe fn steal(pin_bank: u8) -> Peri<'static, Self> { + Peri::new_unchecked(Self { pin_bank }) + } +} + +impl_peripheral!(AnyPin); + +impl Pin for AnyPin {} +impl SealedPin for AnyPin { + fn pin_bank(&self) -> u8 { + self.pin_bank + } +} + +// ========================== + +macro_rules! impl_pin { + ($name:ident, $bank:expr, $pin_num:expr) => { + impl Pin for peripherals::$name {} + impl SealedPin for peripherals::$name { + #[inline] + fn pin_bank(&self) -> u8 { + ($bank as u8) * 128 + $pin_num + } + } + + impl From for crate::gpio::AnyPin { + fn from(val: peripherals::$name) -> Self { + Self { + pin_bank: val.pin_bank(), + } + } + } + }; +} + +impl_pin!(PIN_0, Bank::Bank0, 0); +impl_pin!(PIN_1, Bank::Bank0, 1); +impl_pin!(PIN_2, Bank::Bank0, 2); +impl_pin!(PIN_3, Bank::Bank0, 3); +impl_pin!(PIN_4, Bank::Bank0, 4); +impl_pin!(PIN_5, Bank::Bank0, 5); +impl_pin!(PIN_6, Bank::Bank0, 6); +impl_pin!(PIN_7, Bank::Bank0, 7); +impl_pin!(PIN_8, Bank::Bank0, 8); +impl_pin!(PIN_9, Bank::Bank0, 9); +impl_pin!(PIN_10, Bank::Bank0, 10); +impl_pin!(PIN_11, Bank::Bank0, 11); +impl_pin!(PIN_12, Bank::Bank0, 12); +impl_pin!(PIN_13, Bank::Bank0, 13); +impl_pin!(PIN_14, Bank::Bank0, 14); +impl_pin!(PIN_15, Bank::Bank0, 15); +impl_pin!(PIN_16, Bank::Bank0, 16); +impl_pin!(PIN_17, Bank::Bank0, 17); +impl_pin!(PIN_18, Bank::Bank0, 18); +impl_pin!(PIN_19, Bank::Bank0, 19); +impl_pin!(PIN_20, Bank::Bank0, 20); +impl_pin!(PIN_21, Bank::Bank0, 21); +impl_pin!(PIN_22, Bank::Bank0, 22); +impl_pin!(PIN_23, Bank::Bank0, 23); +impl_pin!(PIN_24, Bank::Bank0, 24); +impl_pin!(PIN_25, Bank::Bank0, 25); +impl_pin!(PIN_26, Bank::Bank0, 26); +impl_pin!(PIN_27, Bank::Bank0, 27); +impl_pin!(PIN_28, Bank::Bank0, 28); +impl_pin!(PIN_29, Bank::Bank0, 29); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, Bank::Bank0, 30); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, Bank::Bank0, 31); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, Bank::Bank0, 32); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, Bank::Bank0, 33); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, Bank::Bank0, 34); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, Bank::Bank0, 35); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, Bank::Bank0, 36); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, Bank::Bank0, 37); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, Bank::Bank0, 38); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, Bank::Bank0, 39); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, Bank::Bank0, 40); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, Bank::Bank0, 41); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, Bank::Bank0, 42); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, Bank::Bank0, 43); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, Bank::Bank0, 44); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, Bank::Bank0, 45); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, Bank::Bank0, 46); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, Bank::Bank0, 47); + +// TODO rp235x bank1 as gpio support +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SCLK, Bank::Qspi, 0); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SS, Bank::Qspi, 1); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD0, Bank::Qspi, 2); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD1, Bank::Qspi, 3); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD2, Bank::Qspi, 4); +#[cfg(feature = "qspi-as-gpio")] +impl_pin!(PIN_QSPI_SD3, Bank::Qspi, 5); + +// ==================== + +mod eh02 { + use super::*; + + impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + Ok(self.toggle()) + } + } + + impl<'d> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d> { + type Error = Infallible; + + #[inline] + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + #[inline] + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + Ok(self.toggle()) + } + } + + impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { + type Error = Infallible; + + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { + fn is_set_high(&self) -> Result { + Ok(self.is_set_high()) + } + + fn is_set_low(&self) -> Result { + Ok(self.is_set_low()) + } + } + + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { + type Error = Infallible; + #[inline] + fn toggle(&mut self) -> Result<(), Self::Error> { + Ok(self.toggle()) + } + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { + type Error = Infallible; +} + +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { + fn is_high(&mut self) -> Result { + Ok((*self).is_high()) + } + + fn is_low(&mut self) -> Result { + Ok((*self).is_low()) + } +} + +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { + fn set_high(&mut self) -> Result<(), Self::Error> { + Ok(self.set_high()) + } + + fn set_low(&mut self) -> Result<(), Self::Error> { + Ok(self.set_low()) + } +} + +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { + fn is_set_high(&mut self) -> Result { + Ok((*self).is_set_high()) + } + + fn is_set_low(&mut self) -> Result { + Ok((*self).is_set_low()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Flex<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for Input<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} + +impl<'d> embedded_hal_async::digital::Wait for OutputOpenDrain<'d> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } +} diff --git a/embassy-rp-fork/src/i2c.rs b/embassy-rp-fork/src/i2c.rs new file mode 100644 index 0000000..ffbef63 --- /dev/null +++ b/embassy-rp-fork/src/i2c.rs @@ -0,0 +1,924 @@ +#![cfg_attr(feature = "defmt", allow(deprecated))] // Suppress warnings for defmt::Format using Error::AddressReserved + +//! I2C driver. +use core::future; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::i2c; + +use crate::gpio::AnyPin; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, pac, peripherals}; + +/// I2C error abort reason +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AbortReason { + /// A bus operation was not acknowledged, e.g. due to the addressed device + /// not being available on the bus or the device not being ready to process + /// requests at the moment + NoAcknowledge, + /// The arbitration was lost, e.g. electrical problems with the clock signal + ArbitrationLoss, + /// Transmit ended with data still in fifo + TxNotEmpty(u16), + /// Other reason. + Other(u32), +} + +/// I2C error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// I2C abort with error + Abort(AbortReason), + /// User passed in a read buffer that was 0 length + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + InvalidWriteBufferLength, + /// Target i2c address is out of range + AddressOutOfRange(u16), + /// Target i2c address is reserved + #[deprecated = "embassy_rp no longer prevents accesses to reserved addresses."] + AddressReserved(u16), +} + +/// I2C Config error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Max i2c speed is 1MHz + FrequencyTooHigh, + /// The sys clock is too slow to support given frequency + ClockTooSlow, + /// The sys clock is too fast to support given frequency + ClockTooFast, +} + +/// I2C config. +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + /// Frequency. + pub frequency: u32, + /// Enable internal pullup on SDA. + /// + /// Using external pullup resistors is recommended for I2C. If you do + /// have external pullups you should not enable this. + pub sda_pullup: bool, + /// Enable internal pullup on SCL. + /// + /// Using external pullup resistors is recommended for I2C. If you do + /// have external pullups you should not enable this. + pub scl_pullup: bool, +} +impl Default for Config { + fn default() -> Self { + Self { + frequency: 100_000, + sda_pullup: true, + scl_pullup: true, + } + } +} +/// Size of I2C FIFO. +pub const FIFO_SIZE: u8 = 16; + +/// I2C driver. +#[derive(Debug)] +pub struct I2c<'d, T: Instance, M: Mode> { + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance> I2c<'d, T, Blocking> { + /// Create a new driver instance in blocking mode. + pub fn new_blocking( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + config: Config, + ) -> Self { + Self::new_inner(peri, scl.into(), sda.into(), config) + } +} + +impl<'d, T: Instance> I2c<'d, T, Async> { + /// Create a new driver instance in async mode. + pub fn new_async( + peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + _irq: impl Binding>, + config: Config, + ) -> Self { + let i2c = Self::new_inner(peri, scl.into(), sda.into(), config); + + let r = T::regs(); + + // mask everything initially + r.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + i2c + } + + /// Calls `f` to check if we are ready or not. + /// If not, `g` is called once(to eg enable the required interrupts). + /// The waker will always be registered prior to calling `f`. + async fn wait_on(&mut self, mut f: F, mut g: G) -> U + where + F: FnMut(&mut Self) -> Poll, + G: FnMut(&mut Self), + { + future::poll_fn(|cx| { + // Register prior to checking the condition + T::waker().register(cx.waker()); + let r = f(self); + + if r.is_pending() { + g(self); + } + r + }) + .await + } + + async fn read_async_internal(&mut self, buffer: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> { + if buffer.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + let p = T::regs(); + + let mut remaining = buffer.len(); + let mut remaining_queue = buffer.len(); + + let mut abort_reason = Ok(()); + + while remaining > 0 { + // Waggle SCK - basically the same as write + let tx_fifo_space = Self::tx_fifo_capacity(); + let mut batch = 0; + + debug_assert!(remaining_queue > 0); + + for _ in 0..remaining_queue.min(tx_fifo_space as usize) { + remaining_queue -= 1; + let last = remaining_queue == 0; + batch += 1; + + p.ic_data_cmd().write(|w| { + w.set_restart(restart && remaining_queue == buffer.len() - 1); + w.set_stop(last && send_stop); + w.set_cmd(true); + }); + } + + // We've either run out of txfifo or just plain finished setting up + // the clocks for the message - either way we need to wait for rx + // data. + + debug_assert!(batch > 0); + let res = self + .wait_on( + |me| { + let rxfifo = Self::rx_fifo_len(); + if let Err(abort_reason) = me.read_and_clear_abort_reason() { + Poll::Ready(Err(abort_reason)) + } else if rxfifo >= batch { + Poll::Ready(Ok(rxfifo)) + } else { + Poll::Pending + } + }, + |_me| { + // Set the read threshold to the number of bytes we're + // expecting so we don't get spurious interrupts. + p.ic_rx_tl().write(|w| w.set_rx_tl(batch - 1)); + + p.ic_intr_mask().modify(|w| { + w.set_m_rx_full(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + + match res { + Err(reason) => { + abort_reason = Err(reason); + break; + } + Ok(rxfifo) => { + // Fetch things from rx fifo. We're assuming we're the only + // rxfifo reader, so nothing else can take things from it. + let rxbytes = (rxfifo as usize).min(remaining); + let received = buffer.len() - remaining; + for b in &mut buffer[received..received + rxbytes] { + *b = p.ic_data_cmd().read().dat(); + } + remaining -= rxbytes; + } + }; + } + + self.wait_stop_det(abort_reason, send_stop).await + } + + async fn write_async_internal( + &mut self, + bytes: impl IntoIterator, + send_stop: bool, + ) -> Result<(), Error> { + let p = T::regs(); + + let mut bytes = bytes.into_iter().peekable(); + + let res = 'xmit: loop { + let tx_fifo_space = Self::tx_fifo_capacity(); + + for _ in 0..tx_fifo_space { + if let Some(byte) = bytes.next() { + let last = bytes.peek().is_none(); + + p.ic_data_cmd().write(|w| { + w.set_stop(last && send_stop); + w.set_cmd(false); + w.set_dat(byte); + }); + } else { + break 'xmit Ok(()); + } + } + + let res = self + .wait_on( + |me| { + if let abort_reason @ Err(_) = me.read_and_clear_abort_reason() { + Poll::Ready(abort_reason) + } else if !Self::tx_fifo_full() { + // resume if there's any space free in the tx fifo + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |_me| { + // Set tx "free" threshold a little high so that we get + // woken before the fifo completely drains to minimize + // transfer stalls. + p.ic_tx_tl().write(|w| w.set_tx_tl(1)); + + p.ic_intr_mask().modify(|w| { + w.set_m_tx_empty(true); + w.set_m_tx_abrt(true); + }) + }, + ) + .await; + if res.is_err() { + break res; + } + }; + + self.wait_stop_det(res, send_stop).await + } + + /// Helper to wait for a stop bit, for both tx and rx. If we had an abort, + /// then we'll get a hardware-generated stop, otherwise wait for a stop if + /// we're expecting it. + /// + /// Also handles an abort which arises while processing the tx fifo. + async fn wait_stop_det(&mut self, had_abort: Result<(), Error>, do_stop: bool) -> Result<(), Error> { + if had_abort.is_err() || do_stop { + let p = T::regs(); + + let had_abort2 = self + .wait_on( + |me| { + // We could see an abort while processing fifo backlog, + // so handle it here. + let abort = me.read_and_clear_abort_reason(); + if had_abort.is_ok() && abort.is_err() { + Poll::Ready(abort) + } else if p.ic_raw_intr_stat().read().stop_det() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().modify(|w| { + w.set_m_stop_det(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + p.ic_clr_stop_det().read(); + + had_abort.and(had_abort2) + } else { + had_abort + } + } + + /// Read from address into buffer asynchronously. + pub async fn read_async(&mut self, addr: impl Into, buffer: &mut [u8]) -> Result<(), Error> { + Self::setup(addr.into())?; + self.read_async_internal(buffer, true, true).await + } + + /// Write to address from buffer asynchronously. + pub async fn write_async( + &mut self, + addr: impl Into, + bytes: impl IntoIterator, + ) -> Result<(), Error> { + Self::setup(addr.into())?; + self.write_async_internal(bytes, true).await + } + + /// Write to address from bytes and read from address into buffer asynchronously. + pub async fn write_read_async( + &mut self, + addr: impl Into, + bytes: impl IntoIterator, + buffer: &mut [u8], + ) -> Result<(), Error> { + Self::setup(addr.into())?; + self.write_async_internal(bytes, false).await?; + self.read_async_internal(buffer, true, true).await + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + // Mask interrupts and wake any task waiting for this interrupt + unsafe fn on_interrupt() { + let i2c = T::regs(); + i2c.ic_intr_mask().write_value(pac::i2c::regs::IcIntrMask::default()); + + T::waker().wake(); + } +} + +pub(crate) fn set_up_i2c_pin(pin: &P, pullup: bool) +where + P: core::ops::Deref, + T: crate::gpio::Pin, +{ + pin.gpio().ctrl().write(|w| w.set_funcsel(3)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(pullup); + w.set_pde(false); + }); +} + +impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { + fn new_inner(_peri: Peri<'d, T>, scl: Peri<'d, AnyPin>, sda: Peri<'d, AnyPin>, config: Config) -> Self { + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + + // Configure SCL & SDA pins + set_up_i2c_pin(&scl, config.scl_pullup); + set_up_i2c_pin(&sda, config.sda_pullup); + + let mut me = Self { phantom: PhantomData }; + + if let Err(e) = me.set_config_inner(&config) { + panic!("Error configuring i2c: {:?}", e); + } + + me + } + + fn set_config_inner(&mut self, config: &Config) -> Result<(), ConfigError> { + if config.frequency > 1_000_000 { + return Err(ConfigError::FrequencyTooHigh); + } + + let p = T::regs(); + + p.ic_enable().write(|w| w.set_enable(false)); + + // Configure baudrate + + // There are some subtleties to I2C timing which we are completely + // ignoring here See: + // https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let clk_base = crate::clocks::clk_peri_freq(); + + let period = (clk_base + config.frequency / 2) / config.frequency; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + if hcnt > 0xffff || lcnt > 0xffff { + return Err(ConfigError::ClockTooFast); + } + if hcnt < 8 || lcnt < 8 { + return Err(ConfigError::ClockTooSlow); + } + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA + // signal to bridge the undefined region of the falling edge of SCL. + // A smaller hold time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if config.frequency < 1_000_000 { + // sda_tx_hold_count = clk_base [cycles/s] * 300ns * (1s / + // 1e9ns) Reduce 300/1e9 to 3/1e7 to avoid numbers that don't + // fit in uint. Add 1 to avoid division truncation. + ((clk_base * 3) / 10_000_000) + 1 + } else { + // fast mode plus requires a clk_base > 32MHz + if clk_base <= 32_000_000 { + return Err(ConfigError::ClockTooSlow); + } + + // sda_tx_hold_count = clk_base [cycles/s] * 120ns * (1s / + // 1e9ns) Reduce 120/1e9 to 3/25e6 to avoid numbers that don't + // fit in uint. Add 1 to avoid division truncation. + ((clk_base * 3) / 25_000_000) + 1 + }; + + if sda_tx_hold_count > lcnt - 2 { + return Err(ConfigError::ClockTooSlow); + } + + p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16)); + p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16)); + p.ic_fs_spklen() + .write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 })); + p.ic_sda_hold() + .modify(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16)); + + p.ic_enable().write(|w| w.set_enable(true)); + + Ok(()) + } + + fn setup(addr: u16) -> Result<(), Error> { + if addr >= 0x80 { + return Err(Error::AddressOutOfRange(addr)); + } + + let p = T::regs(); + p.ic_enable().write(|w| w.set_enable(false)); + p.ic_tar().write(|w| w.set_ic_tar(addr)); + p.ic_enable().write(|w| w.set_enable(true)); + Ok(()) + } + + #[inline] + fn tx_fifo_full() -> bool { + Self::tx_fifo_capacity() == 0 + } + + #[inline] + fn tx_fifo_capacity() -> u8 { + let p = T::regs(); + FIFO_SIZE - p.ic_txflr().read().txflr() + } + + #[inline] + fn rx_fifo_len() -> u8 { + let p = T::regs(); + p.ic_rxflr().read().rxflr() + } + + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let p = T::regs(); + let abort_reason = p.ic_tx_abrt_source().read(); + if abort_reason.0 != 0 { + // Note clearing the abort flag also clears the reason, and this + // instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + p.ic_clr_tx_abrt().read(); + + let reason = if abort_reason.abrt_7b_addr_noack() + | abort_reason.abrt_10addr1_noack() + | abort_reason.abrt_10addr2_noack() + { + AbortReason::NoAcknowledge + } else if abort_reason.arb_lost() { + AbortReason::ArbitrationLoss + } else { + AbortReason::Other(abort_reason.0) + }; + + Err(Error::Abort(reason)) + } else { + Ok(()) + } + } + + fn read_blocking_internal(&mut self, read: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> { + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + let p = T::regs(); + let lastindex = read.len() - 1; + for (i, byte) in read.iter_mut().enumerate() { + let first = i == 0; + let last = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while Self::tx_fifo_full() {} + + p.ic_data_cmd().write(|w| { + w.set_restart(restart && first); + w.set_stop(send_stop && last); + + w.set_cmd(true); + }); + + while Self::rx_fifo_len() == 0 { + self.read_and_clear_abort_reason()?; + } + + *byte = p.ic_data_cmd().read().dat(); + } + + Ok(()) + } + + fn write_blocking_internal(&mut self, write: &[u8], send_stop: bool) -> Result<(), Error> { + if write.is_empty() { + return Err(Error::InvalidWriteBufferLength); + } + + let p = T::regs(); + + for (i, byte) in write.iter().enumerate() { + let last = i == write.len() - 1; + + p.ic_data_cmd().write(|w| { + w.set_stop(send_stop && last); + w.set_dat(*byte); + }); + + // Wait until the transmission of the address/data from the + // internal shift register has completed. For this to function + // correctly, the TX_EMPTY_CTRL flag in IC_CON must be set. The + // TX_EMPTY_CTRL flag was set in i2c_init. + while !p.ic_raw_intr_stat().read().tx_empty() {} + + let abort_reason = self.read_and_clear_abort_reason(); + + if abort_reason.is_err() || (send_stop && last) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + + while !p.ic_raw_intr_stat().read().stop_det() {} + + p.ic_clr_stop_det().read().clr_stop_det(); + } + + // Note the hardware issues a STOP automatically on an abort + // condition. Note also the hardware clears RX FIFO as well as + // TX on abort, ecause we set hwparam + // IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. + abort_reason?; + } + Ok(()) + } + + // ========================= + // Blocking public API + // ========================= + + /// Read from address into buffer blocking caller until done. + pub fn blocking_read(&mut self, address: impl Into, read: &mut [u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.read_blocking_internal(read, true, true) + // Automatic Stop + } + + /// Write to address from buffer blocking caller until done. + pub fn blocking_write(&mut self, address: impl Into, write: &[u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.write_blocking_internal(write, true) + } + + /// Write to address from bytes and read from address into buffer blocking caller until done. + pub fn blocking_write_read(&mut self, address: impl Into, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.write_blocking_internal(write, false)?; + self.read_blocking_internal(read, true, true) + // Automatic Stop + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, bytes) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> { + type Error = Error; + + fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, bytes, buffer) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> { + type Error = Error; + + fn exec( + &mut self, + address: u8, + operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + Self::setup(address.into())?; + for i in 0..operations.len() { + let last = i == operations.len() - 1; + match &mut operations[i] { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.read_blocking_internal(buf, false, last)? + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?, + } + } + Ok(()) + } +} + +impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::Abort(AbortReason::ArbitrationLoss) => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, + Self::Abort(AbortReason::NoAcknowledge) => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) + } + Self::Abort(AbortReason::TxNotEmpty(_)) => embedded_hal_1::i2c::ErrorKind::Other, + Self::Abort(AbortReason::Other(_)) => embedded_hal_1::i2c::ErrorKind::Other, + Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other, + Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other, + Self::AddressOutOfRange(_) => embedded_hal_1::i2c::ErrorKind::Other, + #[allow(deprecated)] + Self::AddressReserved(_) => embedded_hal_1::i2c::ErrorKind::Other, + } + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) + } + + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + Self::setup(address.into())?; + for i in 0..operations.len() { + let last = i == operations.len() - 1; + match &mut operations[i] { + embedded_hal_1::i2c::Operation::Read(buf) => self.read_blocking_internal(buf, false, last)?, + embedded_hal_1::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?, + } + } + Ok(()) + } +} + +impl<'d, A, T> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> +where + A: embedded_hal_async::i2c::AddressMode + Into + 'static, + T: Instance + 'd, +{ + async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + self.read_async(address, read).await + } + + async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + self.write_async(address, write.iter().copied()).await + } + + async fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.write_read_async(address, write.iter().copied(), read).await + } + + async fn transaction( + &mut self, + address: A, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + use embedded_hal_1::i2c::Operation; + + let addr: u16 = address.into(); + + if !operations.is_empty() { + Self::setup(addr)?; + } + let mut iterator = operations.iter_mut(); + + while let Some(op) = iterator.next() { + let last = iterator.len() == 0; + + match op { + Operation::Read(buffer) => { + self.read_async_internal(buffer, false, last).await?; + } + Operation::Write(buffer) => { + self.write_async_internal(buffer.iter().cloned(), last).await?; + } + } + } + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config_inner(config) + } +} + +pub(crate) trait SealedInstance { + fn regs() -> crate::pac::i2c::I2c; + fn reset() -> crate::pac::resets::regs::Peripherals; + fn waker() -> &'static AtomicWaker; +} + +trait SealedMode {} + +/// Driver mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +/// I2C instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($type:ident, $irq:ident, $reset:ident) => { + impl SealedInstance for peripherals::$type { + #[inline] + fn regs() -> pac::i2c::I2c { + pac::$type + } + + #[inline] + fn reset() -> pac::resets::regs::Peripherals { + let mut ret = pac::resets::regs::Peripherals::default(); + ret.$reset(true); + ret + } + + #[inline] + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } + } + impl Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_instance!(I2C0, I2C0_IRQ, set_i2c0); +impl_instance!(I2C1, I2C1_IRQ, set_i2c1); + +/// SDA pin. +pub trait SdaPin: crate::gpio::Pin {} +/// SCL pin. +pub trait SclPin: crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, I2C0, SdaPin); +impl_pin!(PIN_1, I2C0, SclPin); +impl_pin!(PIN_2, I2C1, SdaPin); +impl_pin!(PIN_3, I2C1, SclPin); +impl_pin!(PIN_4, I2C0, SdaPin); +impl_pin!(PIN_5, I2C0, SclPin); +impl_pin!(PIN_6, I2C1, SdaPin); +impl_pin!(PIN_7, I2C1, SclPin); +impl_pin!(PIN_8, I2C0, SdaPin); +impl_pin!(PIN_9, I2C0, SclPin); +impl_pin!(PIN_10, I2C1, SdaPin); +impl_pin!(PIN_11, I2C1, SclPin); +impl_pin!(PIN_12, I2C0, SdaPin); +impl_pin!(PIN_13, I2C0, SclPin); +impl_pin!(PIN_14, I2C1, SdaPin); +impl_pin!(PIN_15, I2C1, SclPin); +impl_pin!(PIN_16, I2C0, SdaPin); +impl_pin!(PIN_17, I2C0, SclPin); +impl_pin!(PIN_18, I2C1, SdaPin); +impl_pin!(PIN_19, I2C1, SclPin); +impl_pin!(PIN_20, I2C0, SdaPin); +impl_pin!(PIN_21, I2C0, SclPin); +impl_pin!(PIN_22, I2C1, SdaPin); +impl_pin!(PIN_23, I2C1, SclPin); +impl_pin!(PIN_24, I2C0, SdaPin); +impl_pin!(PIN_25, I2C0, SclPin); +impl_pin!(PIN_26, I2C1, SdaPin); +impl_pin!(PIN_27, I2C1, SclPin); +impl_pin!(PIN_28, I2C0, SdaPin); +impl_pin!(PIN_29, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, I2C1, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, I2C0, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, I2C0, SclPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, I2C1, SdaPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, I2C1, SclPin); diff --git a/embassy-rp-fork/src/i2c_slave.rs b/embassy-rp-fork/src/i2c_slave.rs new file mode 100644 index 0000000..0853709 --- /dev/null +++ b/embassy-rp-fork/src/i2c_slave.rs @@ -0,0 +1,406 @@ +//! I2C slave driver. +use core::future; +use core::marker::PhantomData; +use core::task::Poll; + +use pac::i2c; + +use crate::i2c::{AbortReason, FIFO_SIZE, Instance, InterruptHandler, SclPin, SdaPin, set_up_i2c_pin}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{Peri, pac}; + +/// I2C error +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// I2C abort with error + Abort(AbortReason), + /// User passed in a response buffer that was 0 length + InvalidResponseBufferLength, + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::Write`. + PartialWrite(usize), + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::GeneralCall`. + PartialGeneralCall(usize), +} + +/// Received command +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// General Call + GeneralCall(usize), + /// Read + Read, + /// Write+read + WriteRead(usize), + /// Write + Write(usize), +} + +/// Possible responses to responding to a read +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReadStatus { + /// Transaction Complete, controller naked our last byte + Done, + /// Transaction Incomplete, controller trying to read more bytes than were provided + NeedMoreBytes, + /// Transaction Complete, but controller stopped reading bytes before we ran out + LeftoverBytes(u16), +} + +/// Slave Configuration +#[non_exhaustive] +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Config { + /// Target Address + pub addr: u16, + /// Control if the peripheral should ack to and report general calls. + pub general_call: bool, + /// Enable internal pullup on SDA. + /// + /// Using external pullup resistors is recommended for I2C. If you do + /// have external pullups you should not enable this. + pub sda_pullup: bool, + /// Enable internal pullup on SCL. + /// + /// Using external pullup resistors is recommended for I2C. If you do + /// have external pullups you should not enable this. + pub scl_pullup: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + addr: 0x55, + general_call: true, + sda_pullup: true, + scl_pullup: true, + } + } +} + +/// I2CSlave driver. +pub struct I2cSlave<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + pending_byte: Option, + config: Config, +} + +impl<'d, T: Instance> I2cSlave<'d, T> { + /// Create a new instance. + pub fn new( + _peri: Peri<'d, T>, + scl: Peri<'d, impl SclPin>, + sda: Peri<'d, impl SdaPin>, + _irq: impl Binding>, + config: Config, + ) -> Self { + assert!(config.addr != 0); + + // Configure SCL & SDA pins + set_up_i2c_pin(&scl, config.scl_pullup); + set_up_i2c_pin(&sda, config.sda_pullup); + + let mut ret = Self { + phantom: PhantomData, + pending_byte: None, + config, + }; + + ret.reset(); + + ret + } + + /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus. + /// You can recover the bus by calling this function, but doing so will almost certainly cause + /// an i/o error in the master. + pub fn reset(&mut self) { + let p = T::regs(); + + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + + p.ic_enable().write(|w| w.set_enable(false)); + + p.ic_sar().write(|w| w.set_ic_sar(self.config.addr)); + p.ic_con().modify(|w| { + w.set_master_mode(false); + w.set_ic_slave_disable(false); + w.set_tx_empty_ctrl(true); + w.set_rx_fifo_full_hld_ctrl(true); + + // This typically makes no sense for a slave, but it is used to + // tune spike suppression, according to the datasheet. + w.set_speed(pac::i2c::vals::Speed::FAST); + + // Generate stop interrupts for general calls + // This also causes stop interrupts for other devices on the bus but those will not be + // propagated up to the application. + w.set_stop_det_ifaddressed(!self.config.general_call); + }); + p.ic_ack_general_call() + .write(|w| w.set_ack_gen_call(self.config.general_call)); + + // Set FIFO watermarks to 1 to make things simpler. This is encoded + // by a register value of 0. Rx watermark should never change, but Tx watermark will be + // adjusted in operation. + p.ic_tx_tl().write(|w| w.set_tx_tl(0)); + p.ic_rx_tl().write(|w| w.set_rx_tl(0)); + + // Clear interrupts + p.ic_clr_intr().read(); + + // Enable I2C block + p.ic_enable().write(|w| w.set_enable(true)); + + // mask everything initially + p.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + } + + /// Calls `f` to check if we are ready or not. + /// If not, `g` is called once(to eg enable the required interrupts). + /// The waker will always be registered prior to calling `f`. + #[inline(always)] + async fn wait_on(&mut self, mut f: F, mut g: G) -> U + where + F: FnMut(&mut Self) -> Poll, + G: FnMut(&mut Self), + { + future::poll_fn(|cx| { + // Register prior to checking the condition + T::waker().register(cx.waker()); + let r = f(self); + + if r.is_pending() { + g(self); + } + + r + }) + .await + } + + #[inline(always)] + fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) { + let p = T::regs(); + + if let Some(pending) = self.pending_byte.take() { + buffer[*offset] = pending; + *offset += 1; + } + + for b in &mut buffer[*offset..] { + if !p.ic_status().read().rfne() { + break; + } + + let dat = p.ic_data_cmd().read(); + if *offset != 0 && dat.first_data_byte() { + // The RP2040 state machine will keep placing bytes into the + // FIFO, even if they are part of a subsequent write transaction. + // + // Unfortunately merely reading ic_data_cmd will consume that + // byte, the first byte of the next transaction, so we need + // to store it elsewhere + self.pending_byte = Some(dat.dat()); + break; + } + + *b = dat.dat(); + *offset += 1; + } + } + + /// Wait asynchronously for commands from an I2C master. + /// `buffer` is provided in case master does a 'write', 'write read', or 'general call' and is unused for 'read'. + pub async fn listen(&mut self, buffer: &mut [u8]) -> Result { + let p = T::regs(); + + // set rx fifo watermark to 1 byte + p.ic_rx_tl().write(|w| w.set_rx_tl(0)); + + let mut len = 0; + self.wait_on( + |me| { + let stat = p.ic_raw_intr_stat().read(); + trace!("ls:{:013b} len:{}", stat.0, len); + + if p.ic_rxflr().read().rxflr() > 0 || me.pending_byte.is_some() { + me.drain_fifo(buffer, &mut len); + // we're receiving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise + p.ic_rx_tl().write(|w| w.set_rx_tl(11)); + } + + if buffer.len() == len { + if stat.gen_call() { + return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len()))); + } else { + return Poll::Ready(Err(Error::PartialWrite(buffer.len()))); + } + } + trace!("len:{}, pend:{:?}", len, me.pending_byte); + if me.pending_byte.is_some() { + warn!("pending") + } + + if stat.restart_det() && stat.rd_req() { + p.ic_clr_restart_det().read(); + Poll::Ready(Ok(Command::WriteRead(len))) + } else if stat.gen_call() && stat.stop_det() && len > 0 { + p.ic_clr_gen_call().read(); + p.ic_clr_stop_det().read(); + Poll::Ready(Ok(Command::GeneralCall(len))) + } else if stat.stop_det() && len > 0 { + p.ic_clr_stop_det().read(); + Poll::Ready(Ok(Command::Write(len))) + } else if stat.rd_req() { + p.ic_clr_stop_det().read(); + p.ic_clr_restart_det().read(); + p.ic_clr_gen_call().read(); + Poll::Ready(Ok(Command::Read)) + } else if stat.stop_det() { + // clear stuck stop bit + // This can happen if the SDA/SCL pullups are enabled after calling this func + p.ic_clr_stop_det().read(); + Poll::Pending + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().write(|w| { + w.set_m_stop_det(true); + w.set_m_restart_det(true); + w.set_m_gen_call(true); + w.set_m_rd_req(true); + w.set_m_rx_full(true); + }); + }, + ) + .await + } + + /// Respond to an I2C master READ command, asynchronously. + pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result { + let p = T::regs(); + + if buffer.is_empty() { + return Err(Error::InvalidResponseBufferLength); + } + + let mut chunks = buffer.chunks(FIFO_SIZE as usize); + + self.wait_on( + |me| { + let stat = p.ic_raw_intr_stat().read(); + trace!("rs:{:013b}", stat.0); + + if stat.tx_abrt() { + if let Err(abort_reason) = me.read_and_clear_abort_reason() { + if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason { + p.ic_clr_intr().read(); + return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes))); + } else { + return Poll::Ready(Err(abort_reason)); + } + } + } + + if let Some(chunk) = chunks.next() { + for byte in chunk { + p.ic_clr_rd_req().read(); + p.ic_data_cmd().write(|w| w.set_dat(*byte)); + } + + Poll::Pending + } else if stat.rx_done() { + p.ic_clr_rx_done().read(); + Poll::Ready(Ok(ReadStatus::Done)) + } else if stat.rd_req() && stat.tx_empty() { + Poll::Ready(Ok(ReadStatus::NeedMoreBytes)) + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().write(|w| { + w.set_m_rx_done(true); + w.set_m_tx_empty(true); + w.set_m_tx_abrt(true); + }) + }, + ) + .await + } + + /// Respond to reads with the fill byte until the controller stops asking + pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> { + // Send fill bytes a full fifo at a time, to reduce interrupt noise. + // This does mean we'll almost certainly abort the write, but since these are fill bytes, + // we don't care. + let buff = [fill; FIFO_SIZE as usize]; + loop { + match self.respond_to_read(&buff).await { + Ok(ReadStatus::NeedMoreBytes) => (), + Ok(ReadStatus::LeftoverBytes(_)) => break Ok(()), + Ok(_) => break Ok(()), + Err(e) => break Err(e), + } + } + } + + /// Respond to a master read, then fill any remaining read bytes with `fill` + pub async fn respond_and_fill(&mut self, buffer: &[u8], fill: u8) -> Result { + let resp_stat = self.respond_to_read(buffer).await?; + + if resp_stat == ReadStatus::NeedMoreBytes { + self.respond_till_stop(fill).await?; + Ok(ReadStatus::Done) + } else { + Ok(resp_stat) + } + } + + #[inline(always)] + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let p = T::regs(); + let abort_reason = p.ic_tx_abrt_source().read(); + + if abort_reason.0 != 0 { + // Note clearing the abort flag also clears the reason, and this + // instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + p.ic_clr_tx_abrt().read(); + + let reason = if abort_reason.abrt_7b_addr_noack() + | abort_reason.abrt_10addr1_noack() + | abort_reason.abrt_10addr2_noack() + { + AbortReason::NoAcknowledge + } else if abort_reason.arb_lost() { + AbortReason::ArbitrationLoss + } else if abort_reason.tx_flush_cnt() > 0 { + AbortReason::TxNotEmpty(abort_reason.tx_flush_cnt()) + } else { + AbortReason::Other(abort_reason.0) + }; + + Err(Error::Abort(reason)) + } else { + Ok(()) + } + } +} diff --git a/embassy-rp-fork/src/intrinsics.rs b/embassy-rp-fork/src/intrinsics.rs new file mode 100644 index 0000000..aed8a32 --- /dev/null +++ b/embassy-rp-fork/src/intrinsics.rs @@ -0,0 +1,476 @@ +#![macro_use] + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/intrinsics.rs + +/// Generate a series of aliases for an intrinsic function. +macro_rules! intrinsics_aliases { + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + intrinsics! { + extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + $name($($argname),*) + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; + + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + intrinsics! { + unsafe extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + $name($($argname),*) + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; +} + +/// The macro used to define overridden intrinsics. +/// +/// This is heavily inspired by the macro used by compiler-builtins. The idea +/// is to abstract anything special that needs to be done to override an +/// intrinsic function. Intrinsic generation is disabled for non-ARM targets +/// so things like CI and docs generation do not have problems. Additionally +/// they can be disabled by disabling the crate feature `intrinsics` for +/// testing or comparing performance. +/// +/// Like the compiler-builtins macro, it accepts a series of functions that +/// looks like normal Rust code: +/// +/// ```rust,ignore +/// intrinsics! { +/// extern "C" fn foo(a: i32) -> u32 { +/// // ... +/// } +/// #[nonstandard_attribute] +/// extern "C" fn bar(a: i32) -> u32 { +/// // ... +/// } +/// } +/// ``` +/// +/// Each function can also be decorated with nonstandard attributes to control +/// additional behaviour: +/// +/// * `slower_than_default` - indicates that the override is slower than the +/// default implementation. Currently this just disables the override +/// entirely. +/// * `bootrom_v2` - indicates that the override is only available +/// on a V2 bootrom or higher. Only enabled when the feature +/// `rom-v2-intrinsics` is set. +/// * `alias` - accepts a list of names to alias the intrinsic to. +/// * `aeabi` - accepts a list of ARM EABI names to alias to. +/// +macro_rules! intrinsics { + () => {}; + + ( + #[slower_than_default] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + #[bootrom_v2] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(feature = "rom-v2-intrinsics"))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(feature = "rom-v2-intrinsics")] + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[aeabi = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern "aapcs" fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + mod $name { + #[unsafe(no_mangle)] + $(#[$($attr)*])* + pub extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + mod $name { + #[unsafe(no_mangle)] + $(#[$($attr)*])* + pub unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))] + #[allow(dead_code)] + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; +} + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/sio.rs + +// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers +// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put +// the remainder in the high order 32 bits of a 64 bit result. We can also +// alias the division operators to these for a similar reason r0 is the +// result either way and r1 a scratch register, so the caller can't assume it +// retains the argument value. +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + ".macro hwdivider_head", + "ldr r2, =(0xd0000000)", // SIO_BASE + // Check the DIRTY state of the divider by shifting it into the C + // status bit. + "ldr r3, [r2, #0x078]", // DIV_CSR + "lsrs r3, #2", // DIRTY = 1, so shift 2 down + // We only need to save the state when DIRTY, otherwise we can just do the + // division directly. + "bcs 2f", + "1:", + // Do the actual division now, we're either not DIRTY, or we've saved the + // state and branched back here so it's safe now. + ".endm", + ".macro hwdivider_tail", + // 8 cycle delay to wait for the result. Each branch takes two cycles + // and fits into a 2-byte Thumb instruction, so this is smaller than + // 8 NOPs. + "b 3f", + "3: b 3f", + "3: b 3f", + "3: b 3f", + "3:", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r1, [r2, #0x074]", // DIV_REMAINDER + "ldr r0, [r2, #0x070]", // DIV_QUOTIENT + // Either return to the caller or back to the state restore. + "bx lr", + "2:", + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The push takes 5 cycles, and we've already spent at least 7 checking + // the DIRTY state to get here. + "push {{r4-r6, lr}}", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r3, [r2, #0x060]", // DIV_UDIVIDEND + "ldr r4, [r2, #0x064]", // DIV_UDIVISOR + "ldr r5, [r2, #0x074]", // DIV_REMAINDER + "ldr r6, [r2, #0x070]", // DIV_QUOTIENT + // If we get interrupted here (before a write sets the DIRTY flag) it's + // fine, since we have the full state, so the interruptor doesn't have to + // restore it. Once the write happens and the DIRTY flag is set, the + // interruptor becomes responsible for restoring our state. + "bl 1b", + // If we are interrupted here, then the interruptor will start an incorrect + // calculation using a wrong divisor, but we'll restore the divisor and + // result ourselves correctly. This sets DIRTY, so any interruptor will + // save the state. + "str r3, [r2, #0x060]", // DIV_UDIVIDEND + // If we are interrupted here, the interruptor may start the calculation + // using incorrectly signed inputs, but we'll restore the result ourselves. + // This sets DIRTY, so any interruptor will save the state. + "str r4, [r2, #0x064]", // DIV_UDIVISOR + // If we are interrupted here, the interruptor will have restored + // everything but the quotient may be wrongly signed. If the calculation + // started by the above writes is still ongoing it is stopped, so it won't + // replace the result we're restoring. DIRTY and READY set, but only + // DIRTY matters to make the interruptor save the state. + "str r5, [r2, #0x074]", // DIV_REMAINDER + // State fully restored after the quotient write. This sets both DIRTY + // and READY, so whatever we may have interrupted can read the result. + "str r6, [r2, #0x070]", // DIV_QUOTIENT + "pop {{r4-r6, pc}}", + ".endm", +); + +macro_rules! division_function { + ( + $name:ident $($intrinsic:ident)* ( $argty:ty ) { + $($begin:literal),+ + } + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".section .text._erphal_", stringify!($name)), + concat!(".global _erphal_", stringify!($name)), + concat!(".type _erphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_erphal_", stringify!($name), ":"), + $( + concat!(".global ", stringify!($intrinsic)), + concat!(".type ", stringify!($intrinsic), ", %function"), + concat!(stringify!($intrinsic), ":"), + )* + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(all(target_arch = "arm", not(feature = "intrinsics")))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".section .text._erphal_", stringify!($name)), + concat!(".global _erphal_", stringify!($name)), + concat!(".type _erphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_erphal_", stringify!($name), ":"), + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(target_arch = "arm")] + unsafe extern "aapcs" { + // Connect a local name to global symbol above through FFI. + #[link_name = concat!("_erphal_", stringify!($name)) ] + fn $name(n: $argty, d: $argty) -> u64; + } + + #[cfg(not(target_arch = "arm"))] + #[allow(unused_variables)] + unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 } + }; +} + +division_function! { + unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) { + "str r0, [r2, #0x060]", // DIV_UDIVIDEND + "str r1, [r2, #0x064]" // DIV_UDIVISOR + } +} + +division_function! { + signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) { + "str r0, [r2, #0x068]", // DIV_SDIVIDEND + "str r1, [r2, #0x06c]" // DIV_SDIVISOR + } +} + +fn divider_unsigned(n: u32, d: u32) -> DivResult { + let packed = unsafe { unsigned_divmod(n, d) }; + DivResult { + quotient: packed as u32, + remainder: (packed >> 32) as u32, + } +} + +fn divider_signed(n: i32, d: i32) -> DivResult { + let packed = unsafe { signed_divmod(n, d) }; + // Double casts to avoid sign extension + DivResult { + quotient: packed as u32 as i32, + remainder: (packed >> 32) as u32 as i32, + } +} + +/// Result of divide/modulo operation +struct DivResult { + /// The quotient of divide/modulo operation + pub quotient: T, + /// The remainder of divide/modulo operation + pub remainder: T, +} + +intrinsics! { + extern "C" fn __udivsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).quotient + } + + extern "C" fn __umodsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).remainder + } + + extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 { + let quo_rem = divider_unsigned(n, d); + if let Some(rem) = rem { + *rem = quo_rem.remainder; + } + quo_rem.quotient + } + + extern "C" fn __divsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).quotient + } + + extern "C" fn __modsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).remainder + } + + extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 { + let quo_rem = divider_signed(n, d); + *rem = quo_rem.remainder; + quo_rem.quotient + } +} diff --git a/embassy-rp-fork/src/lib.rs b/embassy-rp-fork/src/lib.rs new file mode 100644 index 0000000..7944e61 --- /dev/null +++ b/embassy-rp-fork/src/lib.rs @@ -0,0 +1,845 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![allow(unsafe_op_in_unsafe_fn)] +#![allow(unused_unsafe)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "binary-info")] +pub use rp_binary_info as binary_info; + +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; + +#[cfg(feature = "rp2040")] +mod intrinsics; + +pub mod adc; +#[cfg(feature = "_rp235x")] +pub mod block; +#[cfg(feature = "rp2040")] +pub mod bootsel; +pub mod clocks; +pub mod dma; +pub mod flash; +#[cfg(feature = "rp2040")] +mod float; +pub mod gpio; +pub mod i2c; +pub mod i2c_slave; +pub mod multicore; +#[cfg(feature = "_rp235x")] +pub mod otp; +pub mod pio_programs; +#[cfg(feature = "_rp235x")] +pub mod psram; +pub mod pwm; +#[cfg(feature = "_rp235x")] +pub mod qmi_cs1; +mod reset; +pub mod rom_data; +#[cfg(feature = "rp2040")] +pub mod rtc; +pub mod spi; +mod spinlock; +pub mod spinlock_mutex; +#[cfg(feature = "time-driver")] +pub mod time_driver; +#[cfg(feature = "_rp235x")] +pub mod trng; +pub mod uart; +pub mod usb; +pub mod watchdog; + +// PIO +pub mod pio; +pub(crate) mod relocate; + +// Reexports +pub use embassy_hal_internal::{Peri, PeripheralType}; +#[cfg(feature = "unstable-pac")] +pub use rp_pac as pac; +#[cfg(not(feature = "unstable-pac"))] +pub(crate) use rp_pac as pac; + +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +#[cfg(feature = "rp2040")] +embassy_hal_internal::interrupt_mod!( + TIMER_IRQ_0, + TIMER_IRQ_1, + TIMER_IRQ_2, + TIMER_IRQ_3, + PWM_IRQ_WRAP, + USBCTRL_IRQ, + XIP_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + DMA_IRQ_0, + DMA_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_QSPI, + SIO_IRQ_PROC0, + SIO_IRQ_PROC1, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + RTC_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +#[cfg(feature = "_rp235x")] +embassy_hal_internal::interrupt_mod!( + TIMER0_IRQ_0, + TIMER0_IRQ_1, + TIMER0_IRQ_2, + TIMER0_IRQ_3, + TIMER1_IRQ_0, + TIMER1_IRQ_1, + TIMER1_IRQ_2, + TIMER1_IRQ_3, + PWM_IRQ_WRAP_0, + PWM_IRQ_WRAP_1, + DMA_IRQ_0, + DMA_IRQ_1, + USBCTRL_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + PIO2_IRQ_0, + PIO2_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_BANK0_NS, + IO_IRQ_QSPI, + IO_IRQ_QSPI_NS, + SIO_IRQ_FIFO, + SIO_IRQ_BELL, + SIO_IRQ_FIFO_NS, + SIO_IRQ_BELL_NS, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + TRNG_IRQ, + PLL_SYS_IRQ, + PLL_USB_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +/// +/// Example of how to bind one interrupt: +/// +/// ```rust,ignore +/// use embassy_rp::{bind_interrupts, usb, peripherals}; +/// +/// bind_interrupts!( +/// /// Binds the USB Interrupts. +/// struct Irqs { +/// USBCTRL_IRQ => usb::InterruptHandler; +/// } +/// ); +/// ``` +/// +// developer note: this macro can't be in `embassy-hal-internal` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($(#[$attr:meta])* $vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $(#[$attr])* + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[unsafe(no_mangle)] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + unsafe { + $( + $(#[cfg($cond_handler)])? + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + + )* + } + } + + $(#[cfg($cond_irq)])? + $crate::bind_interrupts!(@inner + $( + $(#[cfg($cond_handler)])? + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + ); + )* + }; + (@inner $($t:tt)*) => { + $($t)* + } +} + +#[cfg(feature = "rp2040")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + + WATCHDOG, + BOOTSEL, +} + +#[cfg(feature = "_rp235x")] +embassy_hal_internal::peripherals! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, + #[cfg(feature = "rp235xb")] + PIN_30, + #[cfg(feature = "rp235xb")] + PIN_31, + #[cfg(feature = "rp235xb")] + PIN_32, + #[cfg(feature = "rp235xb")] + PIN_33, + #[cfg(feature = "rp235xb")] + PIN_34, + #[cfg(feature = "rp235xb")] + PIN_35, + #[cfg(feature = "rp235xb")] + PIN_36, + #[cfg(feature = "rp235xb")] + PIN_37, + #[cfg(feature = "rp235xb")] + PIN_38, + #[cfg(feature = "rp235xb")] + PIN_39, + #[cfg(feature = "rp235xb")] + PIN_40, + #[cfg(feature = "rp235xb")] + PIN_41, + #[cfg(feature = "rp235xb")] + PIN_42, + #[cfg(feature = "rp235xb")] + PIN_43, + #[cfg(feature = "rp235xb")] + PIN_44, + #[cfg(feature = "rp235xb")] + PIN_45, + #[cfg(feature = "rp235xb")] + PIN_46, + #[cfg(feature = "rp235xb")] + PIN_47, + PIN_QSPI_SCLK, + PIN_QSPI_SS, + PIN_QSPI_SD0, + PIN_QSPI_SD1, + PIN_QSPI_SD2, + PIN_QSPI_SD3, + + UART0, + UART1, + + SPI0, + SPI1, + + QMI_CS1, + + I2C0, + I2C1, + + DMA_CH0, + DMA_CH1, + DMA_CH2, + DMA_CH3, + DMA_CH4, + DMA_CH5, + DMA_CH6, + DMA_CH7, + DMA_CH8, + DMA_CH9, + DMA_CH10, + DMA_CH11, + DMA_CH12, + DMA_CH13, + DMA_CH14, + DMA_CH15, + + PWM_SLICE0, + PWM_SLICE1, + PWM_SLICE2, + PWM_SLICE3, + PWM_SLICE4, + PWM_SLICE5, + PWM_SLICE6, + PWM_SLICE7, + PWM_SLICE8, + PWM_SLICE9, + PWM_SLICE10, + PWM_SLICE11, + + USB, + + RTC, + + FLASH, + + ADC, + ADC_TEMP_SENSOR, + + CORE1, + + PIO0, + PIO1, + PIO2, + + WATCHDOG, + BOOTSEL, + + TRNG +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] +macro_rules! select_bootloader { + ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { + $( + #[cfg(feature = $feature)] + #[unsafe(link_section = ".boot2")] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$loader; + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[unsafe(link_section = ".boot2")] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$default; + } +} + +#[cfg(all(not(feature = "boot2-none"), feature = "rp2040"))] +select_bootloader! { + "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, + "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, + "boot2-generic-03h" => BOOT_LOADER_GENERIC_03H, + "boot2-is25lp080" => BOOT_LOADER_IS25LP080, + "boot2-ram-memcpy" => BOOT_LOADER_RAM_MEMCPY, + "boot2-w25q080" => BOOT_LOADER_W25Q080, + "boot2-w25x10cl" => BOOT_LOADER_W25X10CL, + default => BOOT_LOADER_W25Q080 +} + +#[cfg(all(not(feature = "imagedef-none"), feature = "_rp235x"))] +macro_rules! select_imagedef { + ( $( $feature:literal => $imagedef:ident, )+ default => $default:ident ) => { + $( + #[cfg(feature = $feature)] + #[unsafe(link_section = ".start_block")] + #[used] + static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$imagedef(); + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[unsafe(link_section = ".start_block")] + #[used] + static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$default(); + } +} + +#[cfg(all(not(feature = "imagedef-none"), feature = "_rp235x"))] +select_imagedef! { + "imagedef-secure-exe" => secure_exe, + "imagedef-nonsecure-exe" => non_secure_exe, + default => secure_exe +} + +/// Installs a stack guard for the CORE0 stack in MPU region 0. +/// Will fail if the MPU is already configured. This function requires +/// a `_stack_end` symbol to be defined by the linker script, and expects +/// `_stack_end` to be located at the lowest address (largest depth) of +/// the stack. +/// +/// This method can *only* set up stack guards on the currently +/// executing core. Stack guards for CORE1 are set up automatically, +/// only CORE0 should ever use this. +/// +/// # Usage +/// +/// ```no_run +/// use embassy_rp::install_core0_stack_guard; +/// use embassy_executor::{Executor, Spawner}; +/// +/// #[embassy_executor::main] +/// async fn main(_spawner: Spawner) { +/// // set up by the linker as follows: +/// // +/// // MEMORY { +/// // STACK0: ORIGIN = 0x20040000, LENGTH = 4K +/// // } +/// // +/// // _stack_end = ORIGIN(STACK0); +/// // _stack_start = _stack_end + LENGTH(STACK0); +/// // +/// install_core0_stack_guard().expect("MPU already configured"); +/// let p = embassy_rp::init(Default::default()); +/// +/// // ... +/// } +/// ``` +pub fn install_core0_stack_guard() -> Result<(), ()> { + unsafe extern "C" { + static mut _stack_end: usize; + } + unsafe { install_stack_guard(core::ptr::addr_of_mut!(_stack_end)) } +} + +#[cfg(all(feature = "rp2040", not(feature = "_test")))] +#[inline(always)] +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + // The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will + // just shorten the valid stack range a tad. + let addr = (stack_bottom as u32 + 31) & !31; + // Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want + let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7)); + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write((addr & !0xff) | (1 << 4)); // set address and update RNR + core.MPU.rasr.write( + 1 // enable region + | (0x7 << 1) // size 2^(7 + 1) = 256 + | (subregion_select << 8) + | 0x10000000, // XN = disable instruction fetch; no other bits means no permissions + ); + } + Ok(()) +} + +#[cfg(all(feature = "_rp235x", not(feature = "_test")))] +#[inline(always)] +unsafe fn install_stack_guard(stack_bottom: *mut usize) -> Result<(), ()> { + // The RP2350 arm cores are cortex-m33 and can use the MSPLIM register to guard the end of stack. + // We'll need to do something else for the riscv cores. + cortex_m::register::msplim::write(stack_bottom.addr() as u32); + + Ok(()) +} + +// This is to hack around cortex_m defaulting to ARMv7 when building tests, +// so the compile fails when we try to use ARMv8 peripherals. +#[cfg(feature = "_test")] +#[inline(always)] +unsafe fn install_stack_guard(_stack_bottom: *mut usize) -> Result<(), ()> { + Ok(()) +} + +/// HAL configuration for RP. +pub mod config { + use crate::clocks::ClockConfig; + + /// HAL configuration passed when initializing. + #[non_exhaustive] + pub struct Config { + /// Clock configuration. + pub clocks: ClockConfig, + } + + impl Default for Config { + fn default() -> Self { + Self { + clocks: ClockConfig::crystal(12_000_000), + } + } + } + + impl Config { + /// Create a new configuration with the provided clock config. + pub fn new(clocks: ClockConfig) -> Self { + Self { clocks } + } + } +} + +/// Initialize the `embassy-rp` HAL with the provided configuration. +/// +/// This returns the peripheral singletons that can be used for creating drivers. +/// +/// This should only be called once at startup, otherwise it panics. +pub fn init(config: config::Config) -> Peripherals { + // Do this first, so that it panics if user is calling `init` a second time + // before doing anything important. + let peripherals = Peripherals::take(); + + unsafe { + clocks::init(config.clocks); + #[cfg(feature = "time-driver")] + time_driver::init(); + dma::init(); + gpio::init(); + } + + peripherals +} + +/// Clock frequencies for Non-Secure TrustZone initialization. +/// +/// The Secure world configures clocks before handing off to NS. +/// These values must match what the Secure world configured. +pub struct NsClockConfig { + /// System clock frequency in Hz (typically 150_000_000) + pub sys_hz: u32, + /// Reference clock frequency in Hz (typically 12_000_000) + pub ref_hz: u32, + /// Peripheral clock frequency in Hz (typically = sys_hz) + pub peri_hz: u32, + /// USB clock frequency in Hz (must be 48_000_000) + pub usb_hz: u32, + /// ADC clock frequency in Hz (typically 48_000_000) + pub adc_hz: u32, + /// PLL_SYS frequency in Hz + pub pll_sys_hz: u32, + /// PLL_USB frequency in Hz + pub pll_usb_hz: u32, + /// XOSC frequency in Hz (typically 12_000_000) + pub xosc_hz: u32, +} + +impl NsClockConfig { + /// Default RP2350 clock configuration (12 MHz crystal). + /// Matches `ClockConfig::crystal(12_000_000)` and rp235x-hal's + /// `init_clocks_and_plls(12_000_000, ...)` with default PLL settings. + pub const fn rp2350_default() -> Self { + Self { + sys_hz: 150_000_000, + ref_hz: 12_000_000, + peri_hz: 150_000_000, + usb_hz: 48_000_000, + adc_hz: 48_000_000, + pll_sys_hz: 150_000_000, + pll_usb_hz: 48_000_000, + xosc_hz: 12_000_000, + } + } +} + +/// Initialize Embassy for Non-Secure TrustZone operation. +/// +/// Use this instead of [`init()`] when the Secure world has already configured +/// clocks, PLLs, and tick generators. This function: +/// - Steals peripheral singletons (no singleton check — Secure world didn't call `take()`) +/// - Populates the internal clock frequency bookkeeping +/// - Initializes the time driver, DMA, and GPIO interrupts +/// - Does NOT touch RESETS, PLLs, XOSC, or clock muxes +/// +/// # Safety +/// - Clocks must already be configured by the Secure world +/// - The provided frequencies must match the actual hardware configuration +/// - Must only be called once +pub unsafe fn init_ns(clocks: NsClockConfig) -> Peripherals { + let peripherals = Peripherals::steal(); + + // Populate the CLOCKS static so Embassy drivers know the frequencies + clocks::set_frequencies( + clocks.xosc_hz, + clocks.sys_hz, + clocks.ref_hz, + clocks.pll_sys_hz, + clocks.pll_usb_hz, + clocks.usb_hz, + clocks.adc_hz, + clocks.peri_hz, + ); + + // Initialize subsystems that just enable interrupts (no hardware reset) + #[cfg(feature = "time-driver")] + time_driver::init(); + dma::init(); + gpio::init(); + + peripherals +} + +#[cfg(all(feature = "rt", not(feature = "trustzone-ns")))] +#[cortex_m_rt::pre_init] +unsafe fn pre_init() { + // SIO does not get reset when core0 is reset with either `scb::sys_reset()` or with SWD. + // Since we're using SIO spinlock 31 for the critical-section impl, this causes random + // hangs if we reset in the middle of a CS, because the next boot sees the spinlock + // as locked and waits forever. + // + // See https://github.com/embassy-rs/embassy/issues/1736 + // and https://github.com/rp-rs/rp-hal/issues/292 + // and https://matrix.to/#/!vhKMWjizPZBgKeknOo:matrix.org/$VfOkQgyf1PjmaXZbtycFzrCje1RorAXd8BQFHTl4d5M + // + // According to Raspberry Pi, this is considered Working As Intended, and not an errata, + // even though this behavior is different from every other ARM chip (sys_reset usually resets + // the *system* as its name implies, not just the current core). + // + // To fix this, reset SIO on boot. We must do this in pre_init because it's unsound to do it + // in `embassy_rp::init`, since the user could've acquired a CS by then. pre_init is guaranteed + // to run before any user code. + // + // A similar thing could happen with PROC1. It is unclear whether it's possible for PROC1 + // to stay unreset through a PROC0 reset, so we reset it anyway just in case. + // + // Important info from PSM logic (from Luke Wren in above Matrix thread) + // + // The logic is, each PSM stage is reset if either of the following is true: + // - The previous stage is in reset and FRCE_ON is false + // - FRCE_OFF is true + // + // The PSM order is SIO -> PROC0 -> PROC1. + // So, we have to force-on PROC0 to prevent it from getting reset when resetting SIO. + #[cfg(feature = "rp2040")] + { + pac::PSM.frce_on().write_and_wait(|w| { + w.set_proc0(true); + }); + // Then reset SIO and PROC1. + pac::PSM.frce_off().write_and_wait(|w| { + w.set_sio(true); + w.set_proc1(true); + }); + // clear force_off first, force_on second. The other way around would reset PROC0. + pac::PSM.frce_off().write_and_wait(|_| {}); + pac::PSM.frce_on().write_and_wait(|_| {}); + } + + #[cfg(feature = "_rp235x")] + { + // on RP235x, datasheet says "The FRCE_ON register is a development feature that does nothing in production devices." + // No idea why they removed it. Removing it means we can't use PSM to reset SIO, because it comes before + // PROC0, so we'd need FRCE_ON to prevent resetting ourselves. + // + // So we just unlock the spinlock manually. + pac::SIO.spinlock(31).write_value(1); + + // We can still use PSM to reset PROC1 since it comes after PROC0 in the state machine. + pac::PSM.frce_off().write_and_wait(|w| w.set_proc1(true)); + pac::PSM.frce_off().write_and_wait(|_| {}); + + // Make atomics work between cores. + enable_actlr_extexclall(); + } +} + +/// Set the EXTEXCLALL bit in ACTLR. +/// +/// The default MPU memory map marks all memory as non-shareable, so atomics don't +/// synchronize memory accesses between cores at all. This bit forces all memory to be +/// considered shareable regardless of what the MPU says. +/// +/// TODO: does this interfere somehow if the user wants to use a custom MPU configuration? +/// maybe we need to add a way to disable this? +/// +/// This must be done FOR EACH CORE. +#[cfg(feature = "_rp235x")] +unsafe fn enable_actlr_extexclall() { + (&*cortex_m::peripheral::ICB::PTR).actlr.modify(|w| w | (1 << 29)); +} + +/// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes. +#[allow(unused)] +trait RegExt { + #[allow(unused)] + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_and_wait(&self, f: impl FnOnce(&mut T) -> R) -> R + where + T: PartialEq; +} + +impl RegExt for pac::common::Reg { + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x1000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x2000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x3000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_and_wait(&self, f: impl FnOnce(&mut T) -> R) -> R + where + T: PartialEq, + { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + self.as_ptr().write_volatile(val); + while self.as_ptr().read_volatile() != val {} + } + res + } +} diff --git a/embassy-rp-fork/src/multicore.rs b/embassy-rp-fork/src/multicore.rs new file mode 100644 index 0000000..572d8db --- /dev/null +++ b/embassy-rp-fork/src/multicore.rs @@ -0,0 +1,367 @@ +//! Multicore support +//! +//! This module handles setup of the 2nd cpu core on the rp2040, which we refer to as core1. +//! It provides functionality for setting up the stack, and starting core1. +//! +//! The entrypoint for core1 can be any function that never returns, including closures. +//! +//! Enable the `critical-section-impl` feature in embassy-rp when sharing data across cores using +//! the `embassy-sync` primitives and `CriticalSectionRawMutex`. +//! +//! # Usage +//! +//! ```no_run +//! use embassy_rp::multicore::Stack; +//! use static_cell::StaticCell; +//! use embassy_executor::Executor; +//! use core::ptr::addr_of_mut; +//! +//! static mut CORE1_STACK: Stack<4096> = Stack::new(); +//! static EXECUTOR0: StaticCell = StaticCell::new(); +//! static EXECUTOR1: StaticCell = StaticCell::new(); +//! +//! # // workaround weird error: `main` function not found in crate `rust_out` +//! # let _ = (); +//! +//! #[embassy_executor::task] +//! async fn core0_task() { +//! // ... +//! } +//! +//! #[embassy_executor::task] +//! async fn core1_task() { +//! // ... +//! } +//! +//! #[cortex_m_rt::entry] +//! fn main() -> ! { +//! let p = embassy_rp::init(Default::default()); +//! +//! embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut *addr_of_mut!(CORE1_STACK) }, move || { +//! let executor1 = EXECUTOR1.init(Executor::new()); +//! executor1.run(|spawner| spawner.spawn(core1_task().unwrap())); +//! }); +//! +//! let executor0 = EXECUTOR0.init(Executor::new()); +//! executor0.run(|spawner| spawner.spawn(core0_task().unwrap())) +//! } +//! ``` + +use core::mem::ManuallyDrop; +use core::sync::atomic::{AtomicBool, Ordering, compiler_fence}; + +use crate::interrupt::InterruptExt; +use crate::peripherals::CORE1; +use crate::{Peri, gpio, install_stack_guard, interrupt, pac}; + +const PAUSE_TOKEN: u32 = 0xDEADBEEF; +const RESUME_TOKEN: u32 = !0xDEADBEEF; +static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); + +/// Represents a particular CPU core (SIO_CPUID) +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum CoreId { + /// Core 0 + Core0 = 0x0, + /// Core 1 + Core1 = 0x1, +} + +/// Gets which core we are currently executing from +pub fn current_core() -> CoreId { + if pac::SIO.cpuid().read() == 0 { + CoreId::Core0 + } else { + CoreId::Core1 + } +} + +#[inline(always)] +unsafe fn core1_setup(stack_bottom: *mut usize) { + if install_stack_guard(stack_bottom).is_err() { + // currently only happens if the MPU was already set up, which + // would indicate that the core is already in use from outside + // embassy, somehow. trap if so since we can't deal with that. + cortex_m::asm::udf(); + } + + #[cfg(feature = "_rp235x")] + crate::enable_actlr_extexclall(); + + unsafe { + gpio::init(); + } +} + +/// Data type for a properly aligned stack of N bytes +#[repr(C, align(32))] +pub struct Stack { + /// Memory to be used for the stack + pub mem: [u8; SIZE], +} + +impl Stack { + /// Construct a stack of length SIZE, initialized to 0 + pub const fn new() -> Stack { + Stack { mem: [0_u8; SIZE] } + } +} + +#[cfg(all(feature = "rt", feature = "rp2040"))] +#[interrupt] +unsafe fn SIO_IRQ_PROC1() { + let sio = pac::SIO; + // Clear IRQ + sio.fifo().st().write(|w| w.set_wof(false)); + + while sio.fifo().st().read().vld() { + // Pause CORE1 execution and disable interrupts + if fifo_read_wfe() == PAUSE_TOKEN { + cortex_m::interrupt::disable(); + // Signal to CORE0 that execution is paused + fifo_write(PAUSE_TOKEN); + // Wait for `resume` signal from CORE0 + while fifo_read_wfe() != RESUME_TOKEN { + cortex_m::asm::nop(); + } + cortex_m::interrupt::enable(); + // Signal to CORE0 that execution is resumed + fifo_write(RESUME_TOKEN); + } + } +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +unsafe fn SIO_IRQ_FIFO() { + let sio = pac::SIO; + // Clear IRQ + sio.fifo().st().write(|w| w.set_wof(false)); + + while sio.fifo().st().read().vld() { + // Pause CORE1 execution and disable interrupts + if fifo_read_wfe() == PAUSE_TOKEN { + cortex_m::interrupt::disable(); + // Signal to CORE0 that execution is paused + fifo_write(PAUSE_TOKEN); + // Wait for `resume` signal from CORE0 + while fifo_read_wfe() != RESUME_TOKEN { + cortex_m::asm::nop(); + } + cortex_m::interrupt::enable(); + // Signal to CORE0 that execution is resumed + fifo_write(RESUME_TOKEN); + } + } +} + +/// Spawn a function on this core +pub fn spawn_core1(_core1: Peri<'static, CORE1>, stack: &'static mut Stack, entry: F) +where + F: FnOnce() -> bad::Never + Send + 'static, +{ + // The first two ignored `u64` parameters are there to take up all of the registers, + // which means that the rest of the arguments are taken from the stack, + // where we're able to put them from core 0. + extern "C" fn core1_startup bad::Never>( + _: u64, + _: u64, + entry: *mut ManuallyDrop, + stack_bottom: *mut usize, + ) -> ! { + unsafe { core1_setup(stack_bottom) }; + + let entry = unsafe { ManuallyDrop::take(&mut *entry) }; + + // make sure the preceding read doesn't get reordered past the following fifo write + compiler_fence(Ordering::SeqCst); + + // Signal that it's safe for core 0 to get rid of the original value now. + fifo_write(1); + + IS_CORE1_INIT.store(true, Ordering::Release); + // Enable fifo interrupt on CORE1 for `pause` functionality. + #[cfg(feature = "rp2040")] + unsafe { + interrupt::SIO_IRQ_PROC1.enable() + }; + #[cfg(feature = "_rp235x")] + unsafe { + interrupt::SIO_IRQ_FIFO.enable() + }; + + // Enable FPU + #[cfg(all(feature = "_rp235x", has_fpu))] + unsafe { + let p = cortex_m::Peripherals::steal(); + p.SCB.cpacr.modify(|cpacr| cpacr | (3 << 20) | (3 << 22)); + } + + entry() + } + + // Reset the core + let psm = pac::PSM; + psm.frce_off().modify(|w| w.set_proc1(true)); + while !psm.frce_off().read().proc1() { + cortex_m::asm::nop(); + } + psm.frce_off().modify(|w| w.set_proc1(false)); + + // The ARM AAPCS ABI requires 8-byte stack alignment. + // #[align] on `struct Stack` ensures the bottom is aligned, but the top could still be + // unaligned if the user chooses a stack size that's not multiple of 8. + // So, we round down to the next multiple of 8. + let stack_words = stack.mem.len() / 8 * 2; + let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack_words) }; + + // Set up the stack + let mut stack_ptr = unsafe { mem.as_mut_ptr().add(mem.len()) }; + + // We don't want to drop this, since it's getting moved to the other core. + let mut entry = ManuallyDrop::new(entry); + + // Push the arguments to `core1_startup` onto the stack. + unsafe { + // Push `stack_bottom`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut usize>().write(mem.as_mut_ptr()); + + // Push `entry`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut ManuallyDrop>().write(&mut entry); + } + + // Make sure the compiler does not reorder the stack writes after to after the + // below FIFO writes, which would result in them not being seen by the second + // core. + // + // From the compiler perspective, this doesn't guarantee that the second core + // actually sees those writes. However, we know that the RP2040 doesn't have + // memory caches, and writes happen in-order. + compiler_fence(Ordering::Release); + + let p = unsafe { cortex_m::Peripherals::steal() }; + let vector_table = p.SCB.vtor.read(); + + // After reset, core 1 is waiting to receive commands over FIFO. + // This is the sequence to have it jump to some code. + let cmd_seq = [ + 0, + 0, + 1, + vector_table as usize, + stack_ptr as usize, + core1_startup:: as usize, + ]; + + let mut seq = 0; + let mut fails = 0; + loop { + let cmd = cmd_seq[seq] as u32; + if cmd == 0 { + fifo_drain(); + cortex_m::asm::sev(); + } + fifo_write(cmd); + + let response = fifo_read(); + if cmd == response { + seq += 1; + } else { + seq = 0; + fails += 1; + if fails > 16 { + // The second core isn't responding, and isn't going to take the entrypoint + panic!("CORE1 not responding"); + } + } + if seq >= cmd_seq.len() { + break; + } + } + + // Wait until the other core has copied `entry` before returning. + fifo_read(); +} + +/// Pause execution on CORE1. +pub fn pause_core1() { + if IS_CORE1_INIT.load(Ordering::Acquire) { + fifo_write(PAUSE_TOKEN); + // Wait for CORE1 to signal it has paused execution. + while fifo_read() != PAUSE_TOKEN {} + } +} + +/// Resume CORE1 execution. +pub fn resume_core1() { + if IS_CORE1_INIT.load(Ordering::Acquire) { + fifo_write(RESUME_TOKEN); + // Wait for CORE1 to signal it has resumed execution. + while fifo_read() != RESUME_TOKEN {} + } +} + +// Push a value to the inter-core FIFO, block until space is available +#[inline(always)] +fn fifo_write(value: u32) { + let sio = pac::SIO; + // Wait for the FIFO to have enough space + while !sio.fifo().st().read().rdy() { + cortex_m::asm::nop(); + } + sio.fifo().wr().write_value(value); + // Fire off an event to the other core. + // This is required as the other core may be `wfe` (waiting for event) + cortex_m::asm::sev(); +} + +// Pop a value from inter-core FIFO, block until available +#[inline(always)] +fn fifo_read() -> u32 { + let sio = pac::SIO; + // Wait until FIFO has data + while !sio.fifo().st().read().vld() { + cortex_m::asm::nop(); + } + sio.fifo().rd().read() +} + +// Pop a value from inter-core FIFO, `wfe` until available +#[inline(always)] +#[allow(unused)] +fn fifo_read_wfe() -> u32 { + let sio = pac::SIO; + // Wait until FIFO has data + while !sio.fifo().st().read().vld() { + cortex_m::asm::wfe(); + } + sio.fifo().rd().read() +} + +// Drain inter-core FIFO +#[inline(always)] +fn fifo_drain() { + let sio = pac::SIO; + while sio.fifo().st().read().vld() { + let _ = sio.fifo().rd().read(); + } +} + +// https://github.com/nvzqz/bad-rs/blob/master/src/never.rs +mod bad { + pub(crate) type Never = ::Output; + + pub trait HasOutput { + type Output; + } + + impl HasOutput for fn() -> O { + type Output = O; + } + + type F = fn() -> !; +} diff --git a/embassy-rp-fork/src/otp.rs b/embassy-rp-fork/src/otp.rs new file mode 100644 index 0000000..6091f71 --- /dev/null +++ b/embassy-rp-fork/src/otp.rs @@ -0,0 +1,175 @@ +//! Interface to the RP2350's One Time Programmable Memory + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +use crate::rom_data::otp_access; + +/// The ways in which we can fail to access OTP +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The user passed an invalid index to a function. + InvalidIndex, + /// The hardware refused to let us read this word, probably due to + /// read or write lock set earlier in the boot process. + InvalidPermissions, + /// Modification is impossible based on current state; e.g. + /// attempted to clear an OTP bit. + UnsupportedModification, + /// Value being written is bigger than 24 bits allowed for raw writes. + Overflow, + /// An unexpected failure that contains the exact return code + UnexpectedFailure(i32), +} + +/// OTP read address, using automatic Error Correction. +/// +/// A 32-bit read returns the ECC-corrected data for two neighbouring rows, or +/// all-ones on permission failure. Only the first 8 KiB is populated. +pub const OTP_DATA_BASE: *const u32 = 0x4013_0000 as *const u32; + +/// OTP read address, without using any automatic Error Correction. +/// +/// A 32-bit read returns 24-bits of raw data from the OTP word. +pub const OTP_DATA_RAW_BASE: *const u32 = 0x4013_4000 as *const u32; + +/// How many pages in OTP (post error-correction) +pub const NUM_PAGES: usize = 64; + +/// How many rows in one page in OTP (post error-correction) +pub const NUM_ROWS_PER_PAGE: usize = 64; + +/// How many rows in OTP (post error-correction) +pub const NUM_ROWS: usize = NUM_PAGES * NUM_ROWS_PER_PAGE; + +/// 24bit mask for raw writes +pub const RAW_WRITE_BIT_MASK: u32 = 0x00FF_FFFF; + +/// Read one ECC protected word from the OTP +pub fn read_ecc_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // First do a raw read to check permissions + let _ = read_raw_word(row)?; + // One 32-bit read gets us two rows + let offset = row >> 1; + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_BASE.add(offset).read() }; + if (row & 1) == 0 { + Ok(value as u16) + } else { + Ok((value >> 16) as u16) + } +} + +/// Read one raw word from the OTP +/// +/// You get the 24-bit raw value in the lower part of the 32-bit result. +pub fn read_raw_word(row: usize) -> Result { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + // One 32-bit read gets us one row + // # Safety + // + // We checked this offset was in range already. + let value = unsafe { OTP_DATA_RAW_BASE.add(row).read() }; + if value == 0xFFFF_FFFF { + Err(Error::InvalidPermissions) + } else { + Ok(value) + } +} +/// Write one raw word to the OTP +/// +/// 24 bit value will be written to the OTP +pub fn write_raw_word(row: usize, data: u32) -> Result<(), Error> { + if data > RAW_WRITE_BIT_MASK { + return Err(Error::Overflow); + } + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + let row_with_write_bit = row | 0x00010000; + // # Safety + // + // We checked this row was in range already. + let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 4, row_with_write_bit as u32) }; + if result == 0 { + Ok(()) + } else { + // 5.4.3. API Function Return Codes + let error = match result { + -4 => Error::InvalidPermissions, + -18 => Error::UnsupportedModification, + _ => Error::UnexpectedFailure(result), + }; + Err(error) + } +} + +/// Write one raw word to the OTP with ECC +/// +/// 16 bit value will be written + ECC +pub fn write_ecc_word(row: usize, data: u16) -> Result<(), Error> { + if row >= NUM_ROWS { + return Err(Error::InvalidIndex); + } + let row_with_write_and_ecc_bit = row | 0x00030000; + + // # Safety + // + // We checked this row was in range already. + + let result = unsafe { otp_access(data.to_le_bytes().as_mut_ptr(), 2, row_with_write_and_ecc_bit as u32) }; + if result == 0 { + Ok(()) + } else { + // 5.4.3. API Function Return Codes + // 5.4.3. API Function Return Codes + let error = match result { + -4 => Error::InvalidPermissions, + -18 => Error::UnsupportedModification, + _ => Error::UnexpectedFailure(result), + }; + Err(error) + } +} + +/// Get the random 64bit chipid from rows 0x0-0x3. +pub fn get_chipid() -> Result { + let w0 = read_ecc_word(0x000)?.to_be_bytes(); + let w1 = read_ecc_word(0x001)?.to_be_bytes(); + let w2 = read_ecc_word(0x002)?.to_be_bytes(); + let w3 = read_ecc_word(0x003)?.to_be_bytes(); + + Ok(u64::from_be_bytes([ + w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} + +/// Get the 128bit private random number from rows 0x4-0xb. +/// +/// This ID is not exposed through the USB PICOBOOT GET_INFO command +/// or the ROM get_sys_info() API. However note that the USB PICOBOOT OTP +/// access point can read the entirety of page 0, so this value is not +/// meaningfully private unless the USB PICOBOOT interface is disabled via the +//// DISABLE_BOOTSEL_USB_PICOBOOT_IFC flag in BOOT_FLAGS0 +pub fn get_private_random_number() -> Result { + let w0 = read_ecc_word(0x004)?.to_be_bytes(); + let w1 = read_ecc_word(0x005)?.to_be_bytes(); + let w2 = read_ecc_word(0x006)?.to_be_bytes(); + let w3 = read_ecc_word(0x007)?.to_be_bytes(); + let w4 = read_ecc_word(0x008)?.to_be_bytes(); + let w5 = read_ecc_word(0x009)?.to_be_bytes(); + let w6 = read_ecc_word(0x00a)?.to_be_bytes(); + let w7 = read_ecc_word(0x00b)?.to_be_bytes(); + + Ok(u128::from_be_bytes([ + w7[0], w7[1], w6[0], w6[1], w5[0], w5[1], w4[0], w4[1], w3[0], w3[1], w2[0], w2[1], w1[0], w1[1], w0[0], w0[1], + ])) +} diff --git a/embassy-rp-fork/src/pio/instr.rs b/embassy-rp-fork/src/pio/instr.rs new file mode 100644 index 0000000..304ddb2 --- /dev/null +++ b/embassy-rp-fork/src/pio/instr.rs @@ -0,0 +1,119 @@ +//! Instructions controlling the PIO. +use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination}; + +use crate::pio::{Instance, StateMachine}; + +impl<'d, PIO: Instance, const SM: usize> StateMachine<'d, PIO, SM> { + /// Set value of scratch register X. + /// + /// SAFETY: autopull enabled else it will write undefined data. + /// Make sure to have setup the according configuration see + /// [shift_out](crate::pio::Config::shift_out) and [auto_fill](crate::pio::ShiftConfig::auto_fill). + pub unsafe fn set_x(&mut self, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::X, + bit_count: 32, + } + .encode(); + self.tx().push(value); + self.exec_instr(OUT); + } + + /// Get value of scratch register X. + /// + /// SAFETY: autopush enabled else it will read undefined data. + /// Make sure to have setup the according configurations see + /// [shift_in](crate::pio::Config::shift_in) and [auto_fill](crate::pio::ShiftConfig::auto_fill). + pub unsafe fn get_x(&mut self) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::X, + bit_count: 32, + } + .encode(); + self.exec_instr(IN); + self.rx().pull() + } + + /// Set value of scratch register Y. + /// + /// SAFETY: autopull enabled else it will write undefined data. + /// Make sure to have setup the according configuration see + /// [shift_out](crate::pio::Config::shift_out) and [auto_fill](crate::pio::ShiftConfig::auto_fill). + pub unsafe fn set_y(&mut self, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::Y, + bit_count: 32, + } + .encode(); + self.tx().push(value); + self.exec_instr(OUT); + } + + /// Get value of scratch register Y. + /// + /// SAFETY: autopush enabled else it will read undefined data. + /// Make sure to have setup the according configurations see + /// [shift_in](crate::pio::Config::shift_in) and [auto_fill](crate::pio::ShiftConfig::auto_fill). + pub unsafe fn get_y(&mut self) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::Y, + bit_count: 32, + } + .encode(); + self.exec_instr(IN); + + self.rx().pull() + } + + /// Set instruction for pindir destination. + pub unsafe fn set_pindir(&mut self, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINDIRS, + data, + } + .encode(); + self.exec_instr(set); + } + + /// Set instruction for pin destination. + pub unsafe fn set_pin(&mut self, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINS, + data, + } + .encode(); + self.exec_instr(set); + } + + /// Out instruction for pin destination. + pub unsafe fn set_out_pin(&mut self, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINS, + bit_count: 32, + } + .encode(); + self.tx().push(data); + self.exec_instr(OUT); + } + + /// Out instruction for pindir destination. + pub unsafe fn set_out_pindir(&mut self, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINDIRS, + bit_count: 32, + } + .encode(); + self.tx().push(data); + self.exec_instr(OUT); + } + + /// Jump instruction to address. + pub unsafe fn exec_jmp(&mut self, to_addr: u8) { + let jmp: u16 = InstructionOperands::JMP { + address: to_addr, + condition: JmpCondition::Always, + } + .encode(); + self.exec_instr(jmp); + } +} diff --git a/embassy-rp-fork/src/pio/mod.rs b/embassy-rp-fork/src/pio/mod.rs new file mode 100644 index 0000000..1c370fd --- /dev/null +++ b/embassy-rp-fork/src/pio/mod.rs @@ -0,0 +1,1589 @@ +//! PIO driver. +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::sync::atomic::{AtomicU8, AtomicU32, Ordering, compiler_fence}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::FixedU32; +use fixed::types::extra::U8; +use pio::{Program, SideSet, Wrap}; + +use crate::dma::{self, Channel, Transfer, Word}; +use crate::gpio::{self, AnyPin, Drive, Level, Pull, SealedPin, SlewRate}; +use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; +use crate::relocate::RelocatedProgram; +use crate::{RegExt, pac, peripherals}; + +mod instr; + +#[doc(inline)] +pub use pio as program; + +/// Wakers for interrupts and FIFOs. +pub struct Wakers([AtomicWaker; 12]); + +impl Wakers { + #[inline(always)] + fn fifo_in(&self) -> &[AtomicWaker] { + &self.0[0..4] + } + #[inline(always)] + fn fifo_out(&self) -> &[AtomicWaker] { + &self.0[4..8] + } + #[inline(always)] + fn irq(&self) -> &[AtomicWaker] { + &self.0[8..12] + } +} + +/// FIFO config. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum FifoJoin { + /// Both TX and RX fifo is enabled + #[default] + Duplex, + /// Rx fifo twice as deep. TX fifo disabled + RxOnly, + /// Tx fifo twice as deep. RX fifo disabled + TxOnly, + /// Enable random writes (`FJOIN_RX_PUT`) from the state machine (through ISR), + /// and random reads from the system (using [`StateMachine::get_rxf_entry`]). + #[cfg(feature = "_rp235x")] + RxAsStatus, + /// Enable random reads (`FJOIN_RX_GET`) from the state machine (through OSR), + /// and random writes from the system (using [`StateMachine::set_rxf_entry`]). + #[cfg(feature = "_rp235x")] + RxAsControl, + /// FJOIN_RX_PUT | FJOIN_RX_GET: RX can be used as a scratch register, + /// not accessible from the CPU + #[cfg(feature = "_rp235x")] + PioScratch, +} + +/// Shift direction. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ShiftDirection { + #[default] + Right = 1, + Left = 0, +} + +/// Pin direction. +#[allow(missing_docs)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Direction { + In = 0, + Out = 1, +} + +/// Which fifo level to use in status check. +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum StatusSource { + #[default] + /// All-ones if TX FIFO level < N, otherwise all-zeroes. + TxFifoLevel = 0, + /// All-ones if RX FIFO level < N, otherwise all-zeroes. + RxFifoLevel = 1, + /// All-ones if the indexed IRQ flag is raised, otherwise all-zeroes + #[cfg(feature = "_rp235x")] + Irq = 2, +} + +const RXNEMPTY_MASK: u32 = 1 << 0; +const TXNFULL_MASK: u32 = 1 << 4; +const SMIRQ_MASK: u32 = 1 << 8; + +/// Interrupt handler for PIO. +pub struct InterruptHandler { + _pio: PhantomData, +} + +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let ints = PIO::PIO.irqs(0).ints().read().0; + for bit in 0..12 { + if ints & (1 << bit) != 0 { + PIO::wakers().0[bit].wake(); + } + } + PIO::PIO.irqs(0).inte().write_clear(|m| m.0 = ints); + } +} + +/// Future that waits for TX-FIFO to become writable +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoOutFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_tx: &'a mut StateMachineTx<'d, PIO, SM>, + value: u32, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoOutFuture<'a, 'd, PIO, SM> { + /// Create a new future waiting for TX-FIFO to become writable. + pub fn new(sm: &'a mut StateMachineTx<'d, PIO, SM>, value: u32) -> Self { + FifoOutFuture { sm_tx: sm, value } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoOutFuture<'a, 'd, PIO, SM> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + let value = self.value; + if self.get_mut().sm_tx.try_push(value) { + Poll::Ready(()) + } else { + PIO::wakers().fifo_out()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = TXNFULL_MASK << SM; + }); + // debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoOutFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = TXNFULL_MASK << SM; + }); + } +} + +/// Future that waits for RX-FIFO to become readable. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoInFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_rx: &'a mut StateMachineRx<'d, PIO, SM>, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoInFuture<'a, 'd, PIO, SM> { + /// Create future that waits for RX-FIFO to become readable. + pub fn new(sm: &'a mut StateMachineRx<'d, PIO, SM>) -> Self { + FifoInFuture { sm_rx: sm } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoInFuture<'a, 'd, PIO, SM> { + type Output = u32; + fn poll(mut self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + if let Some(v) = self.sm_rx.try_pull() { + Poll::Ready(v) + } else { + PIO::wakers().fifo_in()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + //debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoInFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + } +} + +/// Future that waits for IRQ +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct IrqFuture<'a, 'd, PIO: Instance> { + pio: PhantomData<&'a mut Irq<'d, PIO, 0>>, + irq_no: u8, +} + +impl<'a, 'd, PIO: Instance> Future for IrqFuture<'a, 'd, PIO> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + + // Check if IRQ flag is already set + if PIO::PIO.irq().read().0 & (1 << self.irq_no) != 0 { + PIO::PIO.irq().write(|m| m.0 = 1 << self.irq_no); + return Poll::Ready(()); + } + + PIO::wakers().irq()[self.irq_no as usize].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + Poll::Pending + } +} + +impl<'a, 'd, PIO: Instance> Drop for IrqFuture<'a, 'd, PIO> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + } +} + +/// Type representing a PIO pin. +pub struct Pin<'l, PIO: Instance> { + pin: Peri<'l, AnyPin>, + pio: PhantomData, +} + +impl<'l, PIO: Instance> Pin<'l, PIO> { + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2M_A, + Drive::_4mA => pac::pads::vals::Drive::_4M_A, + Drive::_8mA => pac::pads::vals::Drive::_8M_A, + Drive::_12mA => pac::pads::vals::Drive::_12M_A, + }); + }); + } + + /// Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + self.pin.pad_ctrl().modify(|w| { + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + } + + /// Set the pin's schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + + /// Configure the output logic inversion of this pin. + #[inline] + pub fn set_output_inversion(&mut self, invert: bool) { + self.pin.gpio().ctrl().modify(|w| { + w.set_outover(if invert { + pac::io::vals::Outover::INVERT + } else { + pac::io::vals::Outover::NORMAL + }) + }); + } + + /// Set the pin's input sync bypass. + pub fn set_input_sync_bypass(&mut self, bypass: bool) { + let mask = 1 << self.pin(); + if bypass { + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask); + } else { + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask); + } + } + + /// Get the underlying pin number. + pub fn pin(&self) -> u8 { + self.pin._pin() + } +} + +/// Type representing a state machine RX FIFO. +pub struct StateMachineRx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { + /// Check if RX FIFO is empty. + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().rxempty() & (1u8 << SM) != 0 + } + + /// Check if RX FIFO is full. + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().rxfull() & (1u8 << SM) != 0 + } + + /// Check RX FIFO level. + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8 + 4)) as u8 & 0x0f + } + + /// Check if state machine has stalled on full RX FIFO. + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxstall(1 << SM)); + } + ret + } + + /// Check if RX FIFO underflow (i.e. read-on-empty by the system) has occurred. + pub fn underflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxunder() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxunder(1 << SM)); + } + ret + } + + /// Pull data from RX FIFO. + /// + /// This function doesn't check if there is data available to be read. + /// If the rx FIFO is empty, an undefined value is returned. If you only + /// want to pull if data is available, use `try_pull` instead. + pub fn pull(&mut self) -> u32 { + PIO::PIO.rxf(SM).read() + } + + /// Attempt pulling data from RX FIFO. + pub fn try_pull(&mut self) -> Option { + if self.empty() { + return None; + } + Some(self.pull()) + } + + /// Wait for RX FIFO readable. + pub fn wait_pull<'a>(&'a mut self) -> FifoInFuture<'a, 'd, PIO, SM> { + FifoInFuture::new(self) + } + + fn dreq() -> crate::pac::dma::vals::TreqSel { + crate::pac::dma::vals::TreqSel::from(PIO::PIO_NO * 8 + SM as u8 + 4) + } + + /// Prepare DMA transfer from RX FIFO. + pub fn dma_pull<'a, C: Channel, W: Word>( + &'a mut self, + ch: Peri<'a, C>, + data: &'a mut [W], + bswap: bool, + ) -> Transfer<'a, C> { + let p = ch.regs(); + p.write_addr().write_value(data.as_ptr() as u32); + p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = data.len() as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(data.len() as u32)); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + w.set_treq_sel(Self::dreq()); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(true); + w.set_bswap(bswap); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } + + /// Prepare a repeated DMA transfer from RX FIFO. + pub fn dma_pull_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> { + // This is the read version of dma::write_repeated. This allows us to + // discard reads from the RX FIFO through DMA. + + // static mut so it gets allocated in RAM + static mut DUMMY: u32 = 0; + + let p = ch.regs(); + p.write_addr().write_value(core::ptr::addr_of_mut!(DUMMY) as u32); + p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); + + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = len as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(len as u32)); + + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + w.set_treq_sel(Self::dreq()); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +/// Type representing a state machine TX FIFO. +pub struct StateMachineTx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { + /// Check if TX FIFO is empty. + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().txempty() & (1u8 << SM) != 0 + } + + /// Check if TX FIFO is full. + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().txfull() & (1u8 << SM) != 0 + } + + /// Check TX FIFO level. + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f + } + + /// Check if state machine has stalled on empty TX FIFO. + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txstall(1 << SM)); + } + ret + } + + /// Check if FIFO overflowed. + pub fn overflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txover() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txover(1 << SM)); + } + ret + } + + /// Force push data to TX FIFO. + pub fn push(&mut self, v: u32) { + PIO::PIO.txf(SM).write_value(v); + } + + /// Attempt to push data to TX FIFO. + pub fn try_push(&mut self, v: u32) -> bool { + if self.full() { + return false; + } + self.push(v); + true + } + + /// Wait until FIFO is ready for writing. + pub fn wait_push<'a>(&'a mut self, value: u32) -> FifoOutFuture<'a, 'd, PIO, SM> { + FifoOutFuture::new(self, value) + } + + fn dreq() -> crate::pac::dma::vals::TreqSel { + crate::pac::dma::vals::TreqSel::from(PIO::PIO_NO * 8 + SM as u8) + } + + /// Prepare a DMA transfer to TX FIFO. + pub fn dma_push<'a, C: Channel, W: Word>( + &'a mut self, + ch: Peri<'a, C>, + data: &'a [W], + bswap: bool, + ) -> Transfer<'a, C> { + let p = ch.regs(); + p.read_addr().write_value(data.as_ptr() as u32); + p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); + #[cfg(feature = "rp2040")] + p.trans_count().write(|w| *w = data.len() as u32); + #[cfg(feature = "_rp235x")] + p.trans_count().write(|w| w.set_count(data.len() as u32)); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + w.set_treq_sel(Self::dreq()); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(true); + w.set_incr_write(false); + w.set_bswap(bswap); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } + + /// Prepare a repeated DMA transfer to TX FIFO. + pub fn dma_push_repeated<'a, C: Channel, W: Word>(&'a mut self, ch: Peri<'a, C>, len: usize) -> Transfer<'a, C> { + unsafe { dma::write_repeated(ch, PIO::PIO.txf(SM).as_ptr(), len, Self::dreq()) } + } +} + +/// A type representing a single PIO state machine. +pub struct StateMachine<'d, PIO: Instance, const SM: usize> { + rx: StateMachineRx<'d, PIO, SM>, + tx: StateMachineTx<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> Drop for StateMachine<'d, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(1 << SM)); + on_pio_drop::(); + } +} + +fn assert_consecutive(pins: &[&Pin]) { + for (p1, p2) in pins.iter().zip(pins.iter().skip(1)) { + // purposely does not allow wrap-around because we can't claim pins 30 and 31. + assert!(p1.pin() + 1 == p2.pin(), "pins must be consecutive"); + } +} + +/// PIO Execution config. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ExecConfig { + /// If true, the MSB of the Delay/Side-set instruction field is used as side-set enable, rather than a side-set data bit. + pub side_en: bool, + /// If true, side-set data is asserted to pin directions, instead of pin values. + pub side_pindir: bool, + /// Pin to trigger jump. + pub jmp_pin: u8, + /// After reaching this address, execution is wrapped to wrap_bottom. + pub wrap_top: u8, + /// After reaching wrap_top, execution is wrapped to this address. + pub wrap_bottom: u8, +} + +/// PIO shift register config for input or output. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ShiftConfig { + /// Number of bits shifted out of OSR before autopull. + pub threshold: u8, + /// Shift direction. + pub direction: ShiftDirection, + /// For output: Pull automatically output shift register is emptied. + /// For input: Push automatically when the input shift register is filled. + pub auto_fill: bool, +} + +/// PIO pin config. +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PinConfig { + /// The number of MSBs of the Delay/Side-set instruction field which are used for side-set. + pub sideset_count: u8, + /// The number of pins asserted by a SET. In the range 0 to 5 inclusive. + pub set_count: u8, + /// The number of pins asserted by an OUT PINS, OUT PINDIRS or MOV PINS instruction. In the range 0 to 32 inclusive. + pub out_count: u8, + /// The pin which is mapped to the least-significant bit of a state machine's IN data bus. + pub in_base: u8, + /// The lowest-numbered pin that will be affected by a side-set operation. + pub sideset_base: u8, + /// The lowest-numbered pin that will be affected by a SET PINS or SET PINDIRS instruction. + pub set_base: u8, + /// The lowest-numbered pin that will be affected by an OUT PINS, OUT PINDIRS or MOV PINS instruction. + pub out_base: u8, +} + +/// Comparison level or IRQ index for the MOV x, STATUS instruction. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[cfg(feature = "_rp235x")] +pub enum StatusN { + /// IRQ flag in this PIO block + This(u8), + /// IRQ flag in the next lower PIO block + Lower(u8), + /// IRQ flag in the next higher PIO block + Higher(u8), +} + +#[cfg(feature = "_rp235x")] +impl Default for StatusN { + fn default() -> Self { + Self::This(0) + } +} + +#[cfg(feature = "_rp235x")] +impl Into for StatusN { + fn into(self) -> crate::pac::pio::vals::ExecctrlStatusN { + let x = match self { + StatusN::This(n) => n, + StatusN::Lower(n) => n + 0x08, + StatusN::Higher(n) => n + 0x10, + }; + + crate::pac::pio::vals::ExecctrlStatusN(x) + } +} + +/// PIO config. +#[derive(Clone, Copy, Debug)] +pub struct Config<'d, PIO: Instance> { + /// Clock divisor register for state machines. + pub clock_divider: FixedU32, + /// Which data bit to use for inline OUT enable. + pub out_en_sel: u8, + /// Use a bit of OUT data as an auxiliary write enable When used in conjunction with OUT_STICKY. + pub inline_out_en: bool, + /// Continuously assert the most recent OUT/SET to the pins. + pub out_sticky: bool, + /// Which source to use for checking status. + pub status_sel: StatusSource, + /// Status comparison level. + #[cfg(feature = "rp2040")] + pub status_n: u8, + // This cfg probably shouldn't be required, but the SVD for the 2040 doesn't have the enum + #[cfg(feature = "_rp235x")] + /// Status comparison level. + pub status_n: StatusN, + exec: ExecConfig, + origin: Option, + /// Configure FIFO allocation. + pub fifo_join: FifoJoin, + /// Input shifting config. + pub shift_in: ShiftConfig, + /// Output shifting config. + pub shift_out: ShiftConfig, + // PINCTRL + pins: PinConfig, + in_count: u8, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Default for Config<'d, PIO> { + fn default() -> Self { + Self { + clock_divider: 1u8.into(), + out_en_sel: Default::default(), + inline_out_en: Default::default(), + out_sticky: Default::default(), + status_sel: Default::default(), + status_n: Default::default(), + exec: Default::default(), + origin: Default::default(), + fifo_join: Default::default(), + shift_in: Default::default(), + shift_out: Default::default(), + pins: Default::default(), + in_count: Default::default(), + _pio: Default::default(), + } + } +} + +impl<'d, PIO: Instance> Config<'d, PIO> { + /// Get execution configuration. + pub fn get_exec(&self) -> ExecConfig { + self.exec + } + + /// Update execution configuration. + pub unsafe fn set_exec(&mut self, e: ExecConfig) { + self.exec = e; + } + + /// Get pin configuration. + pub fn get_pins(&self) -> PinConfig { + self.pins + } + + /// Update pin configuration. + pub unsafe fn set_pins(&mut self, p: PinConfig) { + self.pins = p; + } + + /// Configures this state machine to use the given program, including jumping to the origin + /// of the program. The state machine is not started. + /// + /// `side_set` sets the range of pins affected by side-sets. The range must be consecutive. + /// Sideset pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn use_program(&mut self, prog: &LoadedProgram<'d, PIO>, side_set: &[&Pin<'d, PIO>]) { + assert!((prog.side_set.bits() - prog.side_set.optional() as u8) as usize == side_set.len()); + assert_consecutive(side_set); + self.exec.side_en = prog.side_set.optional(); + self.exec.side_pindir = prog.side_set.pindirs(); + self.exec.wrap_bottom = prog.wrap.target; + self.exec.wrap_top = prog.wrap.source; + self.pins.sideset_count = prog.side_set.bits(); + self.pins.sideset_base = side_set.first().map_or(0, |p| p.pin()); + self.origin = Some(prog.origin); + } + + /// Set pin used to signal jump. + pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { + self.exec.jmp_pin = pin.pin(); + } + + /// Sets the range of pins affected by SET instructions. The range must be consecutive. + /// Set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert!(pins.len() <= 5); + assert_consecutive(pins); + self.pins.set_base = pins.first().map_or(0, |p| p.pin()); + self.pins.set_count = pins.len() as u8; + } + + /// Sets the range of pins affected by OUT instructions. The range must be consecutive. + /// Out pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.out_base = pins.first().map_or(0, |p| p.pin()); + self.pins.out_count = pins.len() as u8; + } + + /// Sets the range of pins used by IN instructions. The range must be consecutive. + /// In pins must configured as inputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.in_base = pins.first().map_or(0, |p| p.pin()); + self.in_count = pins.len() as u8; + } +} + +impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { + /// Set the config for a given PIO state machine. + pub fn set_config(&mut self, config: &Config<'d, PIO>) { + // sm expects 0 for 65536, truncation makes that happen + assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(config.clock_divider >= 1, "clkdiv must be >= 1"); + assert!(config.out_en_sel < 32, "out_en_sel must be < 32"); + //assert!(config.status_n < 32, "status_n must be < 32"); + // sm expects 0 for 32, truncation makes that happen + assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32"); + assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32"); + let sm = Self::this_sm(); + sm.clkdiv().write(|w| w.0 = config.clock_divider.to_bits() << 8); + sm.execctrl().write(|w| { + w.set_side_en(config.exec.side_en); + w.set_side_pindir(config.exec.side_pindir); + w.set_jmp_pin(config.exec.jmp_pin); + w.set_out_en_sel(config.out_en_sel); + w.set_inline_out_en(config.inline_out_en); + w.set_out_sticky(config.out_sticky); + w.set_wrap_top(config.exec.wrap_top); + w.set_wrap_bottom(config.exec.wrap_bottom); + #[cfg(feature = "_rp235x")] + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => pac::pio::vals::ExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => pac::pio::vals::ExecctrlStatusSel::RXLEVEL, + StatusSource::Irq => pac::pio::vals::ExecctrlStatusSel::IRQ, + }); + #[cfg(feature = "rp2040")] + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => pac::pio::vals::SmExecctrlStatusSel::RXLEVEL, + }); + w.set_status_n(config.status_n.into()); + }); + sm.shiftctrl().write(|w| { + w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly); + w.set_fjoin_tx(config.fifo_join == FifoJoin::TxOnly); + w.set_pull_thresh(config.shift_out.threshold); + w.set_push_thresh(config.shift_in.threshold); + w.set_out_shiftdir(config.shift_out.direction == ShiftDirection::Right); + w.set_in_shiftdir(config.shift_in.direction == ShiftDirection::Right); + w.set_autopull(config.shift_out.auto_fill); + w.set_autopush(config.shift_in.auto_fill); + + #[cfg(feature = "_rp235x")] + { + w.set_fjoin_rx_get( + config.fifo_join == FifoJoin::RxAsControl || config.fifo_join == FifoJoin::PioScratch, + ); + w.set_fjoin_rx_put( + config.fifo_join == FifoJoin::RxAsStatus || config.fifo_join == FifoJoin::PioScratch, + ); + w.set_in_count(config.in_count); + } + }); + + #[cfg(feature = "rp2040")] + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base); + w.set_sideset_base(config.pins.sideset_base); + w.set_set_base(config.pins.set_base); + w.set_out_base(config.pins.out_base); + }); + + #[cfg(feature = "_rp235x")] + { + let mut low_ok = true; + let mut high_ok = true; + + let in_pins = config.pins.in_base..config.pins.in_base + config.in_count; + let side_pins = config.pins.sideset_base..config.pins.sideset_base + config.pins.sideset_count; + let set_pins = config.pins.set_base..config.pins.set_base + config.pins.set_count; + let out_pins = config.pins.out_base..config.pins.out_base + config.pins.out_count; + + for pin_range in [in_pins, side_pins, set_pins, out_pins] { + for pin in pin_range { + low_ok &= pin < 32; + high_ok &= pin >= 16; + } + } + + if !low_ok && !high_ok { + panic!( + "All pins must either be <32 or >=16, in:{:?}-{:?}, side:{:?}-{:?}, set:{:?}-{:?}, out:{:?}-{:?}", + config.pins.in_base, + config.pins.in_base + config.in_count - 1, + config.pins.sideset_base, + config.pins.sideset_base + config.pins.sideset_count - 1, + config.pins.set_base, + config.pins.set_base + config.pins.set_count - 1, + config.pins.out_base, + config.pins.out_base + config.pins.out_count - 1, + ) + } + let shift = if low_ok { 0 } else { 16 }; + + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base.checked_sub(shift).unwrap_or_default()); + w.set_sideset_base(config.pins.sideset_base.checked_sub(shift).unwrap_or_default()); + w.set_set_base(config.pins.set_base.checked_sub(shift).unwrap_or_default()); + w.set_out_base(config.pins.out_base.checked_sub(shift).unwrap_or_default()); + }); + + PIO::PIO.gpiobase().write(|w| w.set_gpiobase(shift == 16)); + } + + if let Some(origin) = config.origin { + unsafe { self.exec_jmp(origin) } + } + } + + /// Read current instruction address for this state machine + pub fn get_addr(&self) -> u8 { + let addr = Self::this_sm().addr(); + addr.read().addr() + } + + /// Read TX FIFO threshold for this state machine. + pub fn get_tx_threshold(&self) -> u8 { + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.read().pull_thresh() + } + + /// Set/change the TX FIFO threshold for this state machine. + pub fn set_tx_threshold(&mut self, threshold: u8) { + assert!(threshold <= 31); + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_pull_thresh(threshold); + }); + } + + /// Read TX FIFO threshold for this state machine. + pub fn get_rx_threshold(&self) -> u8 { + Self::this_sm().shiftctrl().read().push_thresh() + } + + /// Set/change the RX FIFO threshold for this state machine. + pub fn set_rx_threshold(&mut self, threshold: u8) { + assert!(threshold <= 31); + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_push_thresh(threshold); + }); + } + + /// Set/change both TX and RX FIFO thresholds for this state machine. + pub fn set_thresholds(&mut self, threshold: u8) { + assert!(threshold <= 31); + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_push_thresh(threshold); + w.set_pull_thresh(threshold); + }); + } + + /// Set the clock divider for this state machine. + pub fn set_clock_divider(&mut self, clock_divider: FixedU32) { + let sm = Self::this_sm(); + sm.clkdiv().write(|w| w.0 = clock_divider.to_bits() << 8); + } + + #[inline(always)] + fn this_sm() -> crate::pac::pio::StateMachine { + PIO::PIO.sm(SM) + } + + /// Restart this state machine. + pub fn restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_sm_restart(mask)); + } + + /// Enable state machine. + pub fn set_enable(&mut self, enable: bool) { + let mask = 1u8 << SM; + if enable { + PIO::PIO.ctrl().write_set(|w| w.set_sm_enable(mask)); + } else { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(mask)); + } + } + + /// Check if state machine is enabled. + pub fn is_enabled(&self) -> bool { + PIO::PIO.ctrl().read().sm_enable() & (1u8 << SM) != 0 + } + + /// Restart a state machine's clock divider from an initial phase of 0. + pub fn clkdiv_restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_clkdiv_restart(mask)); + } + + fn with_paused(&mut self, f: impl FnOnce(&mut Self)) { + let enabled = self.is_enabled(); + self.set_enable(false); + let pincfg = Self::this_sm().pinctrl().read(); + let execcfg = Self::this_sm().execctrl().read(); + Self::this_sm().execctrl().write_clear(|w| w.set_out_sticky(true)); + f(self); + Self::this_sm().pinctrl().write_value(pincfg); + Self::this_sm().execctrl().write_value(execcfg); + self.set_enable(enabled); + } + + #[cfg(feature = "rp2040")] + fn pin_base() -> u8 { + 0 + } + + #[cfg(feature = "_rp235x")] + fn pin_base() -> u8 { + if PIO::PIO.gpiobase().read().gpiobase() { 16 } else { 0 } + } + + /// Sets pin directions. This pauses the current state machine to run `SET` commands + /// and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin() - Self::pin_base()); + w.set_set_count(1); + }); + // SET PINDIRS, (dir) + unsafe { sm.exec_instr(0b111_00000_100_00000 | dir as u16) }; + } + }); + } + + /// Sets pin output values. This pauses the current state machine to run + /// `SET` commands and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pins(&mut self, level: Level, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin() - Self::pin_base()); + w.set_set_count(1); + }); + // SET PINS, (dir) + unsafe { sm.exec_instr(0b11100_000_000_00000 | level as u16) }; + } + }); + } + + /// Flush FIFOs for state machine. + pub fn clear_fifos(&mut self) { + // Toggle FJOIN_RX to flush FIFOs + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + } + + /// Instruct state machine to execute a given instructions + /// + /// SAFETY: The state machine must be in a state where executing + /// an arbitrary instruction does not crash it. + pub unsafe fn exec_instr(&mut self, instr: u16) { + Self::this_sm().instr().write(|w| w.set_instr(instr)); + } + + /// Return a read handle for reading state machine outputs. + pub fn rx(&mut self) -> &mut StateMachineRx<'d, PIO, SM> { + &mut self.rx + } + + /// Return a handle for writing to inputs. + pub fn tx(&mut self) -> &mut StateMachineTx<'d, PIO, SM> { + &mut self.tx + } + + /// Return both read and write handles for the state machine. + pub fn rx_tx(&mut self) -> (&mut StateMachineRx<'d, PIO, SM>, &mut StateMachineTx<'d, PIO, SM>) { + (&mut self.rx, &mut self.tx) + } + + /// Return the contents of the nth entry of the RX FIFO + /// (should be used only when the FIFO config is set to [`FifoJoin::RxAsStatus`]) + #[cfg(feature = "_rp235x")] + pub fn get_rxf_entry(&self, n: usize) -> u32 { + PIO::PIO.rxf_putget(SM).putget(n).read() + } + + /// Set the contents of the nth entry of the RX FIFO + /// (should be used only when the FIFO config is set to [`FifoJoin::RxAsControl`]) + #[cfg(feature = "_rp235x")] + pub fn set_rxf_entry(&self, n: usize, val: u32) { + PIO::PIO.rxf_putget(SM).putget(n).write_value(val) + } +} + +/// PIO handle. +pub struct Common<'d, PIO: Instance> { + instructions_used: u32, + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Drop for Common<'d, PIO> { + fn drop(&mut self) { + on_pio_drop::(); + } +} + +/// Memory of PIO instance. +pub struct InstanceMemory<'d, PIO: Instance> { + used_mask: u32, + pio: PhantomData<&'d mut PIO>, +} + +/// A loaded PIO program. +pub struct LoadedProgram<'d, PIO: Instance> { + /// Memory used by program. + pub used_memory: InstanceMemory<'d, PIO>, + /// Program origin for loading. + pub origin: u8, + /// Wrap controls what to do once program is done executing. + pub wrap: Wrap, + /// Data for 'side' set instruction parameters. + pub side_set: SideSet, +} + +/// Errors loading a PIO program. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LoadError { + /// Insufficient consecutive free instruction space to load program. + InsufficientSpace, + /// Loading the program would overwrite an instruction address already + /// used by another program. + AddressInUse(usize), +} + +impl<'d, PIO: Instance> Common<'d, PIO> { + /// Load a PIO program. This will automatically relocate the program to + /// an available chunk of free instruction memory if the program origin + /// was not explicitly specified, otherwise it will attempt to load the + /// program only at its origin. + pub fn load_program(&mut self, prog: &Program) -> LoadedProgram<'d, PIO> { + match self.try_load_program(prog) { + Ok(r) => r, + Err(e) => panic!("Failed to load PIO program: {:?}", e), + } + } + + /// Load a PIO program. This will automatically relocate the program to + /// an available chunk of free instruction memory if the program origin + /// was not explicitly specified, otherwise it will attempt to load the + /// program only at its origin. + pub fn try_load_program( + &mut self, + prog: &Program, + ) -> Result, LoadError> { + match prog.origin { + Some(origin) => self.try_load_program_at(prog, origin).map_err(LoadError::AddressInUse), + None => { + // naively search for free space, allowing wraparound since + // PIO does support that. with only 32 instruction slots it + // doesn't make much sense to do anything more fancy. + let mut origin = 0; + while origin < 32 { + match self.try_load_program_at(prog, origin as _) { + Ok(r) => return Ok(r), + Err(a) => origin = a + 1, + } + } + Err(LoadError::InsufficientSpace) + } + } + } + + fn try_load_program_at( + &mut self, + prog: &Program, + origin: u8, + ) -> Result, usize> { + #[cfg(not(feature = "_rp235x"))] + assert!(prog.version == pio::PioVersion::V0); + + let prog = RelocatedProgram::new_with_origin(prog, origin); + let used_memory = self.try_write_instr(prog.origin() as _, prog.code())?; + Ok(LoadedProgram { + used_memory, + origin: prog.origin(), + wrap: prog.wrap(), + side_set: prog.side_set(), + }) + } + + fn try_write_instr(&mut self, start: usize, instrs: I) -> Result, usize> + where + I: Iterator, + { + let mut used_mask = 0; + for (i, instr) in instrs.enumerate() { + // wrapping around the end of program memory is valid, let's make use of that. + let addr = (i + start) % 32; + let mask = 1 << addr; + if (self.instructions_used | used_mask) & mask != 0 { + return Err(addr); + } + PIO::PIO.instr_mem(addr).write(|w| { + w.set_instr_mem(instr); + }); + used_mask |= mask; + } + self.instructions_used |= used_mask; + Ok(InstanceMemory { + used_mask, + pio: PhantomData, + }) + } + + /// Free instruction memory. This is always possible but unsafe if any + /// state machine is still using this bit of memory. + pub unsafe fn free_instr(&mut self, instrs: InstanceMemory) { + self.instructions_used &= !instrs.used_mask; + } + + /// Bypass flipflop synchronizer on GPIO inputs. + pub fn set_input_sync_bypass<'a>(&'a mut self, bypass: u32, mask: u32) { + // this can interfere with per-pin bypass functions. splitting the + // modification is going to be fine since nothing that relies on + // it can reasonably run before we finish. + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask & bypass); + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask & !bypass); + } + + /// Get bypass configuration. + pub fn get_input_sync_bypass(&self) -> u32 { + PIO::PIO.input_sync_bypass().read() + } + + /// Register a pin for PIO usage. Pins will be released from the PIO block + /// (i.e., have their `FUNCSEL` reset to `NULL`) when the [`Common`] *and* + /// all [`StateMachine`]s for this block have been dropped. **Other members + /// of [`Pio`] do not keep pin registrations alive.** + pub fn make_pio_pin(&mut self, pin: Peri<'d, impl PioPin + 'd>) -> Pin<'d, PIO> { + // enable the outputs + pin.pad_ctrl().write(|w| w.set_od(false)); + // especially important on the 235x, where IE defaults to 0 + pin.pad_ctrl().write(|w| w.set_ie(true)); + + pin.gpio().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + // TODO rp235x errata E9 recommends to not enable IE if we're not + // going to use input. Maybe add an API for the user to enable/disable this? + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + // we can be relaxed about this because we're &mut here and nothing is cached + critical_section::with(|_| { + let val = PIO::state().used_pins.load(Ordering::Relaxed); + PIO::state() + .used_pins + .store(val | 1 << pin.pin_bank(), Ordering::Relaxed); + }); + + Pin { + pin: pin.into(), + pio: PhantomData::default(), + } + } + + /// Apply changes to all state machines in a batch. + pub fn apply_sm_batch(&mut self, f: impl FnOnce(&mut PioBatch<'d, PIO>)) { + let mut batch = PioBatch { + clkdiv_restart: 0, + sm_restart: 0, + sm_enable_mask: 0, + sm_enable: 0, + _pio: PhantomData, + }; + f(&mut batch); + PIO::PIO.ctrl().modify(|w| { + w.set_clkdiv_restart(batch.clkdiv_restart); + w.set_sm_restart(batch.sm_restart); + w.set_sm_enable((w.sm_enable() & !batch.sm_enable_mask) | batch.sm_enable); + }); + } +} + +/// Represents multiple state machines in a single type. +pub struct PioBatch<'a, PIO: Instance> { + clkdiv_restart: u8, + sm_restart: u8, + sm_enable_mask: u8, + sm_enable: u8, + _pio: PhantomData<&'a PIO>, +} + +impl<'a, PIO: Instance> PioBatch<'a, PIO> { + /// Restart a state machine's clock divider from an initial phase of 0. + pub fn restart(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { + self.clkdiv_restart |= 1 << SM; + } + + /// Enable a specific state machine. + pub fn set_enable(&mut self, _sm: &mut StateMachine<'a, PIO, SM>, enable: bool) { + self.sm_enable_mask |= 1 << SM; + self.sm_enable |= (enable as u8) << SM; + } +} + +/// Type representing a PIO interrupt. +pub struct Irq<'d, PIO: Instance, const N: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const N: usize> Irq<'d, PIO, N> { + /// Wait for an IRQ to fire. + pub fn wait<'a>(&'a mut self) -> IrqFuture<'a, 'd, PIO> { + IrqFuture { + pio: PhantomData, + irq_no: N as u8, + } + } +} + +/// Interrupt flags for a PIO instance. +#[derive(Clone)] +pub struct IrqFlags<'d, PIO: Instance> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> IrqFlags<'d, PIO> { + /// Check if interrupt fired. + pub fn check(&self, irq_no: u8) -> bool { + assert!(irq_no < 8); + self.check_any(1 << irq_no) + } + + /// Check if any of the interrupts in the bitmap fired. + pub fn check_any(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs != 0 + } + + /// Check if all interrupts have fired. + pub fn check_all(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs == irqs + } + + /// Clear interrupt for interrupt number. + pub fn clear(&self, irq_no: usize) { + assert!(irq_no < 8); + self.clear_all(1 << irq_no); + } + + /// Clear all interrupts set in the bitmap. + pub fn clear_all(&self, irqs: u8) { + PIO::PIO.irq().write(|w| w.set_irq(irqs)) + } + + /// Fire a given interrupt. + pub fn set(&self, irq_no: usize) { + assert!(irq_no < 8); + self.set_all(1 << irq_no); + } + + /// Fire all interrupts. + pub fn set_all(&self, irqs: u8) { + PIO::PIO.irq_force().write(|w| w.set_irq_force(irqs)) + } +} + +/// An instance of the PIO driver. +pub struct Pio<'d, PIO: Instance> { + /// PIO handle. + pub common: Common<'d, PIO>, + /// PIO IRQ flags. + pub irq_flags: IrqFlags<'d, PIO>, + /// IRQ0 configuration. + pub irq0: Irq<'d, PIO, 0>, + /// IRQ1 configuration. + pub irq1: Irq<'d, PIO, 1>, + /// IRQ2 configuration. + pub irq2: Irq<'d, PIO, 2>, + /// IRQ3 configuration. + pub irq3: Irq<'d, PIO, 3>, + /// State machine 0 handle. + pub sm0: StateMachine<'d, PIO, 0>, + /// State machine 1 handle. + pub sm1: StateMachine<'d, PIO, 1>, + /// State machine 2 handle. + pub sm2: StateMachine<'d, PIO, 2>, + /// State machine 3 handle. + pub sm3: StateMachine<'d, PIO, 3>, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Pio<'d, PIO> { + /// Create a new instance of a PIO peripheral. + pub fn new(_pio: Peri<'d, PIO>, _irq: impl Binding>) -> Self { + PIO::state().users.store(5, Ordering::Release); + PIO::state().used_pins.store(0, Ordering::Release); + PIO::Interrupt::unpend(); + + unsafe { PIO::Interrupt::enable() }; + Self { + common: Common { + instructions_used: 0, + pio: PhantomData, + }, + irq_flags: IrqFlags { pio: PhantomData }, + irq0: Irq { pio: PhantomData }, + irq1: Irq { pio: PhantomData }, + irq2: Irq { pio: PhantomData }, + irq3: Irq { pio: PhantomData }, + sm0: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm1: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm2: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm3: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + _pio: PhantomData, + } + } +} + +struct AtomicU64 { + upper_32: AtomicU32, + lower_32: AtomicU32, +} + +impl AtomicU64 { + const fn new(val: u64) -> Self { + let upper_32 = (val >> 32) as u32; + let lower_32 = val as u32; + + Self { + upper_32: AtomicU32::new(upper_32), + lower_32: AtomicU32::new(lower_32), + } + } + + fn load(&self, order: Ordering) -> u64 { + let (upper, lower) = critical_section::with(|_| (self.upper_32.load(order), self.lower_32.load(order))); + + let upper = (upper as u64) << 32; + let lower = lower as u64; + + upper | lower + } + + fn store(&self, val: u64, order: Ordering) { + let upper_32 = (val >> 32) as u32; + let lower_32 = val as u32; + + critical_section::with(|_| { + self.upper_32.store(upper_32, order); + self.lower_32.store(lower_32, order); + }); + } +} + +/// Representation of the PIO state keeping a record of which pins are assigned to +/// each PIO. +// make_pio_pin notionally takes ownership of the pin it is given, but the wrapped pin +// cannot be treated as an owned resource since dropping it would have to deconfigure +// the pin, breaking running state machines in the process. pins are also shared +// between all state machines, which makes ownership even messier to track any +// other way. +pub struct State { + users: AtomicU8, + used_pins: AtomicU64, +} + +fn on_pio_drop() { + let state = PIO::state(); + let users_state = critical_section::with(|_| { + let val = state.users.load(Ordering::Acquire) - 1; + state.users.store(val, Ordering::Release); + val + }); + if users_state == 1 { + let used_pins = state.used_pins.load(Ordering::Relaxed); + let null = pac::io::vals::Gpio0ctrlFuncsel::NULL as _; + for i in 0..crate::gpio::BANK0_PIN_COUNT { + if used_pins & (1 << i) != 0 { + pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); + } + } + } +} + +trait SealedInstance { + const PIO_NO: u8; + const PIO: &'static crate::pac::pio::Pio; + const FUNCSEL: crate::pac::io::vals::Gpio0ctrlFuncsel; + + #[inline] + fn wakers() -> &'static Wakers { + static WAKERS: Wakers = Wakers([const { AtomicWaker::new() }; 12]); + &WAKERS + } + + #[inline] + fn state() -> &'static State { + static STATE: State = State { + users: AtomicU8::new(0), + used_pins: AtomicU64::new(0), + }; + + &STATE + } +} + +/// PIO instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + Sized + Unpin { + /// Interrupt for this peripheral. + type Interrupt: crate::interrupt::typelevel::Interrupt; +} + +macro_rules! impl_pio { + ($name:ident, $pio:expr, $pac:ident, $funcsel:ident, $irq:ident) => { + impl SealedInstance for peripherals::$name { + const PIO_NO: u8 = $pio; + const PIO: &'static pac::pio::Pio = &pac::$pac; + const FUNCSEL: pac::io::vals::Gpio0ctrlFuncsel = pac::io::vals::Gpio0ctrlFuncsel::$funcsel; + } + impl Instance for peripherals::$name { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0); +impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0); +#[cfg(feature = "_rp235x")] +impl_pio!(PIO2, 2, PIO2, PIO2_0, PIO2_IRQ_0); + +/// PIO pin. +pub trait PioPin: gpio::Pin {} + +macro_rules! impl_pio_pin { + ($( $pin:ident, )*) => { + $( + impl PioPin for peripherals::$pin {} + )* + }; +} + +impl_pio_pin! { + PIN_0, + PIN_1, + PIN_2, + PIN_3, + PIN_4, + PIN_5, + PIN_6, + PIN_7, + PIN_8, + PIN_9, + PIN_10, + PIN_11, + PIN_12, + PIN_13, + PIN_14, + PIN_15, + PIN_16, + PIN_17, + PIN_18, + PIN_19, + PIN_20, + PIN_21, + PIN_22, + PIN_23, + PIN_24, + PIN_25, + PIN_26, + PIN_27, + PIN_28, + PIN_29, +} + +#[cfg(feature = "rp235xb")] +impl_pio_pin! { + PIN_30, + PIN_31, + PIN_32, + PIN_33, + PIN_34, + PIN_35, + PIN_36, + PIN_37, + PIN_38, + PIN_39, + PIN_40, + PIN_41, + PIN_42, + PIN_43, + PIN_44, + PIN_45, + PIN_46, + PIN_47, +} diff --git a/embassy-rp-fork/src/pio_programs/clock_divider.rs b/embassy-rp-fork/src/pio_programs/clock_divider.rs new file mode 100644 index 0000000..02e353f --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/clock_divider.rs @@ -0,0 +1,25 @@ +//! Helper functions for calculating PIO clock dividers + +use fixed::traits::ToFixed; +use fixed::types::extra::U8; + +use crate::clocks::clk_sys_freq; + +/// Calculate a PIO clock divider value based on the desired target frequency. +/// +/// # Arguments +/// +/// * `target_hz` - The desired PIO clock frequency in Hz +/// +/// # Returns +/// +/// A fixed-point divider value suitable for use in a PIO state machine configuration +#[inline] +pub fn calculate_pio_clock_divider(target_hz: u32) -> fixed::FixedU32 { + // Requires a non-zero frequency + assert!(target_hz > 0, "PIO clock frequency cannot be zero"); + + // Calculate the divider + let divider = (clk_sys_freq() + target_hz / 2) / target_hz; + divider.to_fixed() +} diff --git a/embassy-rp-fork/src/pio_programs/hd44780.rs b/embassy-rp-fork/src/pio_programs/hd44780.rs new file mode 100644 index 0000000..78281dd --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/hd44780.rs @@ -0,0 +1,208 @@ +//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use crate::Peri; +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, + StateMachine, +}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; + +/// This struct represents a HD44780 program that takes command words ( <0:4>) +pub struct PioHD44780CommandWordProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandWordProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// This struct represents a HD44780 program that takes command sequences ( , data...) +pub struct PioHD44780CommandSequenceProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioHD44780CommandSequenceProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + // many side sets are only there to free up a delay bit! + let prg = pio::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed HD44780 driver +pub struct PioHD44780<'l, P: Instance, const S: usize> { + dma: Peri<'l, AnyChannel>, + sm: StateMachine<'l, P, S>, + + buf: [u8; 40], +} + +impl<'l, P: Instance, const S: usize> PioHD44780<'l, P, S> { + /// Configure the given state machine to first init, then write data to, a HD44780 display. + pub async fn new( + common: &mut Common<'l, P>, + mut sm: StateMachine<'l, P, S>, + mut irq: Irq<'l, P, S>, + mut dma: Peri<'l, impl Channel>, + rs: Peri<'l, impl PioPin>, + rw: Peri<'l, impl PioPin>, + e: Peri<'l, impl PioPin>, + db4: Peri<'l, impl PioPin>, + db5: Peri<'l, impl PioPin>, + db6: Peri<'l, impl PioPin>, + db7: Peri<'l, impl PioPin>, + word_prg: &PioHD44780CommandWordProgram<'l, P>, + seq_prg: &PioHD44780CommandSequenceProgram<'l, P>, + ) -> PioHD44780<'l, P, S> { + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let mut cfg = Config::default(); + cfg.use_program(&word_prg.prg, &[&e]); + + // Target 1 MHz PIO clock (each cycle is 1µs) + cfg.clock_divider = calculate_pio_clock_divider(1_000_000); + + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + // init to 8 bit thrice + sm.tx().push((50000 << 8) | 0x30); + sm.tx().push((5000 << 8) | 0x30); + sm.tx().push((200 << 8) | 0x30); + // init 4 bit + sm.tx().push((200 << 8) | 0x20); + // set font and lines + sm.tx().push((50 << 8) | 0x20); + sm.tx().push(0b1100_0000); + + irq.wait().await; + sm.set_enable(false); + + let mut cfg = Config::default(); + cfg.use_program(&seq_prg.prg, &[&e]); + + // Target ~15.6 MHz PIO clock (~64ns/insn) + cfg.clock_divider = calculate_pio_clock_divider(15_600_000); + + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm.set_config(&cfg); + + sm.set_enable(true); + + // display on and cursor on and blinking, reset display + sm.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1], false).await; + + Self { + dma: dma.into(), + sm, + buf: [0x20; 40], + } + } + + /// Write a line to the display + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf, false).await; + } +} diff --git a/embassy-rp-fork/src/pio_programs/i2s.rs b/embassy-rp-fork/src/pio_programs/i2s.rs new file mode 100644 index 0000000..5c49bee --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/i2s.rs @@ -0,0 +1,186 @@ +//! Pio backed I2S output and output drivers + +use fixed::traits::ToFixed; + +use crate::Peri; +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::gpio::Pull; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; + +/// This struct represents an I2S receiver & controller driver program +pub struct PioI2sInProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioI2sInProgram<'d, PIO> { + /// Load the input program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm! { + ".side_set 2", + " set x, 14 side 0b01", + "left_data:", + " in pins, 1 side 0b00", // read one left-channel bit from SD + " jmp x-- left_data side 0b01", + " in pins, 1 side 0b10", // ws changes 1 clock before MSB + " set x, 14 side 0b11", + "right_data:", + " in pins, 1 side 0b10", + " jmp x-- right_data side 0b11", + " in pins, 1 side 0b00" // ws changes 1 clock before ms + }; + let prg = common.load_program(&prg.program); + Self { prg } + } +} + +/// Pio backed I2S input driver +pub struct PioI2sIn<'d, P: Instance, const S: usize> { + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize> PioI2sIn<'d, P, S> { + /// Configure a state machine to act as both the controller (provider of SCK and WS) and receiver (of SD) for an I2S signal + pub fn new( + common: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + // Whether or not to use the MCU's internal pull-down resistor, as the + // Pico 2 is known to have problems with the inbuilt pulldowns, many + // opt to just use an external pull down resistor to meet requirements of common + // I2S microphones such as the INMP441 + data_pulldown: bool, + data_pin: Peri<'d, impl PioPin>, + bit_clock_pin: Peri<'d, impl PioPin>, + lr_clock_pin: Peri<'d, impl PioPin>, + sample_rate: u32, + bit_depth: u32, + channels: u32, + program: &PioI2sInProgram<'d, P>, + ) -> Self { + let mut data_pin = common.make_pio_pin(data_pin); + if data_pulldown { + data_pin.set_pull(Pull::Down); + } + let bit_clock_pin = common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); + cfg.set_in_pins(&[&data_pin]); + let clock_frequency = sample_rate * bit_depth * channels; + cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_in = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::RxOnly; // both control signals are sent via side-setting + cfg + }; + sm.set_config(&cfg); + sm.set_pin_dirs(Direction::In, &[&data_pin]); + sm.set_pin_dirs(Direction::Out, &[&left_right_clock_pin, &bit_clock_pin]); + sm.set_enable(true); + + Self { dma: dma.into(), sm } + } + + /// Return an in-progress dma transfer future. Awaiting it will guarantee a complete transfer. + pub fn read<'b>(&'b mut self, buff: &'b mut [u32]) -> Transfer<'b, AnyChannel> { + self.sm.rx().dma_pull(self.dma.reborrow(), buff, false) + } +} + +/// This struct represents an I2S output driver program +/// +/// The sample bit-depth is set through scratch register `Y`. +/// `Y` has to be set to sample bit-depth - 2. +/// (14 = 16bit, 22 = 24bit, 30 = 32bit) +pub struct PioI2sOutProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioI2sOutProgram<'d, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm!( + ".side_set 2", // side 0bWB - W = Word Clock, B = Bit Clock + " mov x, y side 0b01", // y stores sample depth - 2 (14 = 16bit, 22 = 24bit, 30 = 32bit) + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins, 1 side 0b10", + " mov x, y side 0b11", + "right_data:", + " out pins, 1 side 0b10", + " jmp x-- right_data side 0b11", + " out pins, 1 side 0b00", + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed I2S output driver +pub struct PioI2sOut<'d, P: Instance, const S: usize> { + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize> PioI2sOut<'d, P, S> { + /// Configure a state machine to output I2S + pub fn new( + common: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + data_pin: Peri<'d, impl PioPin>, + bit_clock_pin: Peri<'d, impl PioPin>, + lr_clock_pin: Peri<'d, impl PioPin>, + sample_rate: u32, + bit_depth: u32, + program: &PioI2sOutProgram<'d, P>, + ) -> Self { + let data_pin = common.make_pio_pin(data_pin); + let bit_clock_pin = common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = common.make_pio_pin(lr_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&bit_clock_pin, &left_right_clock_pin]); + cfg.set_out_pins(&[&data_pin]); + let clock_frequency = sample_rate * bit_depth * 2; + cfg.clock_divider = (crate::clocks::clk_sys_freq() as f64 / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_out = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::TxOnly; + cfg + }; + sm.set_config(&cfg); + sm.set_pin_dirs(Direction::Out, &[&data_pin, &left_right_clock_pin, &bit_clock_pin]); + + // Set the `y` register up to configure the sample depth + // The SM counts down to 0 and uses one clock cycle to set up the counter, + // which results in bit_depth - 2 as register value. + unsafe { sm.set_y(bit_depth - 2) }; + + sm.set_enable(true); + + Self { dma: dma.into(), sm } + } + + /// Return an in-progress dma transfer future. Awaiting it will guarantee a complete transfer. + pub fn write<'b>(&'b mut self, buff: &'b [u32]) -> Transfer<'b, AnyChannel> { + self.sm.tx().dma_push(self.dma.reborrow(), buff, false) + } +} diff --git a/embassy-rp-fork/src/pio_programs/mod.rs b/embassy-rp-fork/src/pio_programs/mod.rs new file mode 100644 index 0000000..d05ba38 --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/mod.rs @@ -0,0 +1,12 @@ +//! Pre-built pio programs for common interfaces + +pub mod clock_divider; +pub mod hd44780; +pub mod i2s; +pub mod onewire; +pub mod pwm; +pub mod rotary_encoder; +pub mod spi; +pub mod stepper; +pub mod uart; +pub mod ws2812; diff --git a/embassy-rp-fork/src/pio_programs/onewire.rs b/embassy-rp-fork/src/pio_programs/onewire.rs new file mode 100644 index 0000000..3adab3b --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/onewire.rs @@ -0,0 +1,343 @@ +//! OneWire pio driver + +use crate::Peri; +use crate::clocks::clk_sys_freq; +use crate::gpio::Level; +use crate::pio::{ + Common, Config, Direction, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; + +/// This struct represents a onewire driver program +pub struct PioOneWireProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, + reset_addr: u8, + next_bit_addr: u8, +} + +impl<'a, PIO: Instance> PioOneWireProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + ; We need to use the pins direction to simulate open drain output + ; This results in all the side-set values being swapped from the actual pin value + .side_set 1 pindirs + + ; Set the origin to 0 so we can correctly use jmp instructions externally + .origin 0 + + ; Tick rate is 1 tick per 6us, so all delays should be calculated back to that + ; All the instructions have a calculated delay XX in us as [(XX / CLK) - 1]. + ; The - 1 is for the instruction which also takes one clock cyle. + ; The delay can be 0 which will result in just 6us for the instruction itself + .define CLK 6 + + ; Write the reset block after trigger + public reset: + set x, 4 side 0 [(60 / CLK) - 1] ; idle before reset + reset_inner: ; Repeat the following 5 times, so 5*96us = 480us in total + nop side 1 [(90 / CLK) - 1] + jmp x--, reset_inner side 1 [( 6 / CLK) - 1] + ; Fallthrough + + ; Check for presence of one or more devices. + ; This samples 32 times with an interval of 12us after a 18us delay. + ; If any bit is zero in the end value, there is a detection + ; This whole function takes 480us + set x, 31 side 0 [(24 / CLK) - 1] ; Loop 32 times -> 32*12us = 384us + presence_check: + in pins, 1 side 0 [( 6 / CLK) - 1] ; poll pin and push to isr + jmp x--, presence_check side 0 [( 6 / CLK) - 1] + jmp next_bit side 0 [(72 / CLK) - 1] + + ; The low pulse was already done, we only need to delay and poll the bit in case we are reading + write_1: + jmp y--, continue_1 side 0 [( 6 / CLK) - 1] ; Delay before sampling input. Always decrement y + continue_1: + in pins, 1 side 0 [(48 / CLK) - 1] ; This writes the state of the pin into the ISR + ; Fallthrough + + ; This is the entry point when reading and writing data + public next_bit: + .wrap_target + out x, 1 side 0 [(12 / CLK) - 1] ; Stalls if no data available in TX FIFO and OSR + jmp x--, write_1 side 1 [( 6 / CLK) - 1] ; Do the always low part of a bit, jump to write_1 if we want to write a 1 bit + jmp y--, continue_0 side 1 [(48 / CLK) - 1] ; Do the remainder of the low part of a 0 bit + jmp pullup side 1 [( 6 / CLK) - 1] ; Remain low while jumping + continue_0: + in null, 1 side 1 [( 6 / CLK) - 1] ; This writes 0 into the ISR so that the shift count stays in sync + .wrap + + ; Assume that strong pullup commands always have MSB (the last bit) = 0, + ; since the rising edge can be used to start the operation. + ; That's the case for DS18B20 (44h and 48h). + pullup: + set pins, 1 side 1[( 6 / CLK) - 1] ; Drive pin high output immediately. + ; Strong pullup must be within 10us of rise. + in null, 1 side 1[( 6 / CLK) - 1] ; Keep ISR in sync. Must occur after the y--. + out null, 8 side 1[( 6 / CLK) - 1] ; Wait for write_bytes_pullup() delay to complete. + ; The delay is hundreds of ms, so done externally. + set pins, 0 side 0[( 6 / CLK) - 1] ; Back to open drain, pin low when driven + in null, 8 side 1[( 6 / CLK) - 1] ; Inform write_bytes_pullup() it's ready + jmp next_bit side 0[( 6 / CLK) - 1] ; Continue + "# + ); + + Self { + prg: common.load_program(&prg.program), + reset_addr: prg.public_defines.reset as u8, + next_bit_addr: prg.public_defines.next_bit as u8, + } + } +} +/// Pio backed OneWire driver +pub struct PioOneWire<'d, PIO: Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, + cfg: Config<'d, PIO>, + reset_addr: u8, + next_bit_addr: u8, +} + +impl<'d, PIO: Instance, const SM: usize> PioOneWire<'d, PIO, SM> { + /// Create a new instance the driver + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + pin: Peri<'d, impl PioPin>, + program: &PioOneWireProgram<'d, PIO>, + ) -> Self { + let pin = common.make_pio_pin(pin); + + sm.set_pin_dirs(Direction::In, &[&pin]); + sm.set_pins(Level::Low, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + + let shift_cfg = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.shift_in = shift_cfg; + cfg.shift_out = shift_cfg; + + let divider = (clk_sys_freq() / 1000000) as u16 * 6; + cfg.clock_divider = divider.into(); + + sm.set_config(&cfg); + sm.clear_fifos(); + sm.restart(); + unsafe { + sm.exec_jmp(program.next_bit_addr); + } + sm.set_enable(true); + + Self { + sm, + cfg, + reset_addr: program.reset_addr, + next_bit_addr: program.next_bit_addr, + } + } + + /// Perform an initialization sequence, will return true if a presence pulse was detected from a device + pub async fn reset(&mut self) -> bool { + // The state machine immediately starts running when jumping to this address + unsafe { + self.sm.exec_jmp(self.reset_addr); + } + + let rx = self.sm.rx(); + let mut found = false; + for _ in 0..4 { + if rx.wait_pull().await != 0 { + found = true; + } + } + + found + } + + /// Write bytes to the onewire bus + pub async fn write_bytes(&mut self, data: &[u8]) { + unsafe { + self.sm.set_enable(false); + self.sm.set_y(u32::MAX as u32); + self.sm.set_enable(true); + } + let (rx, tx) = self.sm.rx_tx(); + for b in data { + tx.wait_push(*b as u32).await; + + // Empty the buffer that is being filled with every write + let _ = rx.wait_pull().await; + } + } + + /// Write bytes to the onewire bus, then apply a strong pullup + pub async fn write_bytes_pullup(&mut self, data: &[u8], pullup_time: embassy_time::Duration) { + unsafe { + self.sm.set_enable(false); + self.sm.set_y(data.len() as u32 * 8 - 1); + self.sm.set_enable(true); + }; + let (rx, tx) = self.sm.rx_tx(); + for b in data { + tx.wait_push(*b as u32).await; + + // Empty the buffer that is being filled with every write + let _ = rx.wait_pull().await; + } + + // Perform the delay, usually hundreds of ms. + embassy_time::Timer::after(pullup_time).await; + + // Signal that delay has completed + tx.wait_push(0 as u32).await; + // Wait until it's back at 0 low, open drain + let _ = rx.wait_pull().await; + } + + /// Read bytes from the onewire bus + pub async fn read_bytes(&mut self, data: &mut [u8]) { + unsafe { + self.sm.set_enable(false); + self.sm.set_y(u32::MAX as u32); + self.sm.set_enable(true); + }; + let (rx, tx) = self.sm.rx_tx(); + for b in data { + // Write all 1's so that we can read what the device responds + tx.wait_push(0xff).await; + + *b = (rx.wait_pull().await >> 24) as u8; + } + } + + async fn search(&mut self, state: &mut PioOneWireSearch) -> Option { + if !self.reset().await { + // No device present, no use in searching + state.finished = true; + return None; + } + self.write_bytes(&[0xF0]).await; // 0xF0 is the search rom command + + self.prepare_search(); + + let (rx, tx) = self.sm.rx_tx(); + + let mut value = 0; + let mut last_zero = 0; + + for bit in 0..64 { + // Write 2 dummy bits to read a bit and its complement + tx.wait_push(0x1).await; + tx.wait_push(0x1).await; + let in1 = rx.wait_pull().await; + let in2 = rx.wait_pull().await; + let push = match (in1, in2) { + (0, 0) => { + // If both are 0, it means we have devices with 0 and 1 bits in this position + let write_value = if bit < state.last_discrepancy { + (state.last_rom & (1 << bit)) != 0 + } else { + bit == state.last_discrepancy + }; + + if write_value { + 1 + } else { + last_zero = bit; + 0 + } + } + (0, 1) => 0, // Only devices with a 0 bit in this position + (1, 0) => 1, // Only devices with a 1 bit in this position + _ => { + // If both are 1, it means there is no device active and there is no point in continuing + self.restore_after_search(); + state.finished = true; + return None; + } + }; + value >>= 1; + if push == 1 { + value |= 1 << 63; + } + tx.wait_push(push).await; + let _ = rx.wait_pull().await; // Discard the result of the write action + } + + self.restore_after_search(); + + state.last_discrepancy = last_zero; + state.finished = last_zero == 0; + state.last_rom = value; + Some(value) + } + + fn prepare_search(&mut self) { + self.cfg.shift_in.threshold = 1; + self.cfg.shift_in.direction = ShiftDirection::Left; + self.cfg.shift_out.threshold = 1; + + self.sm.set_enable(false); + self.sm.set_config(&self.cfg); + + // set_config jumps to the wrong address so jump to the right one here + unsafe { + self.sm.exec_jmp(self.next_bit_addr); + } + self.sm.set_enable(true); + } + + fn restore_after_search(&mut self) { + self.cfg.shift_in.threshold = 8; + self.cfg.shift_in.direction = ShiftDirection::Right; + self.cfg.shift_out.threshold = 8; + + self.sm.set_enable(false); + self.sm.set_config(&self.cfg); + + // Clear the state in case we aborted prematurely with some bits still in the shift registers + self.sm.clear_fifos(); + self.sm.restart(); + + // set_config jumps to the wrong address so jump to the right one here + unsafe { + self.sm.exec_jmp(self.next_bit_addr); + } + self.sm.set_enable(true); + } +} + +/// Onewire search state +pub struct PioOneWireSearch { + last_rom: u64, + last_discrepancy: u8, + finished: bool, +} + +impl PioOneWireSearch { + /// Create a new Onewire search state + pub fn new() -> Self { + Self { + last_rom: 0, + last_discrepancy: 0, + finished: false, + } + } + + /// Search for the next address on the bus + pub async fn next(&mut self, pio: &mut PioOneWire<'_, PIO, SM>) -> Option { + if self.finished { None } else { pio.search(self).await } + } + + /// Is finished when all devices have been found + pub fn is_finished(&self) -> bool { + self.finished + } +} diff --git a/embassy-rp-fork/src/pio_programs/pwm.rs b/embassy-rp-fork/src/pio_programs/pwm.rs new file mode 100644 index 0000000..e4ad4a6 --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/pwm.rs @@ -0,0 +1,121 @@ +//! PIO backed PWM driver + +use core::time::Duration; + +use pio::InstructionOperands; + +use crate::gpio::Level; +use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine}; +use crate::{Peri, clocks}; + +/// This converts the duration provided into the number of cycles the PIO needs to run to make it take the same time +fn to_pio_cycles(duration: Duration) -> u32 { + (clocks::clk_sys_freq() / 1_000_000) / 3 * duration.as_micros() as u32 // parentheses are required to prevent overflow +} + +/// This struct represents a PWM program loaded into pio instruction memory. +pub struct PioPwmProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioPwmProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + ".side_set 1 opt" + "pull noblock side 0" + "mov x, osr" + "mov y, isr" + "countloop:" + "jmp x!=y noset" + "jmp skip side 1" + "noset:" + "nop" + "skip:" + "jmp y-- countloop" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed PWM output +pub struct PioPwm<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, + pin: Pin<'d, T>, +} + +impl<'d, T: Instance, const SM: usize> PioPwm<'d, T, SM> { + /// Configure a state machine as a PWM output + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin: Peri<'d, impl PioPin>, + program: &PioPwmProgram<'d, T>, + ) -> Self { + let pin = pio.make_pio_pin(pin); + sm.set_pins(Level::High, &[&pin]); + sm.set_pin_dirs(Direction::Out, &[&pin]); + + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[&pin]); + + sm.set_config(&cfg); + + Self { sm, pin } + } + + /// Enables the PIO program, continuing the wave generation from the PIO program. + pub fn start(&mut self) { + self.sm.set_enable(true); + } + + /// Stops the PIO program, ceasing all signals from the PIN that were generated via PIO. + pub fn stop(&mut self) { + self.sm.set_enable(false); + } + + /// Sets the pwm period, which is the length of time for each pio wave until reset. + pub fn set_period(&mut self, duration: Duration) { + let is_enabled = self.sm.is_enabled(); + while !self.sm.tx().empty() {} // Make sure that the queue is empty + self.sm.set_enable(false); + self.sm.tx().push(to_pio_cycles(duration)); + unsafe { + self.sm.exec_instr( + InstructionOperands::PULL { + if_empty: false, + block: false, + } + .encode(), + ); + self.sm.exec_instr( + InstructionOperands::OUT { + destination: ::pio::OutDestination::ISR, + bit_count: 32, + } + .encode(), + ); + }; + if is_enabled { + self.sm.set_enable(true) // Enable if previously enabled + } + } + + /// Set the number of pio cycles to set the wave on high to. + pub fn set_level(&mut self, level: u32) { + self.sm.tx().push(level); + } + + /// Set the pulse width high time + pub fn write(&mut self, duration: Duration) { + self.set_level(to_pio_cycles(duration)); + } + + /// Return the state machine and pin. + pub fn release(self) -> (StateMachine<'d, T, SM>, Pin<'d, T>) { + (self.sm, self.pin) + } +} diff --git a/embassy-rp-fork/src/pio_programs/rotary_encoder.rs b/embassy-rp-fork/src/pio_programs/rotary_encoder.rs new file mode 100644 index 0000000..6347527 --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/rotary_encoder.rs @@ -0,0 +1,78 @@ +//! PIO backed quadrature encoder + +use crate::Peri; +use crate::gpio::Pull; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; + +/// This struct represents an Encoder program loaded into pio instruction memory. +pub struct PioEncoderProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioEncoderProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!("wait 1 pin 1", "wait 0 pin 1", "in pins, 2", "push",); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio Backed quadrature encoder reader +pub struct PioEncoder<'d, T: Instance, const SM: usize> { + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioEncoder<'d, T, SM> { + /// Configure a state machine with the loaded [PioEncoderProgram] + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + pin_a: Peri<'d, impl PioPin>, + pin_b: Peri<'d, impl PioPin>, + program: &PioEncoderProgram<'d, T>, + ) -> Self { + let mut pin_a = pio.make_pio_pin(pin_a); + let mut pin_b = pio.make_pio_pin(pin_b); + pin_a.set_pull(Pull::Up); + pin_b.set_pull(Pull::Up); + sm.set_pin_dirs(PioDirection::In, &[&pin_a, &pin_b]); + + let mut cfg = Config::default(); + cfg.set_in_pins(&[&pin_a, &pin_b]); + cfg.fifo_join = FifoJoin::RxOnly; + cfg.shift_in.direction = ShiftDirection::Left; + + // Target 12.5 KHz PIO clock + cfg.clock_divider = calculate_pio_clock_divider(12_500); + + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Read a single count from the encoder + pub async fn read(&mut self) -> Direction { + loop { + match self.sm.rx().wait_pull().await { + 0 => return Direction::CounterClockwise, + 1 => return Direction::Clockwise, + _ => {} + } + } + } +} + +/// Encoder Count Direction +pub enum Direction { + /// Encoder turned clockwise + Clockwise, + /// Encoder turned counter clockwise + CounterClockwise, +} diff --git a/embassy-rp-fork/src/pio_programs/spi.rs b/embassy-rp-fork/src/pio_programs/spi.rs new file mode 100644 index 0000000..765ffaa --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/spi.rs @@ -0,0 +1,474 @@ +//! PIO backed SPI drivers + +use core::marker::PhantomData; + +use embassy_futures::join::join; +use embassy_hal_internal::Peri; +use embedded_hal_02::spi::{Phase, Polarity}; +use fixed::traits::ToFixed; +use fixed::types::extra::U8; + +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::Level; +use crate::pio::{Common, Direction, Instance, LoadedProgram, Pin, PioPin, ShiftDirection, StateMachine}; +use crate::spi::{Async, Blocking, Config, Mode}; + +/// This struct represents an SPI program loaded into pio instruction memory. +struct PioSpiProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, + phase: Phase, +} + +impl<'d, PIO: Instance> PioSpiProgram<'d, PIO> { + /// Load the spi program into the given pio + pub fn new(common: &mut Common<'d, PIO>, phase: Phase) -> Self { + // These PIO programs are taken straight from the datasheet (3.6.1 in + // RP2040 datasheet, 11.6.1 in RP2350 datasheet) + + // Pin assignments: + // - SCK is side-set pin 0 + // - MOSI is OUT pin 0 + // - MISO is IN pin 0 + // + // Auto-push and auto-pull must be enabled, and the serial frame size is set by + // configuring the push/pull threshold. Shift left/right is fine, but you must + // justify the data yourself. This is done most conveniently for frame sizes of + // 8 or 16 bits by using the narrow store replication and narrow load byte + // picking behavior of RP2040's IO fabric. + + let prg = match phase { + Phase::CaptureOnFirstTransition => { + let prg = pio::pio_asm!( + r#" + .side_set 1 + + ; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and + ; transitions on the trailing edge, or some time before the first leading edge. + + out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if + in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low) + "# + ); + + common.load_program(&prg.program) + } + Phase::CaptureOnSecondTransition => { + let prg = pio::pio_asm!( + r#" + .side_set 1 + + ; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and + ; is captured on the trailing edge. + + out x, 1 side 0 ; Stall here on empty (keep SCK de-asserted) + mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping) + in pins, 1 side 0 ; Input data, de-assert SCK + "# + ); + + common.load_program(&prg.program) + } + }; + + Self { prg, phase } + } +} + +/// PIO SPI errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +/// PIO based SPI driver. +/// Unlike other PIO programs, the PIO SPI driver owns and holds a reference to +/// the PIO memory it uses. This is so that it can be reconfigured at runtime if +/// desired. +pub struct Spi<'d, PIO: Instance, const SM: usize, M: Mode> { + sm: StateMachine<'d, PIO, SM>, + cfg: crate::pio::Config<'d, PIO>, + program: Option>, + clk_pin: Pin<'d, PIO>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData, +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> Spi<'d, PIO, SM, M> { + #[allow(clippy::too_many_arguments)] + fn new_inner( + pio: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + clk_pin: Peri<'d, impl PioPin>, + mosi_pin: Peri<'d, impl PioPin>, + miso_pin: Peri<'d, impl PioPin>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + let program = PioSpiProgram::new(pio, config.phase); + + let mut clk_pin = pio.make_pio_pin(clk_pin); + let mosi_pin = pio.make_pio_pin(mosi_pin); + let miso_pin = pio.make_pio_pin(miso_pin); + + if let Polarity::IdleHigh = config.polarity { + clk_pin.set_output_inversion(true); + } else { + clk_pin.set_output_inversion(false); + } + + let mut cfg = crate::pio::Config::default(); + + cfg.use_program(&program.prg, &[&clk_pin]); + cfg.set_out_pins(&[&mosi_pin]); + cfg.set_in_pins(&[&miso_pin]); + + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.threshold = 8; + + cfg.shift_out.auto_fill = true; + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.threshold = 8; + + cfg.clock_divider = calculate_clock_divider(config.frequency); + + sm.set_config(&cfg); + + sm.set_pins(Level::Low, &[&clk_pin, &mosi_pin]); + sm.set_pin_dirs(Direction::Out, &[&clk_pin, &mosi_pin]); + sm.set_pin_dirs(Direction::In, &[&miso_pin]); + + sm.set_enable(true); + + Self { + sm, + program: Some(program), + cfg, + clk_pin, + tx_dma, + rx_dma, + phantom: PhantomData, + } + } + + fn blocking_read_u8(&mut self) -> Result { + while self.sm.rx().empty() {} + let value = self.sm.rx().pull() as u8; + + Ok(value) + } + + fn blocking_write_u8(&mut self, v: u8) -> Result<(), Error> { + let value = u32::from_be_bytes([v, 0, 0, 0]); + + while !self.sm.tx().try_push(value) {} + + // need to clear here for flush to work correctly + self.sm.tx().stalled(); + + Ok(()) + } + + /// Read data from SPI blocking execution until done. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { + for v in data { + self.blocking_write_u8(0)?; + *v = self.blocking_read_u8()?; + } + self.flush()?; + Ok(()) + } + + /// Write data to SPI blocking execution until done. + pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { + for v in data { + self.blocking_write_u8(*v)?; + let _ = self.blocking_read_u8()?; + } + self.flush()?; + Ok(()) + } + + /// Transfer data to SPI blocking execution until done. + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + let len = read.len().max(write.len()); + for i in 0..len { + let wb = write.get(i).copied().unwrap_or(0); + self.blocking_write_u8(wb)?; + + let rb = self.blocking_read_u8()?; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + self.flush()?; + Ok(()) + } + + /// Transfer data in place to SPI blocking execution until done. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + for v in data { + self.blocking_write_u8(*v)?; + *v = self.blocking_read_u8()?; + } + self.flush()?; + Ok(()) + } + + /// Block execution until SPI is done. + pub fn flush(&mut self) -> Result<(), Error> { + // Wait for all words in the FIFO to have been pulled by the SM + while !self.sm.tx().empty() {} + + // Wait for last value to be written out to the wire + while !self.sm.tx().stalled() {} + + Ok(()) + } + + /// Set SPI frequency. + pub fn set_frequency(&mut self, freq: u32) { + self.sm.set_enable(false); + + let divider = calculate_clock_divider(freq); + + // save into the config for later but dont use sm.set_config() since + // that operation is relatively more expensive than just setting the + // clock divider + self.cfg.clock_divider = divider; + self.sm.set_clock_divider(divider); + + self.sm.set_enable(true); + } + + /// Set SPI config. + /// + /// This operation will panic if the PIO program needs to be reloaded and + /// there is insufficient room. This is unlikely since the programs for each + /// phase only differ in size by a single instruction. + pub fn set_config(&mut self, pio: &mut Common<'d, PIO>, config: &Config) { + self.sm.set_enable(false); + + self.cfg.clock_divider = calculate_clock_divider(config.frequency); + + if let Polarity::IdleHigh = config.polarity { + self.clk_pin.set_output_inversion(true); + } else { + self.clk_pin.set_output_inversion(false); + } + + if self.program.as_ref().unwrap().phase != config.phase { + let old_program = self.program.take().unwrap(); + + // SAFETY: the state machine is disabled while this happens + unsafe { pio.free_instr(old_program.prg.used_memory) }; + + let new_program = PioSpiProgram::new(pio, config.phase); + + self.cfg.use_program(&new_program.prg, &[&self.clk_pin]); + self.program = Some(new_program); + } + + self.sm.set_config(&self.cfg); + self.sm.restart(); + + self.sm.set_enable(true); + } +} + +fn calculate_clock_divider(frequency_hz: u32) -> fixed::FixedU32 { + // we multiply by 4 since each clock period is equal to 4 instructions + + let sys_freq = clk_sys_freq().to_fixed::>(); + let target_freq = (frequency_hz * 4).to_fixed::>(); + (sys_freq / target_freq).to_fixed() +} + +impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Blocking> { + /// Create an SPI driver in blocking mode. + pub fn new_blocking( + pio: &mut Common<'d, PIO>, + sm: StateMachine<'d, PIO, SM>, + clk: Peri<'d, impl PioPin>, + mosi: Peri<'d, impl PioPin>, + miso: Peri<'d, impl PioPin>, + config: Config, + ) -> Self { + Self::new_inner(pio, sm, clk, mosi, miso, None, None, config) + } +} + +impl<'d, PIO: Instance, const SM: usize> Spi<'d, PIO, SM, Async> { + /// Create an SPI driver in async mode supporting DMA operations. + #[allow(clippy::too_many_arguments)] + pub fn new( + pio: &mut Common<'d, PIO>, + sm: StateMachine<'d, PIO, SM>, + clk: Peri<'d, impl PioPin>, + mosi: Peri<'d, impl PioPin>, + miso: Peri<'d, impl PioPin>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + pio, + sm, + clk, + mosi, + miso, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) + } + + /// Read data from SPI using DMA. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + let (rx, tx) = self.sm.rx_tx(); + + let len = buffer.len(); + + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = rx.dma_pull(rx_ch, buffer, false); + + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = tx.dma_push_repeated::<_, u8>(tx_ch, len); + + join(tx_transfer, rx_transfer).await; + + Ok(()) + } + + /// Write data to SPI using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let (rx, tx) = self.sm.rx_tx(); + + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = rx.dma_pull_repeated::<_, u8>(rx_ch, buffer.len()); + + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = tx.dma_push(tx_ch, buffer, false); + + join(tx_transfer, rx_transfer).await; + + Ok(()) + } + + /// Transfer data to SPI using DMA. + pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> { + self.transfer_inner(rx_buffer, tx_buffer).await + } + + /// Transfer data in place to SPI using DMA. + pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_inner(words, words).await + } + + async fn transfer_inner(&mut self, rx_buffer: *mut [u8], tx_buffer: *const [u8]) -> Result<(), Error> { + let (rx, tx) = self.sm.rx_tx(); + + let mut rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = async { + rx.dma_pull(rx_ch.reborrow(), unsafe { &mut *rx_buffer }, false).await; + + if tx_buffer.len() > rx_buffer.len() { + let read_bytes_len = tx_buffer.len() - rx_buffer.len(); + + rx.dma_pull_repeated::<_, u8>(rx_ch, read_bytes_len).await; + } + }; + + let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = async { + tx.dma_push(tx_ch.reborrow(), unsafe { &*tx_buffer }, false).await; + + if rx_buffer.len() > tx_buffer.len() { + let write_bytes_len = rx_buffer.len() - tx_buffer.len(); + + tx.dma_push_repeated::<_, u8>(tx_ch, write_bytes_len).await; + } + }; + + join(tx_transfer, rx_transfer).await; + + Ok(()) + } +} + +// ==================== + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, PIO, SM, M> { + type Error = Error; + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.blocking_transfer_in_place(words)?; + Ok(words) + } +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_02::blocking::spi::Write for Spi<'d, PIO, SM, M> { + type Error = Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } +} + +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self {} + } +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, PIO, SM, M> { + type Error = Error; +} + +impl<'d, PIO: Instance, const SM: usize, M: Mode> embedded_hal_1::spi::SpiBus for Spi<'d, PIO, SM, M> { + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer(words, &[]) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.blocking_transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer_in_place(words) + } +} + +impl<'d, PIO: Instance, const SM: usize> embedded_hal_async::spi::SpiBus for Spi<'d, PIO, SM, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await + } +} diff --git a/embassy-rp-fork/src/pio_programs/stepper.rs b/embassy-rp-fork/src/pio_programs/stepper.rs new file mode 100644 index 0000000..5762ee1 --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/stepper.rs @@ -0,0 +1,149 @@ +//! Pio Stepper Driver for 5-wire steppers + +use core::mem::{self, MaybeUninit}; + +use crate::Peri; +use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; +use crate::pio_programs::clock_divider::calculate_pio_clock_divider; + +/// This struct represents a Stepper driver program loaded into pio instruction memory. +pub struct PioStepperProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioStepperProgram<'a, PIO> { + /// Load the program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let prg = pio::pio_asm!( + "pull block", + "mov x, osr", + "pull block", + "mov y, osr", + "jmp !x end", + "loop:", + "jmp !osre step", + "mov osr, y", + "step:", + "out pins, 4 [31]" + "jmp x-- loop", + "end:", + "irq 0 rel" + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed Stepper driver +pub struct PioStepper<'d, T: Instance, const SM: usize> { + irq: Irq<'d, T, SM>, + sm: StateMachine<'d, T, SM>, +} + +impl<'d, T: Instance, const SM: usize> PioStepper<'d, T, SM> { + /// Configure a state machine to drive a stepper + pub fn new( + pio: &mut Common<'d, T>, + mut sm: StateMachine<'d, T, SM>, + irq: Irq<'d, T, SM>, + pin0: Peri<'d, impl PioPin>, + pin1: Peri<'d, impl PioPin>, + pin2: Peri<'d, impl PioPin>, + pin3: Peri<'d, impl PioPin>, + program: &PioStepperProgram<'d, T>, + ) -> Self { + let pin0 = pio.make_pio_pin(pin0); + let pin1 = pio.make_pio_pin(pin1); + let pin2 = pio.make_pio_pin(pin2); + let pin3 = pio.make_pio_pin(pin3); + sm.set_pin_dirs(Direction::Out, &[&pin0, &pin1, &pin2, &pin3]); + let mut cfg = Config::default(); + cfg.set_out_pins(&[&pin0, &pin1, &pin2, &pin3]); + + cfg.clock_divider = calculate_pio_clock_divider(100 * 136); + + cfg.use_program(&program.prg, &[]); + sm.set_config(&cfg); + sm.set_enable(true); + Self { irq, sm } + } + + /// Set pulse frequency + pub fn set_frequency(&mut self, freq: u32) { + let clock_divider = calculate_pio_clock_divider(freq * 136); + let divider_f32 = clock_divider.to_num::(); + assert!(divider_f32 <= 65536.0, "clkdiv must be <= 65536"); + assert!(divider_f32 >= 1.0, "clkdiv must be >= 1"); + + self.sm.set_clock_divider(clock_divider); + self.sm.clkdiv_restart(); + } + + /// Full step, one phase + pub async fn step(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1000_0100_0010_0001_1000_0100_0010_0001).await + } else { + self.run(-steps, 0b0001_0010_0100_1000_0001_0010_0100_1000).await + } + } + + /// Full step, two phase + pub async fn step2(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1100_0110_0011_1001_1100_0110_0011).await + } else { + self.run(-steps, 0b0011_0110_1100_1001_0011_0110_1100_1001).await + } + } + + /// Half step + pub async fn step_half(&mut self, steps: i32) { + if steps > 0 { + self.run(steps, 0b1001_1000_1100_0100_0110_0010_0011_0001).await + } else { + self.run(-steps, 0b0001_0011_0010_0110_0100_1100_1000_1001).await + } + } + + async fn run(&mut self, steps: i32, pattern: u32) { + self.sm.tx().wait_push(steps as u32).await; + self.sm.tx().wait_push(pattern).await; + let drop = OnDrop::new(|| { + self.sm.clear_fifos(); + unsafe { + self.sm.exec_instr( + pio::InstructionOperands::JMP { + address: 0, + condition: pio::JmpCondition::Always, + } + .encode(), + ); + } + }); + self.irq.wait().await; + drop.defuse(); + } +} + +struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} diff --git a/embassy-rp-fork/src/pio_programs/uart.rs b/embassy-rp-fork/src/pio_programs/uart.rs new file mode 100644 index 0000000..d59596d --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/uart.rs @@ -0,0 +1,186 @@ +//! Pio backed uart drivers + +use core::convert::Infallible; + +use embedded_io_async::{ErrorType, Read, Write}; +use fixed::traits::ToFixed; + +use crate::Peri; +use crate::clocks::clk_sys_freq; +use crate::gpio::Level; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; + +/// This struct represents a uart tx program loaded into pio instruction memory. +pub struct PioUartTxProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioUartTxProgram<'d, PIO> { + /// Load the uart tx program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + .side_set 1 opt + + ; An 8n1 UART transmit program. + ; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin. + + pull side 1 [7] ; Assert stop bit, or stall with line in idle state + set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks + bitloop: ; This loop will run 8 times (8n1 UART) + out pins, 1 ; Shift 1 bit from OSR to the first OUT pin + jmp x-- bitloop [6] ; Each loop iteration is 8 cycles. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart transmitter +pub struct PioUartTx<'d, PIO: Instance, const SM: usize> { + sm_tx: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> PioUartTx<'d, PIO, SM> { + /// Configure a pio state machine to use the loaded tx program. + pub fn new( + baud: u32, + common: &mut Common<'d, PIO>, + mut sm_tx: StateMachine<'d, PIO, SM>, + tx_pin: Peri<'d, impl PioPin>, + program: &PioUartTxProgram<'d, PIO>, + ) -> Self { + let tx_pin = common.make_pio_pin(tx_pin); + sm_tx.set_pins(Level::High, &[&tx_pin]); + sm_tx.set_pin_dirs(PioDirection::Out, &[&tx_pin]); + + let mut cfg = Config::default(); + + cfg.set_out_pins(&[&tx_pin]); + cfg.use_program(&program.prg, &[&tx_pin]); + cfg.shift_out.auto_fill = false; + cfg.shift_out.direction = ShiftDirection::Right; + cfg.fifo_join = FifoJoin::TxOnly; + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + sm_tx.set_config(&cfg); + sm_tx.set_enable(true); + + Self { sm_tx } + } + + /// Write a single u8 + pub async fn write_u8(&mut self, data: u8) { + self.sm_tx.tx().wait_push(data as u32).await; + } +} + +impl ErrorType for PioUartTx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Write for PioUartTx<'_, PIO, SM> { + async fn write(&mut self, buf: &[u8]) -> Result { + for byte in buf { + self.write_u8(*byte).await; + } + Ok(buf.len()) + } +} + +/// This struct represents a Uart Rx program loaded into pio instruction memory. +pub struct PioUartRxProgram<'d, PIO: Instance> { + prg: LoadedProgram<'d, PIO>, +} + +impl<'d, PIO: Instance> PioUartRxProgram<'d, PIO> { + /// Load the uart rx program into the given pio + pub fn new(common: &mut Common<'d, PIO>) -> Self { + let prg = pio::pio_asm!( + r#" + ; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and + ; break conditions more gracefully. + ; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX. + + start: + wait 0 pin 0 ; Stall until start bit is asserted + set x, 7 [10] ; Preload bit counter, then delay until halfway through + rx_bitloop: ; the first data bit (12 cycles incl wait, set). + in pins, 1 ; Shift data bit into ISR + jmp x-- rx_bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles + jmp pin good_rx_stop ; Check stop bit (should be high) + + irq 4 rel ; Either a framing error or a break. Set a sticky flag, + wait 1 pin 0 ; and wait for line to return to idle state. + jmp start ; Don't push data if we didn't see good framing. + + good_rx_stop: ; No delay before returning to start; a little slack is + in null 24 + push ; important in case the TX clock is slightly too fast. + "# + ); + + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// PIO backed Uart receiver +pub struct PioUartRx<'d, PIO: Instance, const SM: usize> { + sm_rx: StateMachine<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> PioUartRx<'d, PIO, SM> { + /// Configure a pio state machine to use the loaded rx program. + pub fn new( + baud: u32, + common: &mut Common<'d, PIO>, + mut sm_rx: StateMachine<'d, PIO, SM>, + rx_pin: Peri<'d, impl PioPin>, + program: &PioUartRxProgram<'d, PIO>, + ) -> Self { + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + + let rx_pin = common.make_pio_pin(rx_pin); + sm_rx.set_pins(Level::High, &[&rx_pin]); + cfg.set_in_pins(&[&rx_pin]); + cfg.set_jmp_pin(&rx_pin); + sm_rx.set_pin_dirs(PioDirection::In, &[&rx_pin]); + + cfg.clock_divider = (clk_sys_freq() / (8 * baud)).to_fixed(); + cfg.shift_in.auto_fill = false; + cfg.shift_in.direction = ShiftDirection::Right; + cfg.shift_in.threshold = 32; + cfg.fifo_join = FifoJoin::RxOnly; + sm_rx.set_config(&cfg); + sm_rx.set_enable(true); + + Self { sm_rx } + } + + /// Wait for a single u8 + pub async fn read_u8(&mut self) -> u8 { + self.sm_rx.rx().wait_pull().await as u8 + } +} + +impl ErrorType for PioUartRx<'_, PIO, SM> { + type Error = Infallible; +} + +impl Read for PioUartRx<'_, PIO, SM> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + let mut i = 0; + while i < buf.len() { + buf[i] = self.read_u8().await; + i += 1; + } + Ok(i) + } +} diff --git a/embassy-rp-fork/src/pio_programs/ws2812.rs b/embassy-rp-fork/src/pio_programs/ws2812.rs new file mode 100644 index 0000000..0b60353 --- /dev/null +++ b/embassy-rp-fork/src/pio_programs/ws2812.rs @@ -0,0 +1,274 @@ +//! [ws2812](https://www.sparkfun.com/categories/tags/ws2812) + +use embassy_time::Timer; +use fixed::types::U24F8; +use smart_leds::{RGB8, RGBW}; + +use crate::Peri; +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; + +const T1: u8 = 2; // start bit +const T2: u8 = 5; // data bit +const T3: u8 = 3; // stop bit +const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + +/// Color orders for WS2812B, type RGB8 +pub trait RgbColorOrder { + /// Pack an 8-bit RGB color into a u32 + fn pack(color: RGB8) -> u32; +} + +/// Green, Red, Blue order is the common default for WS2812B +pub struct Grb; +impl RgbColorOrder for Grb { + /// Pack an 8-bit RGB color into a u32 in GRB order + fn pack(color: RGB8) -> u32 { + (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8) + } +} + +/// Red, Green, Blue is used by some WS2812B implementations +pub struct Rgb; +impl RgbColorOrder for Rgb { + /// Pack an 8-bit RGB color into a u32 in RGB order + fn pack(color: RGB8) -> u32 { + (u32::from(color.r) << 24) | (u32::from(color.g) << 16) | (u32::from(color.b) << 8) + } +} + +/// Color orders RGBW strips +pub trait RgbwColorOrder { + /// Pack an RGB+W color into a u32 + fn pack(color: RGBW) -> u32; +} + +/// Green, Red, Blue, White order is the common default for RGBW strips +pub struct Grbw; +impl RgbwColorOrder for Grbw { + /// Pack an RGB+W color into a u32 in GRBW order + fn pack(color: RGBW) -> u32 { + (u32::from(color.g) << 24) | (u32::from(color.r) << 16) | (u32::from(color.b) << 8) | u32::from(color.a.0) + } +} + +/// Red, Green, Blue, White order +pub struct Rgbw; +impl RgbwColorOrder for Rgbw { + /// Pack an RGB+W color into a u32 in RGBW order + fn pack(color: RGBW) -> u32 { + (u32::from(color.r) << 24) | (u32::from(color.g) << 16) | (u32::from(color.b) << 8) | u32::from(color.a.0) + } +} + +/// This struct represents a ws2812 program loaded into pio instruction memory. +pub struct PioWs2812Program<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioWs2812Program<'a, PIO> { + /// Load the ws2812 program into the given pio + pub fn new(common: &mut Common<'a, PIO>) -> Self { + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let prg = common.load_program(&prg); + + Self { prg } + } +} + +/// Pio backed RGB ws2812 driver +/// Const N is the number of ws2812 leds attached to this pin +pub struct PioWs2812<'d, P: Instance, const S: usize, const N: usize, ORDER> +where + ORDER: RgbColorOrder, +{ + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, + _order: core::marker::PhantomData, +} + +impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N, Grb> { + /// Configure a pio state machine to use the loaded ws2812 program. + /// Uses the default GRB order. + pub fn new( + pio: &mut Common<'d, P>, + sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + pin: Peri<'d, impl PioPin>, + program: &PioWs2812Program<'d, P>, + ) -> Self { + Self::with_color_order(pio, sm, dma, pin, program) + } +} + +impl<'d, P: Instance, const S: usize, const N: usize, ORDER> PioWs2812<'d, P, S, N, ORDER> +where + ORDER: RgbColorOrder, +{ + /// Configure a pio state machine to use the loaded ws2812 program. + /// Uses the specified color order. + pub fn with_color_order( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + pin: Peri<'d, impl PioPin>, + program: &PioWs2812Program<'d, P>, + ) -> Self { + // Setup sm0 + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&program.prg, &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); + let ws2812_freq = U24F8::from_num(800); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + dma: dma.into(), + sm, + _order: core::marker::PhantomData, + } + } + + /// Write a buffer of [smart_leds::RGB8] to the ws2812 string + pub async fn write(&mut self, colors: &[RGB8; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + words[i] = ORDER::pack(colors[i]); + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await; + + Timer::after_micros(55).await; + } +} + +/// Pio backed RGBW ws2812 driver +/// This version is intended for ws2812 leds with 4 addressable lights +/// Const N is the number of ws2812 leds attached to this pin +pub struct RgbwPioWs2812<'d, P: Instance, const S: usize, const N: usize, ORDER> +where + ORDER: RgbwColorOrder, +{ + dma: Peri<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, + _order: core::marker::PhantomData, +} + +impl<'d, P: Instance, const S: usize, const N: usize> RgbwPioWs2812<'d, P, S, N, Grbw> { + /// Configure a pio state machine to use the loaded ws2812 program. + /// Uses the default GRBW color order + pub fn new( + pio: &mut Common<'d, P>, + sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + pin: Peri<'d, impl PioPin>, + program: &PioWs2812Program<'d, P>, + ) -> Self { + Self::with_color_order(pio, sm, dma, pin, program) + } +} + +impl<'d, P: Instance, const S: usize, const N: usize, ORDER> RgbwPioWs2812<'d, P, S, N, ORDER> +where + ORDER: RgbwColorOrder, +{ + /// Configure a pio state machine to use the loaded ws2812 program. + /// Uses the specified color order + pub fn with_color_order( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: Peri<'d, impl Channel>, + pin: Peri<'d, impl PioPin>, + program: &PioWs2812Program<'d, P>, + ) -> Self { + // Setup sm0 + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + cfg.use_program(&program.prg, &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + let clock_freq = U24F8::from_num(clk_sys_freq() / 1000); + let ws2812_freq = U24F8::from_num(800); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + dma: dma.into(), + sm, + _order: core::marker::PhantomData, + } + } + + /// Write a buffer of [smart_leds::RGBW] to the ws2812 string + pub async fn write(&mut self, colors: &[RGBW; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + words[i] = ORDER::pack(colors[i]); + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await; + + Timer::after_micros(55).await; + } +} diff --git a/embassy-rp-fork/src/psram.rs b/embassy-rp-fork/src/psram.rs new file mode 100644 index 0000000..25185ea --- /dev/null +++ b/embassy-rp-fork/src/psram.rs @@ -0,0 +1,682 @@ +//! PSRAM driver for APS6404L and compatible devices +//! +//! This driver provides support for PSRAM (Pseudo-Static RAM) devices connected via QMI CS1. +//! It handles device verification, initialization, and memory-mapped access configuration. +//! +//! This driver is only available on RP235x chips as it requires the QMI CS1 peripheral. + +// Credit: Initially based on https://github.com/Altaflux/gb-rp2350 (also licensed Apache 2.0 + MIT). +// Copyright (c) Altaflux + +#![cfg(feature = "_rp235x")] + +use critical_section::{CriticalSection, RestoreState, acquire, release}; + +use crate::pac; +use crate::qmi_cs1::QmiCs1; + +/// PSRAM errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// PSRAM device is not detected or not supported + DeviceNotFound, + /// Invalid configuration + InvalidConfig, + /// Detected PSRAM size does not match the expected size + SizeMismatch, +} + +/// PSRAM device verification type. +#[derive(Clone, Copy)] +pub enum VerificationType { + /// Skip device verification + None, + /// Verify as APS6404L device + Aps6404l, +} + +/// Memory configuration. +#[derive(Clone)] +pub struct Config { + /// System clock frequency in Hz + pub clock_hz: u32, + /// Maximum memory operating frequency in Hz + pub max_mem_freq: u32, + /// Maximum CS assert time in microseconds (must be <= 8 us) + pub max_select_us: u32, + /// Minimum CS deassert time in nanoseconds (must be >= 18 ns) + pub min_deselect_ns: u32, + /// Cooldown period between operations (in SCLK cycles) + pub cooldown: u8, + /// Page break size for memory operations + pub page_break: PageBreak, + /// Clock divisor for direct mode operations during initialization + pub init_clkdiv: u8, + /// Enter Quad Mode command + pub enter_quad_cmd: Option, + /// Quad Read command (fast read with 4-bit data) + pub quad_read_cmd: u8, + /// Quad Write command (page program with 4-bit data) + pub quad_write_cmd: Option, + /// Number of dummy cycles for quad read operations + pub dummy_cycles: u8, + /// Read format configuration + pub read_format: FormatConfig, + /// Write format configuration + pub write_format: Option, + /// Expected memory size in bytes + pub mem_size: usize, + /// Device verification type + pub verification_type: VerificationType, + /// Whether the memory is writable via XIP (e.g., PSRAM vs. read-only flash) + pub xip_writable: bool, +} + +/// Page break configuration for memory window operations. +#[derive(Clone, Copy)] +pub enum PageBreak { + /// No page breaks + None, + /// Break at 256-byte boundaries + _256, + /// Break at 1024-byte boundaries + _1024, + /// Break at 4096-byte boundaries + _4096, +} + +/// Format configuration for read/write operations. +#[derive(Clone)] +pub struct FormatConfig { + /// Width of command prefix phase + pub prefix_width: Width, + /// Width of address phase + pub addr_width: Width, + /// Width of command suffix phase + pub suffix_width: Width, + /// Width of dummy/turnaround phase + pub dummy_width: Width, + /// Width of data phase + pub data_width: Width, + /// Length of prefix (None or 8 bits) + pub prefix_len: bool, + /// Length of suffix (None or 8 bits) + pub suffix_len: bool, +} + +/// Interface width for different phases of SPI transfer. +#[derive(Clone, Copy)] +pub enum Width { + /// Single-bit (standard SPI) + Single, + /// Dual-bit (2 data lines) + Dual, + /// Quad-bit (4 data lines) + Quad, +} + +impl Default for Config { + fn default() -> Self { + Self::aps6404l() + } +} + +impl Config { + /// Create configuration for APS6404L PSRAM. + pub fn aps6404l() -> Self { + Self { + clock_hz: 125_000_000, // Default to 125MHz + max_mem_freq: 133_000_000, // APS6404L max frequency + max_select_us: 8, // 8 microseconds max CS assert + min_deselect_ns: 18, // 18 nanoseconds min CS deassert + cooldown: 1, // 1 SCLK cycle cooldown + page_break: PageBreak::_1024, // 1024-byte page boundaries + init_clkdiv: 10, // Medium clock for initialization + enter_quad_cmd: Some(0x35), // Enter Quad Mode + quad_read_cmd: 0xEB, // Fast Quad Read + quad_write_cmd: Some(0x38), // Quad Page Program + dummy_cycles: 24, // 24 dummy cycles for quad read + read_format: FormatConfig { + prefix_width: Width::Quad, + addr_width: Width::Quad, + suffix_width: Width::Quad, + dummy_width: Width::Quad, + data_width: Width::Quad, + prefix_len: true, // 8-bit prefix + suffix_len: false, // No suffix + }, + write_format: Some(FormatConfig { + prefix_width: Width::Quad, + addr_width: Width::Quad, + suffix_width: Width::Quad, + dummy_width: Width::Quad, + data_width: Width::Quad, + prefix_len: true, // 8-bit prefix + suffix_len: false, // No suffix + }), + mem_size: 8 * 1024 * 1024, // 8MB for APS6404L + verification_type: VerificationType::Aps6404l, + xip_writable: true, // PSRAM is writable + } + } + + /// Create a custom memory configuration. + pub fn custom( + clock_hz: u32, + max_mem_freq: u32, + max_select_us: u32, + min_deselect_ns: u32, + cooldown: u8, + page_break: PageBreak, + init_clkdiv: u8, + enter_quad_cmd: Option, + quad_read_cmd: u8, + quad_write_cmd: Option, + dummy_cycles: u8, + read_format: FormatConfig, + write_format: Option, + mem_size: usize, + verification_type: VerificationType, + xip_writable: bool, + ) -> Self { + Self { + clock_hz, + max_mem_freq, + max_select_us, + min_deselect_ns, + cooldown, + page_break, + init_clkdiv, + enter_quad_cmd, + quad_read_cmd, + quad_write_cmd, + dummy_cycles, + read_format, + write_format, + mem_size, + verification_type, + xip_writable, + } + } +} + +/// PSRAM driver. +pub struct Psram<'d> { + #[allow(dead_code)] + qmi_cs1: QmiCs1<'d>, + size: usize, +} + +impl<'d> Psram<'d> { + /// Create a new PSRAM driver instance. + /// + /// This will detect the PSRAM device and configure it for memory-mapped access. + pub fn new(qmi_cs1: QmiCs1<'d>, config: Config) -> Result { + let qmi = pac::QMI; + let xip = pac::XIP_CTRL; + + // Verify PSRAM device if requested + match config.verification_type { + VerificationType::Aps6404l => { + Self::verify_aps6404l(&qmi, config.mem_size)?; + debug!("APS6404L PSRAM verified, size: {:#x}", config.mem_size); + } + VerificationType::None => { + debug!("Skipping PSRAM verification, assuming size: {:#x}", config.mem_size); + } + } + + // Initialize PSRAM with proper timing + Self::init_psram(&qmi, &xip, &config)?; + + Ok(Self { + qmi_cs1, + size: config.mem_size, + }) + } + + /// Get the detected PSRAM size in bytes. + pub fn size(&self) -> usize { + self.size + } + + /// Get the base address for memory-mapped access. + /// + /// After initialization, PSRAM can be accessed directly through memory mapping. + /// The base address for CS1 is typically 0x11000000. + pub fn base_address(&self) -> *mut u8 { + 0x1100_0000 as *mut u8 + } + + /// Verify APS6404L PSRAM device matches expected configuration. + #[unsafe(link_section = ".data.ram_func")] + #[inline(never)] + fn verify_aps6404l(qmi: &pac::qmi::Qmi, expected_size: usize) -> Result<(), Error> { + // APS6404L-specific constants + const EXPECTED_KGD: u8 = 0x5D; + crate::multicore::pause_core1(); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + { + // Helper for making sure `release` is called even if `f` panics. + struct Guard { + state: RestoreState, + } + + impl Drop for Guard { + #[inline(always)] + fn drop(&mut self) { + unsafe { release(self.state) } + } + } + + let state = unsafe { acquire() }; + let _guard = Guard { state }; + + let _cs = unsafe { CriticalSection::new() }; + + let (kgd, eid) = unsafe { Self::read_aps6404l_kgd_eid(qmi) }; + + let mut detected_size: u32 = 0; + if kgd == EXPECTED_KGD as u32 { + detected_size = 1024 * 1024; + let size_id = eid >> 5; + if eid == 0x26 || size_id == 2 { + // APS6404L-3SQR-SN or 8MB variants + detected_size *= 8; + } else if size_id == 0 { + detected_size *= 2; + } else if size_id == 1 { + detected_size *= 4; + } + } + + // Verify the detected size matches the expected size + if detected_size as usize != expected_size { + return Err(Error::SizeMismatch); + } + + Ok(()) + }?; + + crate::multicore::resume_core1(); + + Ok(()) + } + + #[unsafe(link_section = ".data.ram_func")] + #[inline(never)] + unsafe fn read_aps6404l_kgd_eid(qmi: &pac::qmi::Qmi) -> (u32, u32) { + const RESET_ENABLE_CMD: u8 = 0xf5; + const READ_ID_CMD: u8 = 0x9f; + + #[allow(unused_assignments)] + let mut kgd: u32 = 0; + #[allow(unused_assignments)] + let mut eid: u32 = 0; + + let qmi_base = qmi.as_ptr() as usize; + + #[cfg(target_arch = "arm")] + core::arch::asm!( + // Configure DIRECT_CSR: shift clkdiv (30) to bits 29:22 and set EN (bit 0) + "movs {temp}, #30", + "lsls {temp}, {temp}, #22", + "orr {temp}, {temp}, #1", // Set EN bit + "str {temp}, [{qmi_base}]", + + // Poll for BUSY to clear before first operation + "1:", + "ldr {temp}, [{qmi_base}]", + "lsls {temp}, {temp}, #30", // Shift BUSY bit to sign position + "bmi 1b", // Branch if negative (BUSY = 1) + + // Assert CS1N (bit 3) + "ldr {temp}, [{qmi_base}]", + "orr {temp}, {temp}, #8", // Set ASSERT_CS1N bit (bit 3) + "str {temp}, [{qmi_base}]", + + // Transmit RESET_ENABLE_CMD as quad + // DIRECT_TX: OE=1 (bit 19), IWIDTH=2 (bits 17:16), DATA=RESET_ENABLE_CMD + "movs {temp}, {reset_enable_cmd}", + "orr {temp}, {temp}, #0x80000", // Set OE (bit 19) + "orr {temp}, {temp}, #0x20000", // Set IWIDTH=2 (quad, bits 17:16) + "str {temp}, [{qmi_base}, #4]", // Store to DIRECT_TX + + // Wait for BUSY to clear + "2:", + "ldr {temp}, [{qmi_base}]", + "lsls {temp}, {temp}, #30", + "bmi 2b", + + // Read and discard RX data + "ldr {temp}, [{qmi_base}, #8]", + + // Deassert CS1N + "ldr {temp}, [{qmi_base}]", + "bic {temp}, {temp}, #8", // Clear ASSERT_CS1N bit + "str {temp}, [{qmi_base}]", + + // Assert CS1N again + "ldr {temp}, [{qmi_base}]", + "orr {temp}, {temp}, #8", // Set ASSERT_CS1N bit + "str {temp}, [{qmi_base}]", + + // Read ID loop (7 iterations) + "movs {counter}, #0", // Initialize counter + + "3:", // Loop start + "cmp {counter}, #0", + "bne 4f", // If not first iteration, send 0xFF + + // First iteration: send READ_ID_CMD + "movs {temp}, {read_id_cmd}", + "b 5f", + "4:", // Other iterations: send 0xFF + "movs {temp}, #0xFF", + "5:", + "str {temp}, [{qmi_base}, #4]", // Store to DIRECT_TX + + // Wait for TXEMPTY + "6:", + "ldr {temp}, [{qmi_base}]", + "lsls {temp}, {temp}, #20", // Shift TXEMPTY (bit 11) to bit 31 + "bpl 6b", // Branch if positive (TXEMPTY = 0) + + // Wait for BUSY to clear + "7:", + "ldr {temp}, [{qmi_base}]", + "lsls {temp}, {temp}, #30", // Shift BUSY bit to sign position + "bmi 7b", // Branch if negative (BUSY = 1) + + // Read RX data + "ldr {temp}, [{qmi_base}, #8]", + "uxth {temp}, {temp}", // Extract lower 16 bits + + // Store KGD or EID based on iteration + "cmp {counter}, #5", + "bne 8f", + "mov {kgd}, {temp}", // Store KGD + "b 9f", + "8:", + "cmp {counter}, #6", + "bne 9f", + "mov {eid}, {temp}", // Store EID + + "9:", + "adds {counter}, #1", + "cmp {counter}, #7", + "blt 3b", // Continue loop if counter < 7 + + // Disable direct mode: clear EN and ASSERT_CS1N + "movs {temp}, #0", + "str {temp}, [{qmi_base}]", + + // Memory barriers + "dmb", + "dsb", + "isb", + qmi_base = in(reg) qmi_base, + temp = out(reg) _, + counter = out(reg) _, + kgd = out(reg) kgd, + eid = out(reg) eid, + reset_enable_cmd = const RESET_ENABLE_CMD as u32, + read_id_cmd = const READ_ID_CMD as u32, + options(nostack), + ); + + #[cfg(target_arch = "riscv32")] + unimplemented!("APS6404L PSRAM verification not implemented for RISC-V"); + + (kgd, eid) + } + + /// Initialize PSRAM with proper timing. + #[unsafe(link_section = ".data.ram_func")] + #[inline(never)] + fn init_psram(qmi: &pac::qmi::Qmi, xip_ctrl: &pac::xip_ctrl::XipCtrl, config: &Config) -> Result<(), Error> { + // Set PSRAM timing for APS6404 + // + // Using an rxdelay equal to the divisor isn't enough when running the APS6404 close to 133 MHz. + // So: don't allow running at divisor 1 above 100 MHz (because delay of 2 would be too late), + // and add an extra 1 to the rxdelay if the divided clock is > 100 MHz (i.e., sys clock > 200 MHz). + let clock_hz = config.clock_hz; + let max_psram_freq = config.max_mem_freq; + + let mut divisor: u32 = (clock_hz + max_psram_freq - 1) / max_psram_freq; + if divisor == 1 && clock_hz > 100_000_000 { + divisor = 2; + } + let mut rxdelay: u32 = divisor; + if clock_hz / divisor > 100_000_000 { + rxdelay += 1; + } + + // - Max select must be <= 8 us. The value is given in multiples of 64 system clocks. + // - Min deselect must be >= 18ns. The value is given in system clock cycles - ceil(divisor / 2). + let clock_period_fs: u64 = 1_000_000_000_000_000_u64 / u64::from(clock_hz); + let max_select: u8 = ((config.max_select_us as u64 * 1_000_000) / clock_period_fs) as u8; + let min_deselect: u32 = ((config.min_deselect_ns as u64 * 1_000_000 + (clock_period_fs - 1)) / clock_period_fs + - u64::from(divisor + 1) / 2) as u32; + + crate::multicore::pause_core1(); + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + if let Some(enter_quad_cmd) = config.enter_quad_cmd { + // Helper for making sure `release` is called even if `f` panics. + struct Guard { + state: RestoreState, + } + + impl Drop for Guard { + #[inline(always)] + fn drop(&mut self) { + unsafe { release(self.state) } + } + } + + let state = unsafe { acquire() }; + let _guard = Guard { state }; + + let _cs = unsafe { CriticalSection::new() }; + + unsafe { Self::direct_csr_send_init_command(config, enter_quad_cmd) }; + + qmi.mem(1).timing().write(|w| { + w.set_cooldown(config.cooldown); + w.set_pagebreak(match config.page_break { + PageBreak::None => pac::qmi::vals::Pagebreak::NONE, + PageBreak::_256 => pac::qmi::vals::Pagebreak::_256, + PageBreak::_1024 => pac::qmi::vals::Pagebreak::_1024, + PageBreak::_4096 => pac::qmi::vals::Pagebreak::_4096, + }); + w.set_max_select(max_select); + w.set_min_deselect(min_deselect as u8); + w.set_rxdelay(rxdelay as u8); + w.set_clkdiv(divisor as u8); + }); + + // Set PSRAM commands and formats + qmi.mem(1).rfmt().write(|w| { + let width_to_pac = |w: Width| match w { + Width::Single => pac::qmi::vals::PrefixWidth::S, + Width::Dual => pac::qmi::vals::PrefixWidth::D, + Width::Quad => pac::qmi::vals::PrefixWidth::Q, + }; + + w.set_prefix_width(width_to_pac(config.read_format.prefix_width)); + w.set_addr_width(match config.read_format.addr_width { + Width::Single => pac::qmi::vals::AddrWidth::S, + Width::Dual => pac::qmi::vals::AddrWidth::D, + Width::Quad => pac::qmi::vals::AddrWidth::Q, + }); + w.set_suffix_width(match config.read_format.suffix_width { + Width::Single => pac::qmi::vals::SuffixWidth::S, + Width::Dual => pac::qmi::vals::SuffixWidth::D, + Width::Quad => pac::qmi::vals::SuffixWidth::Q, + }); + w.set_dummy_width(match config.read_format.dummy_width { + Width::Single => pac::qmi::vals::DummyWidth::S, + Width::Dual => pac::qmi::vals::DummyWidth::D, + Width::Quad => pac::qmi::vals::DummyWidth::Q, + }); + w.set_data_width(match config.read_format.data_width { + Width::Single => pac::qmi::vals::DataWidth::S, + Width::Dual => pac::qmi::vals::DataWidth::D, + Width::Quad => pac::qmi::vals::DataWidth::Q, + }); + w.set_prefix_len(if config.read_format.prefix_len { + pac::qmi::vals::PrefixLen::_8 + } else { + pac::qmi::vals::PrefixLen::NONE + }); + w.set_suffix_len(if config.read_format.suffix_len { + pac::qmi::vals::SuffixLen::_8 + } else { + pac::qmi::vals::SuffixLen::NONE + }); + w.set_dummy_len(match config.dummy_cycles { + 0 => pac::qmi::vals::DummyLen::NONE, + 4 => pac::qmi::vals::DummyLen::_4, + 8 => pac::qmi::vals::DummyLen::_8, + 12 => pac::qmi::vals::DummyLen::_12, + 16 => pac::qmi::vals::DummyLen::_16, + 20 => pac::qmi::vals::DummyLen::_20, + 24 => pac::qmi::vals::DummyLen::_24, + 28 => pac::qmi::vals::DummyLen::_28, + _ => pac::qmi::vals::DummyLen::_24, // Default to 24 + }); + }); + + qmi.mem(1).rcmd().write(|w| w.set_prefix(config.quad_read_cmd)); + + if let Some(ref write_format) = config.write_format { + qmi.mem(1).wfmt().write(|w| { + w.set_prefix_width(match write_format.prefix_width { + Width::Single => pac::qmi::vals::PrefixWidth::S, + Width::Dual => pac::qmi::vals::PrefixWidth::D, + Width::Quad => pac::qmi::vals::PrefixWidth::Q, + }); + w.set_addr_width(match write_format.addr_width { + Width::Single => pac::qmi::vals::AddrWidth::S, + Width::Dual => pac::qmi::vals::AddrWidth::D, + Width::Quad => pac::qmi::vals::AddrWidth::Q, + }); + w.set_suffix_width(match write_format.suffix_width { + Width::Single => pac::qmi::vals::SuffixWidth::S, + Width::Dual => pac::qmi::vals::SuffixWidth::D, + Width::Quad => pac::qmi::vals::SuffixWidth::Q, + }); + w.set_dummy_width(match write_format.dummy_width { + Width::Single => pac::qmi::vals::DummyWidth::S, + Width::Dual => pac::qmi::vals::DummyWidth::D, + Width::Quad => pac::qmi::vals::DummyWidth::Q, + }); + w.set_data_width(match write_format.data_width { + Width::Single => pac::qmi::vals::DataWidth::S, + Width::Dual => pac::qmi::vals::DataWidth::D, + Width::Quad => pac::qmi::vals::DataWidth::Q, + }); + w.set_prefix_len(if write_format.prefix_len { + pac::qmi::vals::PrefixLen::_8 + } else { + pac::qmi::vals::PrefixLen::NONE + }); + w.set_suffix_len(if write_format.suffix_len { + pac::qmi::vals::SuffixLen::_8 + } else { + pac::qmi::vals::SuffixLen::NONE + }); + }); + } + + if let Some(quad_write_cmd) = config.quad_write_cmd { + qmi.mem(1).wcmd().write(|w| w.set_prefix(quad_write_cmd)); + } + + if config.xip_writable { + // Enable XIP writable mode for PSRAM + xip_ctrl.ctrl().modify(|w| w.set_writable_m1(true)); + } else { + // Disable XIP writable mode + xip_ctrl.ctrl().modify(|w| w.set_writable_m1(false)); + } + } + crate::multicore::resume_core1(); + + Ok(()) + } + + #[unsafe(link_section = ".data.ram_func")] + #[inline(never)] + unsafe fn direct_csr_send_init_command(config: &Config, init_cmd: u8) { + #[cfg(target_arch = "arm")] + core::arch::asm!( + // Full memory barrier + "dmb", + "dsb", + "isb", + + // Configure QMI Direct CSR register + // Load base address of QMI (0x400D0000) + "movw {base}, #0x0000", + "movt {base}, #0x400D", + + // Load init_clkdiv and shift to bits 29:22 + "lsl {temp}, {clkdiv}, #22", + + // OR with EN (bit 0) and AUTO_CS1N (bit 7) + "orr {temp}, {temp}, #0x81", + + // Store to DIRECT_CSR register + "str {temp}, [{base}, #0]", + + // Memory barrier + "dmb", + + // First busy wait loop + "1:", + "ldr {temp}, [{base}, #0]", // Load DIRECT_CSR + "tst {temp}, #0x2", // Test BUSY bit (bit 1) + "bne 1b", // Branch if busy + + // Write to Direct TX register + "mov {temp}, {enter_quad_cmd}", + + // OR with NOPUSH (bit 20) + "orr {temp}, {temp}, #0x100000", + + // Store to DIRECT_TX register (offset 0x4) + "str {temp}, [{base}, #4]", + + // Memory barrier + "dmb", + + // Second busy wait loop + "2:", + "ldr {temp}, [{base}, #0]", // Load DIRECT_CSR + "tst {temp}, #0x2", // Test BUSY bit (bit 1) + "bne 2b", // Branch if busy + + // Disable Direct CSR + "mov {temp}, #0", + "str {temp}, [{base}, #0]", // Clear DIRECT_CSR register + + // Full memory barrier to ensure no prefetching + "dmb", + "dsb", + "isb", + base = out(reg) _, + temp = out(reg) _, + clkdiv = in(reg) config.init_clkdiv as u32, + enter_quad_cmd = in(reg) u32::from(init_cmd), + options(nostack), + ); + + #[cfg(target_arch = "riscv32")] + unimplemented!("Direct CSR command sending is not implemented for RISC-V yet"); + } +} diff --git a/embassy-rp-fork/src/pwm.rs b/embassy-rp-fork/src/pwm.rs new file mode 100644 index 0000000..59a3fc9 --- /dev/null +++ b/embassy-rp-fork/src/pwm.rs @@ -0,0 +1,601 @@ +//! Pulse Width Modulation (PWM) + +use embassy_hal_internal::{Peri, PeripheralType}; +pub use embedded_hal_1::pwm::SetDutyCycle; +use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; +use fixed::FixedU16; +use fixed::traits::ToFixed; +use pac::pwm::regs::{ChDiv, Intr}; +use pac::pwm::vals::Divmode; + +use crate::gpio::{AnyPin, Pin as GpioPin, Pull, SealedPin as _}; +use crate::{RegExt, pac, peripherals}; + +/// The configuration of a PWM slice. +/// Note the period in clock cycles of a slice can be computed as: +/// `(top + 1) * (phase_correct ? 1 : 2) * divider` +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Inverts the PWM output signal on channel A. + pub invert_a: bool, + /// Inverts the PWM output signal on channel B. + pub invert_b: bool, + /// Enables phase-correct mode for PWM operation. + /// In phase-correct mode, the PWM signal is generated in such a way that + /// the pulse is always centered regardless of the duty cycle. + /// The output frequency is halved when phase-correct mode is enabled. + pub phase_correct: bool, + /// Enables the PWM slice, allowing it to generate an output. + pub enable: bool, + /// A fractional clock divider, represented as a fixed-point number with + /// 8 integer bits and 4 fractional bits. It allows precise control over + /// the PWM output frequency by gating the PWM counter increment. + /// A higher value will result in a slower output frequency. + pub divider: fixed::FixedU16, + /// The output on channel A goes high when `compare_a` is higher than the + /// counter. A compare of 0 will produce an always low output, while a + /// compare of `top + 1` will produce an always high output. + pub compare_a: u16, + /// The output on channel B goes high when `compare_b` is higher than the + /// counter. A compare of 0 will produce an always low output, while a + /// compare of `top + 1` will produce an always high output. + pub compare_b: u16, + /// The point at which the counter wraps, representing the maximum possible + /// period. The counter will either wrap to 0 or reverse depending on the + /// setting of `phase_correct`. + pub top: u16, +} + +impl Default for Config { + fn default() -> Self { + Self { + invert_a: false, + invert_b: false, + phase_correct: false, + enable: true, // differs from reset value + divider: 1.to_fixed(), + compare_a: 0, + compare_b: 0, + top: 0xffff, + } + } +} + +/// PWM input mode. +pub enum InputMode { + /// Level mode. + Level, + /// Rising edge mode. + RisingEdge, + /// Falling edge mode. + FallingEdge, +} + +impl From for Divmode { + fn from(value: InputMode) -> Self { + match value { + InputMode::Level => Divmode::LEVEL, + InputMode::RisingEdge => Divmode::RISE, + InputMode::FallingEdge => Divmode::FALL, + } + } +} + +/// PWM error. +#[derive(Debug)] +pub enum PwmError { + /// Invalid Duty Cycle. + InvalidDutyCycle, +} + +impl Error for PwmError { + fn kind(&self) -> ErrorKind { + match self { + PwmError::InvalidDutyCycle => ErrorKind::Other, + } + } +} + +/// PWM driver. +pub struct Pwm<'d> { + pin_a: Option>, + pin_b: Option>, + slice: usize, +} + +impl<'d> ErrorType for Pwm<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for Pwm<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + p.cc().modify(|w| { + w.set_a(duty); + w.set_b(duty); + }); + Ok(()) + } +} + +impl<'d> Pwm<'d> { + fn new_inner( + slice: usize, + a: Option>, + b: Option>, + b_pull: Pull, + config: Config, + divmode: Divmode, + ) -> Self { + let p = pac::PWM.ch(slice); + p.csr().modify(|w| { + w.set_divmode(divmode); + w.set_en(false); + }); + p.ctr().write(|w| w.0 = 0); + Self::configure(p, &config); + + if let Some(pin) = &a { + pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + #[cfg(feature = "_rp235x")] + pin.pad_ctrl().modify(|w| { + w.set_iso(false); + }); + } + if let Some(pin) = &b { + pin.gpio().ctrl().write(|w| w.set_funcsel(4)); + pin.pad_ctrl().modify(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + #[cfg(feature = "_rp235x")] + if divmode != Divmode::DIV { + // Is in input mode and so must enable input mode for the pin + w.set_ie(true); + } + w.set_pue(b_pull == Pull::Up); + w.set_pde(b_pull == Pull::Down); + }); + } + Self { + // inner: p.into(), + pin_a: a, + pin_b: b, + slice, + } + } + + /// Create PWM driver without any configured pins. + #[inline] + pub fn new_free(slice: Peri<'d, T>, config: Config) -> Self { + Self::new_inner(slice.number(), None, None, Pull::None, config, Divmode::DIV) + } + + /// Create PWM driver with a single 'a' pin as output. + #[inline] + pub fn new_output_a(slice: Peri<'d, T>, a: Peri<'d, impl ChannelAPin>, config: Config) -> Self { + Self::new_inner(slice.number(), Some(a.into()), None, Pull::None, config, Divmode::DIV) + } + + /// Create PWM driver with a single 'b' pin as output. + #[inline] + pub fn new_output_b(slice: Peri<'d, T>, b: Peri<'d, impl ChannelBPin>, config: Config) -> Self { + Self::new_inner(slice.number(), None, Some(b.into()), Pull::None, config, Divmode::DIV) + } + + /// Create PWM driver with a 'a' and 'b' pins as output. + #[inline] + pub fn new_output_ab( + slice: Peri<'d, T>, + a: Peri<'d, impl ChannelAPin>, + b: Peri<'d, impl ChannelBPin>, + config: Config, + ) -> Self { + Self::new_inner( + slice.number(), + Some(a.into()), + Some(b.into()), + Pull::None, + config, + Divmode::DIV, + ) + } + + /// Create PWM driver with a single 'b' as input pin. + #[inline] + pub fn new_input( + slice: Peri<'d, T>, + b: Peri<'d, impl ChannelBPin>, + b_pull: Pull, + mode: InputMode, + config: Config, + ) -> Self { + Self::new_inner(slice.number(), None, Some(b.into()), b_pull, config, mode.into()) + } + + /// Create PWM driver with a 'a' and 'b' pins in the desired input mode. + #[inline] + pub fn new_output_input( + slice: Peri<'d, T>, + a: Peri<'d, impl ChannelAPin>, + b: Peri<'d, impl ChannelBPin>, + b_pull: Pull, + mode: InputMode, + config: Config, + ) -> Self { + Self::new_inner( + slice.number(), + Some(a.into()), + Some(b.into()), + b_pull, + config, + mode.into(), + ) + } + + /// Set the PWM config. + pub fn set_config(&mut self, config: &Config) { + Self::configure(pac::PWM.ch(self.slice), config); + } + + fn configure(p: pac::pwm::Channel, config: &Config) { + if config.divider > FixedU16::::from_bits(0xFFF) { + panic!("Requested divider is too large"); + } + + p.div().write_value(ChDiv(config.divider.to_bits() as u32)); + p.cc().write(|w| { + w.set_a(config.compare_a); + w.set_b(config.compare_b); + }); + p.top().write(|w| w.set_top(config.top)); + p.csr().modify(|w| { + w.set_a_inv(config.invert_a); + w.set_b_inv(config.invert_b); + w.set_ph_correct(config.phase_correct); + w.set_en(config.enable); + }); + } + + /// Advances a slice's output phase by one count while it is running + /// by inserting a pulse into the clock enable. The counter + /// will not count faster than once per cycle. + #[inline] + pub fn phase_advance(&mut self) { + let p = pac::PWM.ch(self.slice); + p.csr().write_set(|w| w.set_ph_adv(true)); + while p.csr().read().ph_adv() {} + } + + /// Retards a slice's output phase by one count while it is running + /// by deleting a pulse from the clock enable. The counter will not + /// count backward when clock enable is permanently low. + #[inline] + pub fn phase_retard(&mut self) { + let p = pac::PWM.ch(self.slice); + p.csr().write_set(|w| w.set_ph_ret(true)); + while p.csr().read().ph_ret() {} + } + + /// Read PWM counter. + #[inline] + pub fn counter(&self) -> u16 { + pac::PWM.ch(self.slice).ctr().read().ctr() + } + + /// Write PWM counter. + #[inline] + pub fn set_counter(&self, ctr: u16) { + pac::PWM.ch(self.slice).ctr().write(|w| w.set_ctr(ctr)) + } + + /// Wait for channel interrupt. + #[inline] + pub fn wait_for_wrap(&mut self) { + while !self.wrapped() {} + self.clear_wrapped(); + } + + /// Check if interrupt for channel is set. + #[inline] + pub fn wrapped(&mut self) -> bool { + pac::PWM.intr().read().0 & self.bit() != 0 + } + + #[inline] + /// Clear interrupt flag. + pub fn clear_wrapped(&mut self) { + pac::PWM.intr().write_value(Intr(self.bit() as _)); + } + + #[inline] + fn bit(&self) -> u32 { + 1 << self.slice as usize + } + + /// Splits the PWM driver into separate `PwmOutput` instances for channels A and B. + #[inline] + pub fn split(mut self) -> (Option>, Option>) { + ( + self.pin_a + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin), self.slice.clone(), true)), + self.pin_b + .take() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin), self.slice.clone(), true)), + ) + } + /// Splits the PWM driver by reference to allow for separate duty cycle control + /// of each channel (A and B) without taking ownership of the PWM instance. + #[inline] + pub fn split_by_ref(&mut self) -> (Option>, Option>) { + ( + self.pin_a + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::A(pin.reborrow()), self.slice.clone(), false)), + self.pin_b + .as_mut() + .map(|pin| PwmOutput::new(PwmChannelPin::B(pin.reborrow()), self.slice.clone(), false)), + ) + } +} + +enum PwmChannelPin<'d> { + A(Peri<'d, AnyPin>), + B(Peri<'d, AnyPin>), +} + +/// Single channel of Pwm driver. +pub struct PwmOutput<'d> { + //pin that can be ether ChannelAPin or ChannelBPin + channel_pin: PwmChannelPin<'d>, + slice: usize, + is_owned: bool, +} + +impl<'d> PwmOutput<'d> { + fn new(channel_pin: PwmChannelPin<'d>, slice: usize, is_owned: bool) -> Self { + Self { + channel_pin, + slice, + is_owned, + } + } +} + +impl<'d> Drop for PwmOutput<'d> { + fn drop(&mut self) { + if self.is_owned { + let p = pac::PWM.ch(self.slice); + match &self.channel_pin { + PwmChannelPin::A(pin) => { + p.cc().modify(|w| { + w.set_a(0); + }); + + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + //Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + PwmChannelPin::B(pin) => { + p.cc().modify(|w| { + w.set_b(0); + }); + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + //Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + } + } + } +} + +impl<'d> ErrorType for PwmOutput<'d> { + type Error = PwmError; +} + +impl<'d> SetDutyCycle for PwmOutput<'d> { + fn max_duty_cycle(&self) -> u16 { + pac::PWM.ch(self.slice).top().read().top() + } + + fn set_duty_cycle(&mut self, duty: u16) -> Result<(), Self::Error> { + let max_duty = self.max_duty_cycle(); + if duty > max_duty { + return Err(PwmError::InvalidDutyCycle); + } + + let p = pac::PWM.ch(self.slice); + match self.channel_pin { + PwmChannelPin::A(_) => { + p.cc().modify(|w| { + w.set_a(duty); + }); + } + PwmChannelPin::B(_) => { + p.cc().modify(|w| { + w.set_b(duty); + }); + } + } + + Ok(()) + } +} + +/// Batch representation of PWM slices. +pub struct PwmBatch(u32); + +impl PwmBatch { + #[inline] + /// Enable a PWM slice in this batch. + pub fn enable(&mut self, pwm: &Pwm<'_>) { + self.0 |= pwm.bit(); + } + + #[inline] + /// Enable slices in this batch in a PWM. + pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) { + let mut en = PwmBatch(0); + batch(&mut en); + if enabled { + pac::PWM.en().write_set(|w| w.0 = en.0); + } else { + pac::PWM.en().write_clear(|w| w.0 = en.0); + } + } +} + +impl<'d> Drop for Pwm<'d> { + fn drop(&mut self) { + pac::PWM.ch(self.slice).csr().write_clear(|w| w.set_en(false)); + if let Some(pin) = &self.pin_a { + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + // Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + if let Some(pin) = &self.pin_b { + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + #[cfg(feature = "_rp235x")] + // Disable input mode. Only pin_b can be input, so not needed for pin_a + pin.pad_ctrl().modify(|w| { + w.set_ie(false); + }); + // Enable pin PULL-DOWN + pin.pad_ctrl().modify(|w| { + w.set_pde(true); + }); + } + } +} + +trait SealedSlice {} + +/// PWM Slice. +#[allow(private_bounds)] +pub trait Slice: PeripheralType + SealedSlice + Sized + 'static { + /// Slice number. + fn number(&self) -> usize; +} + +macro_rules! slice { + ($name:ident, $num:expr) => { + impl SealedSlice for peripherals::$name {} + impl Slice for peripherals::$name { + fn number(&self) -> usize { + $num + } + } + }; +} + +slice!(PWM_SLICE0, 0); +slice!(PWM_SLICE1, 1); +slice!(PWM_SLICE2, 2); +slice!(PWM_SLICE3, 3); +slice!(PWM_SLICE4, 4); +slice!(PWM_SLICE5, 5); +slice!(PWM_SLICE6, 6); +slice!(PWM_SLICE7, 7); + +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE8, 8); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE9, 9); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE10, 10); +#[cfg(feature = "_rp235x")] +slice!(PWM_SLICE11, 11); + +/// PWM Channel A. +pub trait ChannelAPin: GpioPin {} +/// PWM Channel B. +pub trait ChannelBPin: GpioPin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:ident, $kind:ident) => { + impl $kind for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, PWM_SLICE0, ChannelAPin); +impl_pin!(PIN_1, PWM_SLICE0, ChannelBPin); +impl_pin!(PIN_2, PWM_SLICE1, ChannelAPin); +impl_pin!(PIN_3, PWM_SLICE1, ChannelBPin); +impl_pin!(PIN_4, PWM_SLICE2, ChannelAPin); +impl_pin!(PIN_5, PWM_SLICE2, ChannelBPin); +impl_pin!(PIN_6, PWM_SLICE3, ChannelAPin); +impl_pin!(PIN_7, PWM_SLICE3, ChannelBPin); +impl_pin!(PIN_8, PWM_SLICE4, ChannelAPin); +impl_pin!(PIN_9, PWM_SLICE4, ChannelBPin); +impl_pin!(PIN_10, PWM_SLICE5, ChannelAPin); +impl_pin!(PIN_11, PWM_SLICE5, ChannelBPin); +impl_pin!(PIN_12, PWM_SLICE6, ChannelAPin); +impl_pin!(PIN_13, PWM_SLICE6, ChannelBPin); +impl_pin!(PIN_14, PWM_SLICE7, ChannelAPin); +impl_pin!(PIN_15, PWM_SLICE7, ChannelBPin); +impl_pin!(PIN_16, PWM_SLICE0, ChannelAPin); +impl_pin!(PIN_17, PWM_SLICE0, ChannelBPin); +impl_pin!(PIN_18, PWM_SLICE1, ChannelAPin); +impl_pin!(PIN_19, PWM_SLICE1, ChannelBPin); +impl_pin!(PIN_20, PWM_SLICE2, ChannelAPin); +impl_pin!(PIN_21, PWM_SLICE2, ChannelBPin); +impl_pin!(PIN_22, PWM_SLICE3, ChannelAPin); +impl_pin!(PIN_23, PWM_SLICE3, ChannelBPin); +impl_pin!(PIN_24, PWM_SLICE4, ChannelAPin); +impl_pin!(PIN_25, PWM_SLICE4, ChannelBPin); +impl_pin!(PIN_26, PWM_SLICE5, ChannelAPin); +impl_pin!(PIN_27, PWM_SLICE5, ChannelBPin); +impl_pin!(PIN_28, PWM_SLICE6, ChannelAPin); +impl_pin!(PIN_29, PWM_SLICE6, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, PWM_SLICE7, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, PWM_SLICE7, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, PWM_SLICE8, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, PWM_SLICE8, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, PWM_SLICE9, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, PWM_SLICE9, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, PWM_SLICE10, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, PWM_SLICE10, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, PWM_SLICE11, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, PWM_SLICE11, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, PWM_SLICE8, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, PWM_SLICE8, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, PWM_SLICE9, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, PWM_SLICE9, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, PWM_SLICE10, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, PWM_SLICE10, ChannelBPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, PWM_SLICE11, ChannelAPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, PWM_SLICE11, ChannelBPin); diff --git a/embassy-rp-fork/src/qmi_cs1.rs b/embassy-rp-fork/src/qmi_cs1.rs new file mode 100644 index 0000000..b8ae41e --- /dev/null +++ b/embassy-rp-fork/src/qmi_cs1.rs @@ -0,0 +1,57 @@ +//! QMI CS1 peripheral for RP235x +//! +//! This module provides access to the QMI CS1 functionality for use with external memory devices +//! such as PSRAM. The QMI (Quad SPI) controller supports CS1 as a second chip select signal. +//! +//! This peripheral is only available on RP235x chips. + +#![cfg(feature = "_rp235x")] + +use embassy_hal_internal::{Peri, PeripheralType}; + +use crate::gpio::Pin as GpioPin; +use crate::{pac, peripherals}; + +/// QMI CS1 driver. +pub struct QmiCs1<'d> { + _inner: Peri<'d, peripherals::QMI_CS1>, +} + +impl<'d> QmiCs1<'d> { + /// Create a new QMI CS1 instance. + pub fn new(qmi_cs1: Peri<'d, peripherals::QMI_CS1>, cs1: Peri<'d, impl QmiCs1Pin>) -> Self { + // Configure CS1 pin for QMI function (funcsel = 9) + cs1.gpio().ctrl().write(|w| w.set_funcsel(9)); + + // Configure pad settings for high-speed operation + cs1.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + w.set_drive(pac::pads::vals::Drive::_12M_A); + w.set_slewfast(true); + }); + + Self { _inner: qmi_cs1 } + } +} + +trait SealedInstance {} + +/// QMI CS1 instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType {} + +impl SealedInstance for peripherals::QMI_CS1 {} + +impl Instance for peripherals::QMI_CS1 {} + +/// CS1 pin trait for QMI. +pub trait QmiCs1Pin: GpioPin {} + +// Implement pin traits for CS1-capable GPIO pins +impl QmiCs1Pin for peripherals::PIN_0 {} +impl QmiCs1Pin for peripherals::PIN_8 {} +impl QmiCs1Pin for peripherals::PIN_19 {} +#[cfg(feature = "rp235xb")] +impl QmiCs1Pin for peripherals::PIN_47 {} diff --git a/embassy-rp-fork/src/relocate.rs b/embassy-rp-fork/src/relocate.rs new file mode 100644 index 0000000..6ff40dd --- /dev/null +++ b/embassy-rp-fork/src/relocate.rs @@ -0,0 +1,66 @@ +use pio::{Program, SideSet, Wrap}; + +pub struct CodeIterator<'a, I> +where + I: Iterator, +{ + iter: I, + offset: u8, +} + +impl<'a, I: Iterator> CodeIterator<'a, I> { + pub fn new(iter: I, offset: u8) -> CodeIterator<'a, I> { + CodeIterator { iter, offset } + } +} + +impl<'a, I> Iterator for CodeIterator<'a, I> +where + I: Iterator, +{ + type Item = u16; + fn next(&mut self) -> Option { + self.iter.next().map(|&instr| { + if instr & 0b1110_0000_0000_0000 == 0 { + // this is a JMP instruction -> add offset to address + let address = (instr & 0b1_1111) as u8; + let address = address.wrapping_add(self.offset) % 32; + instr & (!0b11111) | address as u16 + } else { + instr + } + }) + } +} + +pub struct RelocatedProgram<'a, const PROGRAM_SIZE: usize> { + program: &'a Program, + origin: u8, +} + +impl<'a, const PROGRAM_SIZE: usize> RelocatedProgram<'a, PROGRAM_SIZE> { + pub fn new_with_origin(program: &Program, origin: u8) -> RelocatedProgram<'_, PROGRAM_SIZE> { + RelocatedProgram { program, origin } + } + + pub fn code(&'a self) -> CodeIterator<'a, core::slice::Iter<'a, u16>> { + CodeIterator::new(self.program.code.iter(), self.origin) + } + + pub fn wrap(&self) -> Wrap { + let wrap = self.program.wrap; + let origin = self.origin; + Wrap { + source: wrap.source.wrapping_add(origin) % 32, + target: wrap.target.wrapping_add(origin) % 32, + } + } + + pub fn side_set(&self) -> SideSet { + self.program.side_set + } + + pub fn origin(&self) -> u8 { + self.origin + } +} diff --git a/embassy-rp-fork/src/reset.rs b/embassy-rp-fork/src/reset.rs new file mode 100644 index 0000000..4b9e424 --- /dev/null +++ b/embassy-rp-fork/src/reset.rs @@ -0,0 +1,15 @@ +pub use pac::resets::regs::Peripherals; + +use crate::pac; + +pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ff_ffff); + +pub(crate) fn reset(peris: Peripherals) { + pac::RESETS.reset().write_value(peris); +} + +pub(crate) fn unreset_wait(peris: Peripherals) { + // TODO use the "atomic clear" register version + pac::RESETS.reset().modify(|v| *v = Peripherals(v.0 & !peris.0)); + while ((!pac::RESETS.reset_done().read().0) & peris.0) != 0 {} +} diff --git a/embassy-rp-fork/src/rom_data/mod.rs b/embassy-rp-fork/src/rom_data/mod.rs new file mode 100644 index 0000000..a4aba57 --- /dev/null +++ b/embassy-rp-fork/src/rom_data/mod.rs @@ -0,0 +1,33 @@ +#![cfg_attr( + feature = "rp2040", + doc = r" +Functions and data from the RPI Bootrom. + +From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: + +> The Bootrom contains a number of public functions that provide useful +> RP2040 functionality that might be needed in the absence of any other code +> on the device, as well as highly optimized versions of certain key +> functionality that would otherwise have to take up space in most user +> binaries. +" +)] +#![cfg_attr( + feature = "_rp235x", + doc = r" +Functions and data from the RPI Bootrom. + +From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +RP2350 datasheet: + +> Whilst some ROM space is dedicated to the implementation of the boot +> sequence and USB/UART boot interfaces, the bootrom also contains public +> functions that provide useful RP2350 functionality that may be useful for +> any code or runtime running on the device +" +)] + +#[cfg_attr(feature = "rp2040", path = "rp2040.rs")] +#[cfg_attr(feature = "_rp235x", path = "rp235x.rs")] +mod inner; +pub use inner::*; diff --git a/embassy-rp-fork/src/rom_data/rp2040.rs b/embassy-rp-fork/src/rom_data/rp2040.rs new file mode 100644 index 0000000..27a8d89 --- /dev/null +++ b/embassy-rp-fork/src/rom_data/rp2040.rs @@ -0,0 +1,756 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for (table) +type RomTableLookupFn = unsafe extern "C" fn(*const u16, u32) -> T; + +/// The following addresses are described at `2.8.2. Bootrom Contents` +/// Pointer to the lookup table function supplied by the rom. +const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _; + +/// Pointer to helper functions lookup table. +const FUNC_TABLE: *const u16 = 0x0000_0014 as _; + +/// Pointer to the public data lookup table. +const DATA_TABLE: *const u16 = 0x0000_0016 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +/// Retrieve rom content from a table using a code. +fn rom_table_lookup(table: *const u16, tag: RomFnTableCode) -> T { + unsafe { + let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR); + let rom_table_lookup: RomTableLookupFn = core::mem::transmute(rom_table_lookup_ptr); + rom_table_lookup(rom_hword_as_ptr(table) as *const u16, u16::from_le_bytes(tag) as u32) + } +} + +/// To save space, the ROM likes to store memory pointers (which are 32-bit on +/// the Cortex-M0+) using only the bottom 16-bits. The assumption is that the +/// values they point at live in the first 64 KiB of ROM, and the ROM is mapped +/// to address `0x0000_0000` and so 16-bits are always sufficient. +/// +/// This functions grabs a 16-bit value from ROM and expands it out to a full 32-bit pointer. +unsafe fn rom_hword_as_ptr(rom_address: *const u16) -> *const u32 { + let ptr: u16 = *rom_address; + ptr as *const u32 +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + declare_rom_function!{ + __internal , + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret + $lookup + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + declare_rom_function!{ + __internal unsafe , + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret + $lookup + } + }; + + ( + __internal + $( $maybe_unsafe:ident )? , + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + #[cfg(not(feature = "rom-func-cache"))] + pub(crate) fn outer_call() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: *const u32 = $lookup; + unsafe { + let func : $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret + = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + outer_call() + } + + #[cfg(feature = "rom-func-cache")] + // unlike rp2040-hal we store a full word, containing the full function pointer. + // rp2040-hal saves two bytes by storing only the rom offset, at the cost of + // having to do an indirection and an atomic operation on every rom call. + static mut CACHE: $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret + = trampoline; + + #[cfg(feature = "rom-func-cache")] + $( $maybe_unsafe )? extern "C" fn trampoline( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + let p: *const u32 = $lookup; + #[allow(unused_unsafe)] + unsafe { + CACHE = core::mem::transmute(p); + compiler_fence(Ordering::Release); + CACHE($($argname),*) + } + } + + #[cfg(feature = "rom-func-cache")] + pub(crate) fn outer_call() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + // + // We easily get away with using only compiler fences here + // because RP2040 SRAM is not cached. If it were we'd need + // to make sure updates propagate quickly, or just take the + // hit and let each core resolve every function once. + compiler_fence(Ordering::Acquire); + unsafe { + CACHE + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + // We can't just return the trampoline here because we need + // the actual resolved function address (e.x. flash operations + // can't reference a trampoline which itself is in flash). We + // can still utilize the cache, but we have to make sure it has + // been resolved already. Like the normal call path, we + // don't need anything stronger than fences because the + // final value always resolves to the same thing and SRAM + // itself is not cached. + compiler_fence(Ordering::Acquire); + #[allow(unused_unsafe)] + unsafe { + // ROM is 16kB in size at 0x0, so anything outside is cached + if CACHE as u32 >> 14 != 0 { + let p: *const u32 = $lookup; + CACHE = core::mem::transmute(p); + compiler_fence(Ordering::Release); + } + CACHE + } + } + } + + $(#[$outer])* + pub $( $maybe_unsafe )? extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::outer_call()($($argname),*) + } + }; +} + +macro_rules! rom_functions { + () => {}; + + ( + $(#[$outer:meta])* + $c:literal fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; + + ( + $(#[$outer:meta])* + $c:literal unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::inner::rom_table_lookup($crate::rom_data::inner::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; +} + +rom_functions! { + /// Return a count of the number of 1 bits in value. + b"P3" fn popcount32(value: u32) -> u32; + + /// Return the bits of value in the reverse order. + b"R3" fn reverse32(value: u32) -> u32; + + /// Return the number of consecutive high order 0 bits of value. If value is zero, returns 32. + b"L3" fn clz32(value: u32) -> u32; + + /// Return the number of consecutive low order 0 bits of value. If value is zero, returns 32. + b"T3" fn ctz32(value: u32) -> u32; + + /// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode: + /// * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED + /// for the USB Mass Storage Device: + /// * 0 No pins are used as per cold boot. + /// * Otherwise a single bit set indicating which GPIO pin should be set to output and + /// raised whenever there is mass storage activity from the host. + /// * disable_interface_mask may be used to control the exposed USB interfaces: + /// * 0 To enable both interfaces (as per cold boot). + /// * 1 To disable the USB Mass Storage Interface. + /// * 2 to Disable the USB PICOBOOT Interface. + b"UB" fn reset_to_usb_boot(gpio_activity_pin_mask: u32, disable_interface_mask: u32) -> (); + + /// Sets n bytes start at ptr to the value c and returns ptr + b"MS" unsafe fn memset(ptr: *mut u8, c: u8, n: u32) -> *mut u8; + + /// Sets n bytes start at ptr to the value c and returns ptr. + /// + /// Note this is a slightly more efficient variant of _memset that may only + /// be used if ptr is word aligned. + // Note the datasheet does not match the actual ROM for the code here, see + // https://github.com/raspberrypi/pico-feedback/issues/217 + b"S4" unsafe fn memset4(ptr: *mut u32, c: u8, n: u32) -> *mut u32; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + b"MC" unsafe fn memcpy(dest: *mut u8, src: *const u8, n: u32) -> *mut u8; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + /// + /// Note this is a slightly more efficient variant of _memcpy that may only be + /// used if dest and src are word aligned. + b"C4" unsafe fn memcpy44(dest: *mut u32, src: *const u32, n: u32) -> *mut u8; + + /// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads. + b"IF" unsafe fn connect_internal_flash() -> (); + + /// First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence. + /// + /// Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be + /// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This + /// function configures the SSI with a fixed SCK clock divisor of /6. + b"EX" unsafe fn flash_exit_xip() -> (); + + /// Erase a count bytes, starting at addr (offset from start of flash). Optionally, pass a + /// block erase command e.g. D8h block erase, and the size of the block erased by this + /// command — this function will use the larger block erase where possible, for much higher + /// erase speed. addr must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + b"RE" unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> (); + + /// Program data to a range of flash addresses starting at `addr` (and + /// offset from the start of flash) and `count` bytes in size. The value + /// `addr` must be aligned to a 256-byte boundary, and `count` must be a + /// multiple of 256. + b"RP" unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> (); + + /// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can + /// drive the flashchip select as normal. + b"FC" unsafe fn flash_flush_cache() -> (); + + /// Configure the SSI to generate a standard 03h serial read command, with 24 address bits, + /// upon each XIP access. This is a very slow XIP configuration, but is very widely supported. + /// The debugger calls this function after performing a flash erase/programming operation, so + /// that the freshly-programmed code and data is visible to the debug host, without having to + /// know exactly what kind of flash device is connected. + b"CX" unsafe fn flash_enter_cmd_xip() -> (); + + /// This is the method that is entered by core 1 on reset to wait to be launched by core 0. + /// There are few cases where you should call this method (resetting core 1 is much better). + /// This method does not return and should only ever be called on core 1. + b"WV" unsafe fn wait_for_vector() -> !; +} + +// Various C intrinsics in the ROM +intrinsics! { + #[alias = __popcountdi2] + extern "C" fn __popcountsi2(x: u32) -> u32 { + popcount32(x) + } + + #[alias = __clzdi2] + extern "C" fn __clzsi2(x: u32) -> u32 { + clz32(x) + } + + #[alias = __ctzdi2] + extern "C" fn __ctzsi2(x: u32) -> u32 { + ctz32(x) + } + + // __rbit is only unofficial, but it show up in the ARM documentation, + // so may as well hook it up. + #[alias = __rbitl] + extern "C" fn __rbit(x: u32) -> u32 { + reverse32(x) + } + + unsafe extern "aapcs" fn __aeabi_memset(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset(dest, c as u8, n as u32); + } + + #[alias = __aeabi_memset8] + unsafe extern "aapcs" fn __aeabi_memset4(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset4(dest as *mut u32, c as u8, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memclr(dest: *mut u8, n: usize) -> () { + memset(dest, 0, n as u32); + } + + #[alias = __aeabi_memclr8] + unsafe extern "aapcs" fn __aeabi_memclr4(dest: *mut u8, n: usize) -> () { + memset4(dest as *mut u32, 0, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy(dest, src, n as u32); + } + + #[alias = __aeabi_memcpy8] + unsafe extern "aapcs" fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy44(dest as *mut u32, src as *const u32, n as u32); + } +} + +unsafe fn convert_str(s: *const u8) -> &'static str { + let mut end = s; + while *end != 0 { + end = end.add(1); + } + let s = core::slice::from_raw_parts(s, end.offset_from(s) as usize); + core::str::from_utf8_unchecked(s) +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The Raspberry Pi Trading Ltd copyright string. +pub fn copyright_string() -> &'static str { + let s: *const u8 = rom_table_lookup(DATA_TABLE, *b"CR"); + unsafe { convert_str(s) } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let s: *const u32 = rom_table_lookup(DATA_TABLE, *b"GR"); + unsafe { *s } +} + +/// The start address of the floating point library code and data. +/// +/// This and fplib_end along with the individual function pointers in +/// soft_float_table can be used to copy the floating point implementation into +/// RAM if desired. +pub fn fplib_start() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FS") +} + +/// See Table 180 in the RP2040 datasheet for the contents of this table. +#[cfg_attr(feature = "rom-func-cache", inline(never))] +pub fn soft_float_table() -> *const usize { + rom_table_lookup(DATA_TABLE, *b"SF") +} + +/// The end address of the floating point library code and data. +pub fn fplib_end() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FE") +} + +/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table. +#[cfg_attr(feature = "rom-func-cache", inline(never))] +pub fn soft_double_table() -> *const usize { + if rom_version_number() < 2 { + panic!( + "Double precision operations require V2 bootrom (found: V{})", + rom_version_number() + ); + } + rom_table_lookup(DATA_TABLE, *b"SD") +} + +/// ROM functions using single-precision arithmetic (i.e. 'f32' in Rust terms) +pub mod float_funcs { + + macro_rules! make_functions { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_functions! { + /// Calculates `a + b` + 0x00 fadd(a: f32, b: f32) -> f32; + /// Calculates `a - b` + 0x04 fsub(a: f32, b: f32) -> f32; + /// Calculates `a * b` + 0x08 fmul(a: f32, b: f32) -> f32; + /// Calculates `a / b` + 0x0c fdiv(a: f32, b: f32) -> f32; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 fsqrt(v: f32) -> f32; + /// Converts an f32 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c float_to_int(v: f32) -> i32; + /// Converts an f32 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 float_to_fix(v: f32, n: i32) -> i32; + /// Converts an f32 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 float_to_uint(v: f32) -> u32; + /// Converts an f32 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 float_to_ufix(v: f32, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// f32 value, rounding to even on tie + 0x2c int_to_float(v: i32) -> f32; + /// Converts a signed fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_float(v: i32, n: i32) -> f32; + /// Converts an unsigned integer to the nearest + /// f32 value, rounding to even on tie + 0x34 uint_to_float(v: u32) -> f32; + /// Converts an unsigned fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x38 ufix_to_float(v: u32, n: i32) -> f32; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c fcos(angle: f32) -> f32; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 fsin(angle: f32) -> f32; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 ftan(angle: f32) -> f32; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c fexp(v: f32) -> f32; + /// Calculates the natural logarithm of `v`. If `v <= 0` return -Infinity + 0x50 fln(v: f32) -> f32; + } + + macro_rules! make_functions_v2 { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + if $crate::rom_data::rom_version_number() < 2 { + panic!( + "Floating point function requires V2 bootrom (found: V{})", + $crate::rom_data::rom_version_number() + ); + } + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + // These are only on BootROM v2 or higher + make_functions_v2! { + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 fcmp(a: f32, b: f32) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 fatan2(y: f32, x: f32) -> f32; + /// Converts a signed 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x5c int64_to_float(v: i64) -> f32; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_float(v: i64, n: i32) -> f32; + /// Converts an unsigned 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x64 uint64_to_float(v: u64) -> f32; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest f32 value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_float(v: u64, n: i32) -> f32; + /// Convert an f32 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c float_to_int64(v: f32) -> i64; + /// Converts an f32 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 float_to_fix64(v: f32, n: i32) -> f32; + /// Converts an f32 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 float_to_uint64(v: f32) -> u64; + /// Converts an f32 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 float_to_ufix64(v: f32, n: i32) -> u64; + /// Converts an f32 to an f64. + 0x7c float_to_double(v: f32) -> f64; + } +} + +/// Functions using double-precision arithmetic (i.e. 'f64' in Rust terms) +pub mod double_funcs { + + macro_rules! make_double_funcs { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_double_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_double_funcs! { + /// Calculates `a + b` + 0x00 dadd(a: f64, b: f64) -> f64; + /// Calculates `a - b` + 0x04 dsub(a: f64, b: f64) -> f64; + /// Calculates `a * b` + 0x08 dmul(a: f64, b: f64) -> f64; + /// Calculates `a / b` + 0x0c ddiv(a: f64, b: f64) -> f64; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 dsqrt(v: f64) -> f64; + /// Converts an f64 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c double_to_int(v: f64) -> i32; + /// Converts an f64 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 double_to_fix(v: f64, n: i32) -> i32; + /// Converts an f64 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 double_to_uint(v: f64) -> u32; + /// Converts an f64 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 double_to_ufix(v: f64, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// double value, rounding to even on tie + 0x2c int_to_double(v: i32) -> f64; + /// Converts a signed fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_double(v: i32, n: i32) -> f64; + /// Converts an unsigned integer to the nearest + /// double value, rounding to even on tie + 0x34 uint_to_double(v: u32) -> f64; + /// Converts an unsigned fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so f = + /// nearest(v/(2^n)) + 0x38 ufix_to_double(v: u32, n: i32) -> f64; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c dcos(angle: f64) -> f64; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 dsin(angle: f64) -> f64; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 dtan(angle: f64) -> f64; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c dexp(v: f64) -> f64; + /// Calculates the natural logarithm of v. If v <= 0 return -Infinity + 0x50 dln(v: f64) -> f64; + + // These are only on BootROM v2 or higher + + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 dcmp(a: f64, b: f64) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 datan2(y: f64, x: f64) -> f64; + /// Converts a signed 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x5c int64_to_double(v: i64) -> f64; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_doubl(v: i64, n: i32) -> f64; + /// Converts an unsigned 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x64 uint64_to_double(v: u64) -> f64; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest double value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_double(v: u64, n: i32) -> f64; + /// Convert an f64 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c double_to_int64(v: f64) -> i64; + /// Converts an f64 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 double_to_fix64(v: f64, n: i32) -> i64; + /// Converts an f64 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 double_to_uint64(v: f64) -> u64; + /// Converts an f64 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 double_to_ufix64(v: f64, n: i32) -> u64; + /// Converts an f64 to an f32 + 0x7c double_to_float(v: f64) -> f32; + } +} diff --git a/embassy-rp-fork/src/rom_data/rp235x.rs b/embassy-rp-fork/src/rom_data/rp235x.rs new file mode 100644 index 0000000..c0a1ed6 --- /dev/null +++ b/embassy-rp-fork/src/rom_data/rp235x.rs @@ -0,0 +1,784 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From [Section 5.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the +//! RP2350 datasheet: +//! +//! > Whilst some ROM space is dedicated to the implementation of the boot +//! > sequence and USB/UART boot interfaces, the bootrom also contains public +//! > functions that provide useful RP2350 functionality that may be useful for +//! > any code or runtime running on the device + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp235x-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for the tag which matches the mask. +type RomTableLookupFn = unsafe extern "C" fn(code: u32, mask: u32) -> usize; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_0016 as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_0018 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A2: *const u16 = ROM_TABLE_LOOKUP_A2; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On Arm, the same function is used to look up code and data. +#[cfg(all(target_arch = "arm", target_os = "none"))] +const ROM_DATA_LOOKUP_A1: *const u32 = ROM_TABLE_LOOKUP_A1; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A2: *const u16 = 0x0000_7DFA as _; + +/// Pointer to the value lookup function supplied by the ROM. +/// +/// This address is described at `5.5.1. Locating the API Functions` +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_TABLE_LOOKUP_A1: *const u32 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A2: *const u16 = 0x0000_7DF8 as _; + +/// Pointer to the data lookup function supplied by the ROM. +/// +/// On RISC-V, a different function is used to look up data. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +const ROM_DATA_LOOKUP_A1: *const u32 = 0x0000_7DF4 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +#[allow(unused)] +mod rt_flags { + pub const FUNC_RISCV: u32 = 0x0001; + pub const FUNC_RISCV_FAR: u32 = 0x0003; + pub const FUNC_ARM_SEC: u32 = 0x0004; + // reserved for 32-bit pointer: 0x0008 + pub const FUNC_ARM_NONSEC: u32 = 0x0010; + // reserved for 32-bit pointer: 0x0020 + pub const DATA: u32 = 0x0040; + // reserved for 32-bit pointer: 0x0080 + #[cfg(all(target_arch = "arm", target_os = "none"))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_ARM_SEC; + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + pub const FUNC_ARM_SEC_RISCV: u32 = FUNC_RISCV; +} + +/// Retrieve rom content from a table using a code. +pub fn rom_table_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_TABLE_LOOKUP_A1.read() as usize + } else { + ROM_TABLE_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +/// Retrieve rom data content from a table using a code. +pub fn rom_data_lookup(tag: RomFnTableCode, mask: u32) -> usize { + let tag = u16::from_le_bytes(tag) as u32; + unsafe { + let lookup_func = if rom_version_number() == 1 { + ROM_DATA_LOOKUP_A1.read() as usize + } else { + ROM_DATA_LOOKUP_A2.read() as usize + }; + let lookup_func: RomTableLookupFn = core::mem::transmute(lookup_func); + lookup_func(tag, mask) + } +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + pub extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: usize = $lookup; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> unsafe extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{AtomicU16, Ordering}; + + // All pointers in the ROM fit in 16 bits, so we don't need a + // full width word to store the cached value. + static CACHED_PTR: AtomicU16 = AtomicU16::new(0); + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + let p: usize = match CACHED_PTR.load(Ordering::Relaxed) { + 0 => { + let raw: usize = $lookup; + CACHED_PTR.store(raw as u16, Ordering::Relaxed); + raw + }, + val => val as usize, + }; + unsafe { + let func : unsafe extern "C" fn( $($argname: $ty),* ) -> $ret = core::mem::transmute(p); + func + } + } + } + + $(#[$outer])* + /// # Safety + /// + /// This is a low-level C function. It may be difficult to call safely from + /// Rust. If in doubt, check the rp235x datasheet for details and do your own + /// safety evaluation. + pub unsafe extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +// **************** 5.5.7 Low-level Flash Commands **************** + +declare_rom_function! { + /// Restore all QSPI pad controls to their default state, and connect the + /// QMI peripheral to the QSPI pads. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn connect_internal_flash() -> () { + crate::rom_data::rom_table_lookup(*b"IF", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Initialise the QMI for serial operations (direct mode) + /// + /// Also initialise a basic XIP mode, where the QMI will perform 03h serial + /// read commands at low speed (CLKDIV=12) in response to XIP reads. + /// + /// Then, issue a sequence to the QSPI device on chip select 0, designed to + /// return it from continuous read mode ("XIP mode") and/or QPI mode to a + /// state where it will accept serial commands. This is necessary after + /// system reset to restore the QSPI device to a known state, because + /// resetting RP2350 does not reset attached QSPI devices. It is also + /// necessary when user code, having already performed some + /// continuous-read-mode or QPI-mode accesses, wishes to return the QSPI + /// device to a state where it will accept the serial erase and programming + /// commands issued by the bootrom’s flash access functions. + /// + /// If a GPIO for the secondary chip select is configured via FLASH_DEVINFO, + /// then the XIP exit sequence is also issued to chip select 1. + /// + /// The QSPI device should be accessible for XIP reads after calling this + /// function; the name flash_exit_xip refers to returning the QSPI device + /// from its XIP state to a serial command state. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_exit_xip() -> () { + crate::rom_data::rom_table_lookup(*b"EX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Erase count bytes, starting at addr (offset from start of flash). + /// + /// Optionally, pass a block erase command e.g. D8h block erase, and the + /// size of the block erased by this command — this function will use the + /// larger block erase where possible, for much higher erase speed. addr + /// must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API, which can be achieved by calling connect_internal_flash() followed + /// by flash_exit_xip(). After the erase, the flash cache should be flushed + /// via flash_flush_cache() to ensure the modified flash data is visible to + /// cached XIP accesses. + /// + /// Finally, the original XIP mode should be restored by copying the saved + /// XIP setup function from bootram into SRAM, and executing it: the bootrom + /// provides a default function which restores the flash mode/clkdiv + /// discovered during flash scanning, and user programs can override this + /// with their own XIP setup function. + /// + /// For the duration of the erase operation, QMI is in direct mode (Section + /// 12.14.5) and attempting to access XIP from DMA, the debugger or the + /// other core will return a bus fault. XIP becomes accessible again once + /// the function returns. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> () { + crate::rom_data::rom_table_lookup(*b"RE", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Program data to a range of flash storage addresses starting at addr + /// (offset from the start of flash) and count bytes in size. + /// + /// `addr` must be aligned to a 256-byte boundary, and count must be a + /// multiple of 256. + /// + /// This is a low-level flash API, and no validation of the arguments is + /// performed. See flash_op() for a higher-level API which checks alignment, + /// flash bounds and partition permissions, and can transparently apply a + /// runtime-to-storage address translation. + /// + /// The QSPI device must be in a serial command state before calling this + /// API — see notes on flash_range_erase(). + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> () { + crate::rom_data::rom_table_lookup(*b"RP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Flush the entire XIP cache, by issuing an invalidate by set/way + /// maintenance operation to every cache line (Section 4.4.1). + /// + /// This ensures that flash program/erase operations are visible to + /// subsequent cached XIP reads. + /// + /// Note that this unpins pinned cache lines, which may interfere with + /// cache-as-SRAM use of the XIP cache. + /// + /// No other operations are performed. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_flush_cache() -> () { + crate::rom_data::rom_table_lookup(*b"FC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure the QMI to generate a standard 03h serial read command, with + /// 24 address bits, upon each XIP access. + /// + /// This is a slow XIP configuration, but is widely supported. CLKDIV is set + /// to 12. The debugger may call this function to ensure that flash is + /// readable following a program/erase operation. + /// + /// Note that the same setup is performed by flash_exit_xip(), and the + /// RP2350 flash program/erase functions do not leave XIP in an inaccessible + /// state, so calls to this function are largely redundant. It is provided + /// for compatibility with RP2040. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_enter_cmd_xip() -> () { + crate::rom_data::rom_table_lookup(*b"CX", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Configure QMI for one of a small menu of XIP read modes supported by the + /// bootrom. This mode is configured for both memory windows (both chip + /// selects), and the clock divisor is also applied to direct mode. + /// + /// The available modes are: + /// + /// * 0: `03h` serial read: serial address, serial data, no wait cycles + /// * 1: `0Bh` serial read: serial address, serial data, 8 wait cycles + /// * 2: `BBh` dual-IO read: dual address, dual data, 4 wait cycles + /// (including MODE bits, which are driven to 0) + /// * 3: `EBh` quad-IO read: quad address, quad data, 6 wait cycles + /// (including MODE bits, which are driven to 0) + /// + /// The XIP write command/format are not configured by this function. When + /// booting from flash, the bootrom tries each of these modes in turn, from + /// 3 down to 0. The first mode that is found to work is remembered, and a + /// default XIP setup function is written into bootram that calls this + /// function (flash_select_xip_read_mode) with the parameters discovered + /// during flash scanning. This can be called at any time to restore the + /// flash parameters discovered during flash boot. + /// + /// All XIP modes configured by the bootrom have an 8-bit serial command + /// prefix, so that the flash can remain in a serial command state, meaning + /// XIP accesses can be mixed more freely with program/erase serial + /// operations. This has a performance penalty, so users can perform their + /// own flash setup after flash boot using continuous read mode or QPI mode + /// to avoid or alleviate the command prefix cost. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_select_xip_read_mode(bootrom_xip_mode: u8, clkdiv: u8) -> () { + crate::rom_data::rom_table_lookup(*b"XM", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Restore the QMI address translation registers, ATRANS0 through ATRANS7, + /// to their reset state. This makes the runtime- to-storage address map an + /// identity map, i.e. the mapped and unmapped address are equal, and the + /// entire space is fully mapped. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_reset_address_trans() -> () { + crate::rom_data::rom_table_lookup(*b"RA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** High-level Flash Commands **************** + +declare_rom_function! { + /// Applies the address translation currently configured by QMI address + /// translation registers, ATRANS0 through ATRANS7. + /// + /// See [Section 12.14.4](https://rptl.io/rp2350-datasheet#section_bootrom) of the RP2350 + /// datasheet. + /// + /// Translating an address outside of the XIP runtime address window, or + /// beyond the bounds of an ATRANSx_SIZE field, returns + /// BOOTROM_ERROR_INVALID_ADDRESS, which is not a valid flash storage + /// address. Otherwise, return the storage address which QMI would access + /// when presented with the runtime address addr. This is effectively a + /// virtual-to-physical address translation for QMI. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_runtime_to_storage_addr(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_runtime_to_storage_addr()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_runtime_to_storage_addr_ns(addr: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"FA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Perform a flash read, erase, or program operation. + /// + /// Erase operations must be sector-aligned (4096 bytes) and sector- + /// multiple-sized, and program operations must be page-aligned (256 bytes) + /// and page-multiple-sized; misaligned erase and program operations will + /// return BOOTROM_ERROR_BAD_ALIGNMENT. The operation — erase, read, program + /// — is selected by the CFLASH_OP_BITS bitfield of the flags argument. + /// + /// See datasheet section 5.5.8.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn flash_op(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [flash_op()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn flash_op_ns(flags: u32, addr: u32, size_bytes: u32, buffer: *mut u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"FO", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Security Related Functions **************** + +declare_rom_function! { + /// Allow or disallow the specific NS API (note all NS APIs default to + /// disabled). + /// + /// See datasheet section 5.5.9.1 for more details. + /// + /// Supported architectures: ARM-S + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn set_ns_api_permission(ns_api_num: u32, allowed: u8) -> i32 { + crate::rom_data::rom_table_lookup(*b"SP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC) + } +} + +declare_rom_function! { + /// Utility method that can be used by secure ARM code to validate a buffer + /// passed to it from Non-secure code. + /// + /// See datasheet section 5.5.9.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn validate_ns_buffer() -> () { + crate::rom_data::rom_table_lookup(*b"VB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Miscellaneous Functions **************** + +declare_rom_function! { + /// Resets the RP2350 and uses the watchdog facility to restart. + /// + /// See datasheet section 5.5.10.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + fn reboot(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [reboot()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + fn reboot_ns(flags: u32, delay_ms: u32, p0: u32, p1: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"RB", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Resets internal bootrom state. + /// + /// See datasheet section 5.5.10.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn bootrom_state_reset(flags: u32) -> () { + crate::rom_data::rom_table_lookup(*b"SR", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Set a boot ROM callback. + /// + /// The only supported callback_number is 0 which sets the callback used for + /// the secure_call API. + /// + /// See datasheet section 5.5.10.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn set_rom_callback(callback_number: i32, callback_fn: *const ()) -> i32 { + crate::rom_data::rom_table_lookup(*b"RC", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** System Information Functions **************** + +declare_rom_function! { + /// Fills a buffer with various system information. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_sys_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_sys_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_sys_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GS", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Fills a buffer with information from the partition table. + /// + /// Note that this API is also used to return information over the PICOBOOT + /// interface. + /// + /// See datasheet section 5.5.11.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_partition_table_info(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [get_partition_table_info()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn get_partition_table_info_ns(out_buffer: *mut u32, out_buffer_word_size: usize, flags_and_partition: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GP", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +declare_rom_function! { + /// Loads the current partition table from flash, if present. + /// + /// See datasheet section 5.5.11.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn load_partition_table(workarea_base: *mut u8, workarea_size: usize, force_reload: bool) -> i32 { + crate::rom_data::rom_table_lookup(*b"LP", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Writes data from a buffer into OTP, or reads data from OTP into a buffer. + /// + /// See datasheet section 5.5.11.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn otp_access(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Non-secure version of [otp_access()] + /// + /// Supported architectures: ARM-NS + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn otp_access_ns(buf: *mut u8, buf_len: usize, row_and_flags: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"OA", crate::rom_data::inner::rt_flags::FUNC_ARM_NONSEC) + } +} + +// **************** Boot Related Functions **************** + +declare_rom_function! { + /// Determines which of the partitions has the "better" IMAGE_DEF. In the + /// case of executable images, this is the one that would be booted. + /// + /// See datasheet section 5.5.12.1 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn pick_ab_parition(workarea_base: *mut u8, workarea_size: usize, partition_a_num: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"AB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Searches a memory region for a launchable image, and executes it if + /// possible. + /// + /// See datasheet section 5.5.12.2 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn chain_image(workarea_base: *mut u8, workarea_size: usize, region_base: i32, region_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"CI", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Perform an "explicit" buy of an executable launched via an IMAGE_DEF + /// which was "explicit buy" flagged. + /// + /// See datasheet section 5.5.12.3 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn explicit_buy(buffer: *mut u8, buffer_size: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"EB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Not yet documented. + /// + /// See datasheet section 5.5.12.4 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_uf2_target_partition(workarea_base: *mut u8, workarea_size: usize, family_id: u32, partition_out: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GU", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +declare_rom_function! { + /// Returns: The index of the B partition of partition A if a partition + /// table is present and loaded, and there is a partition A with a B + /// partition; otherwise returns BOOTROM_ERROR_NOT_FOUND. + /// + /// See datasheet section 5.5.12.5 for more details. + /// + /// Supported architectures: ARM-S, RISC-V + unsafe fn get_b_partition(partition_a: u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"GB", crate::rom_data::inner::rt_flags::FUNC_ARM_SEC_RISCV) + } +} + +// **************** Non-secure-specific Functions **************** + +// NB: The "secure_call" function should be here, but it doesn't have a fixed +// function signature as it is designed to let you bounce into any secure +// function from non-secure mode. + +// **************** RISC-V Functions **************** + +declare_rom_function! { + /// Set stack for RISC-V bootrom functions to use. + /// + /// See datasheet section 5.5.14.1 for more details. + /// + /// Supported architectures: RISC-V + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn set_bootrom_stack(base_size: *mut u32) -> i32 { + crate::rom_data::rom_table_lookup(*b"SS", crate::rom_data::inner::rt_flags::FUNC_RISCV) + } +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let ptr = rom_data_lookup(*b"GR", rt_flags::DATA) as *const u32; + unsafe { ptr.read() } +} + +/// A pointer to the resident partition table info. +/// +/// The resident partition table is the subset of the full partition table that +/// is kept in memory, and used for flash permissions. +pub fn partition_table_pointer() -> *const u32 { + let ptr = rom_data_lookup(*b"PT", rt_flags::DATA) as *const *const u32; + unsafe { ptr.read() } +} + +/// Determine if we are in secure mode +/// +/// Returns `true` if we are in secure mode and `false` if we are in non-secure +/// mode. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub fn is_secure_mode() -> bool { + // Look at the start of ROM, which is always readable + #[allow(clippy::zero_ptr)] + let rom_base: *mut u32 = 0x0000_0000 as *mut u32; + // Use the 'tt' instruction to check the permissions for that address + let tt = cortex_m::asm::tt(rom_base); + // Is the secure bit set? => secure mode + (tt & (1 << 22)) != 0 +} + +/// Determine if we are in secure mode +/// +/// Always returns `false` on RISC-V as it is impossible to determine if +/// you are in Machine Mode or User Mode by design. +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub fn is_secure_mode() -> bool { + false +} + +// These and the reset_to_usb_boot function are found from https://github.com/raspberrypi/pico-sdk/blob/master/src/rp2_common/pico_bootrom/bootrom.c#L35-L51 +// The following has just been translated to rust from the original c++ +const BOOTSEL_FLAG_GPIO_PIN_SPECIFIED: u32 = 0x20; +const REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL: u32 = 0x2; +const REBOOT2_FLAG_NO_RETURN_ON_SUCCESS: u32 = 0x100; + +/// Resets the RP235x and uses the watchdog facility to re-start in BOOTSEL mode: +/// * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED +/// for the USB Mass Storage Device: +/// * 0 No pins are used as per cold boot. +/// * Otherwise a single bit set indicating which GPIO pin should be set to output and +/// raised whenever there is mass storage activity from the host. +/// * disable_interface_mask may be used to control the exposed USB interfaces: +/// * 0 To enable both interfaces (as per cold boot). +/// * 1 To disable the USB Mass Storage Interface. +/// * 2 to Disable the USB PICOBOOT Interface. +pub fn reset_to_usb_boot(mut usb_activity_gpio_pin_mask: u32, disable_interface_mask: u32) { + let mut flags = disable_interface_mask; + + if usb_activity_gpio_pin_mask != 0 { + flags = flags | BOOTSEL_FLAG_GPIO_PIN_SPECIFIED; + usb_activity_gpio_pin_mask = usb_activity_gpio_pin_mask.trailing_zeros() + } + + reboot( + REBOOT2_FLAG_REBOOT_TYPE_BOOTSEL | REBOOT2_FLAG_NO_RETURN_ON_SUCCESS, + 10, + flags, + usb_activity_gpio_pin_mask, + ); +} diff --git a/embassy-rp-fork/src/rtc/datetime_chrono.rs b/embassy-rp-fork/src/rtc/datetime_chrono.rs new file mode 100644 index 0000000..2818e46 --- /dev/null +++ b/embassy-rp-fork/src/rtc/datetime_chrono.rs @@ -0,0 +1,62 @@ +use chrono::{Datelike, Timelike}; + +use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1}; + +/// Alias for [`chrono::NaiveDateTime`] +pub type DateTime = chrono::NaiveDateTime; +/// Alias for [`chrono::Weekday`] +pub type DayOfWeek = chrono::Weekday; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] has an invalid year. The year must be between 0 and 4095. + InvalidYear, + /// The [DateTime] contains an invalid date. + InvalidDate, + /// The [DateTime] contains an invalid time. + InvalidTime, +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw.num_days_from_sunday() as u8 +} + +pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year() < 0 || dt.year() > 4095 { + // rp2040 can't hold these years + Err(Error::InvalidYear) + } else { + // The rest of the chrono date is assumed to be valid + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) { + w.set_year(dt.year() as u16); + w.set_month(dt.month() as u8); + w.set_day(dt.day() as u8); +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) { + w.set_dotw(dt.weekday().num_days_from_sunday() as u8); + w.set_hour(dt.hour() as u8); + w.set_min(dt.minute() as u8); + w.set_sec(dt.second() as u8); +} + +pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result { + let year = rtc_1.year() as i32; + let month = rtc_1.month() as u32; + let day = rtc_1.day() as u32; + + let hour = rtc_0.hour() as u32; + let minute = rtc_0.min() as u32; + let second = rtc_0.sec() as u32; + + let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?; + let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?; + Ok(DateTime::new(date, time)) +} diff --git a/embassy-rp-fork/src/rtc/datetime_no_deps.rs b/embassy-rp-fork/src/rtc/datetime_no_deps.rs new file mode 100644 index 0000000..77d4a30 --- /dev/null +++ b/embassy-rp-fork/src/rtc/datetime_no_deps.rs @@ -0,0 +1,129 @@ +use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1}; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +#[derive(Clone, Debug)] +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[allow(missing_docs)] +pub enum DayOfWeek { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Sunday, + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) { + w.set_year(dt.year); + w.set_month(dt.month); + w.set_day(dt.day); +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) { + w.set_dotw(dt.day_of_week as u8); + w.set_hour(dt.hour); + w.set_min(dt.minute); + w.set_sec(dt.second); +} + +pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result { + let year = rtc_1.year(); + let month = rtc_1.month(); + let day = rtc_1.day(); + + let day_of_week = rtc_0.dotw(); + let hour = rtc_0.hour(); + let minute = rtc_0.min(); + let second = rtc_0.sec(); + + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} diff --git a/embassy-rp-fork/src/rtc/filter.rs b/embassy-rp-fork/src/rtc/filter.rs new file mode 100644 index 0000000..4330536 --- /dev/null +++ b/embassy-rp-fork/src/rtc/filter.rs @@ -0,0 +1,101 @@ +use super::DayOfWeek; +use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1}; + +/// A filter used for [`RealTimeClock::schedule_alarm`]. +/// +/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DateTimeFilter { + /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value. + pub year: Option, + /// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value. + pub month: Option, + /// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value. + pub day: Option, + /// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value. + pub day_of_week: Option, + /// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value. + pub hour: Option, + /// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value. + pub minute: Option, + /// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value. + pub second: Option, +} + +impl DateTimeFilter { + /// Set a filter on the given year + pub fn year(mut self, year: u16) -> Self { + self.year = Some(year); + self + } + /// Set a filter on the given month + pub fn month(mut self, month: u8) -> Self { + self.month = Some(month); + self + } + /// Set a filter on the given day + pub fn day(mut self, day: u8) -> Self { + self.day = Some(day); + self + } + /// Set a filter on the given day of the week + pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self { + self.day_of_week = Some(day_of_week); + self + } + /// Set a filter on the given hour + pub fn hour(mut self, hour: u8) -> Self { + self.hour = Some(hour); + self + } + /// Set a filter on the given minute + pub fn minute(mut self, minute: u8) -> Self { + self.minute = Some(minute); + self + } + /// Set a filter on the given second + pub fn second(mut self, second: u8) -> Self { + self.second = Some(second); + self + } +} + +// register helper functions +impl DateTimeFilter { + pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) { + if let Some(year) = self.year { + w.set_year_ena(true); + + w.set_year(year); + } + if let Some(month) = self.month { + w.set_month_ena(true); + w.set_month(month); + } + if let Some(day) = self.day { + w.set_day_ena(true); + w.set_day(day); + } + } + pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) { + if let Some(day_of_week) = self.day_of_week { + w.set_dotw_ena(true); + let bits = super::datetime::day_of_week_to_u8(day_of_week); + + w.set_dotw(bits); + } + if let Some(hour) = self.hour { + w.set_hour_ena(true); + w.set_hour(hour); + } + if let Some(minute) = self.minute { + w.set_min_ena(true); + w.set_min(minute); + } + if let Some(second) = self.second { + w.set_sec_ena(true); + w.set_sec(second); + } + } +} diff --git a/embassy-rp-fork/src/rtc/mod.rs b/embassy-rp-fork/src/rtc/mod.rs new file mode 100644 index 0000000..0545729 --- /dev/null +++ b/embassy-rp-fork/src/rtc/mod.rs @@ -0,0 +1,323 @@ +//! RTC driver. +mod filter; + +use core::future::poll_fn; +use core::sync::atomic::{AtomicBool, Ordering, compiler_fence}; +use core::task::Poll; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +pub use self::filter::DateTimeFilter; + +#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")] +#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")] +mod datetime; + +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; +use crate::clocks::clk_rtc_freq; +use crate::interrupt::typelevel::Binding; +use crate::interrupt::{self, InterruptExt}; + +// Static waker for the interrupt handler +static WAKER: AtomicWaker = AtomicWaker::new(); +// Static flag to indicate if an alarm has occurred +static ALARM_OCCURRED: AtomicBool = AtomicBool::new(false); + +/// A reference to the real time clock of the system +pub struct Rtc<'d, T: Instance> { + inner: Peri<'d, T>, +} + +impl<'d, T: Instance> Rtc<'d, T> { + /// Create a new instance of the real time clock, with the given date as an initial value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn new(inner: Peri<'d, T>, _irq: impl Binding) -> Self { + // Set the RTC divider + inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); + + // Setup the IRQ + // Clear any pending interrupts from the RTC_IRQ interrupt and enable it, so we do not have unexpected interrupts after initialization + interrupt::RTC_IRQ.unpend(); + unsafe { interrupt::RTC_IRQ.enable() }; + + Self { inner } + } + + /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisible by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check. + /// + /// Leap year checking is enabled by default. + pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) { + self.inner.regs().ctrl().modify(|w| { + w.set_force_notleapyear(!leap_year_check_enabled); + }); + } + + /// Set the time from internal format + pub fn restore(&mut self, ymd: rp_pac::rtc::regs::Rtc1, hms: rp_pac::rtc::regs::Rtc0) { + // disable RTC while we configure it + self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false)); + while self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + + self.inner.regs().setup_0().write(|w| { + *w = rp_pac::rtc::regs::Setup0(ymd.0); + }); + self.inner.regs().setup_1().write(|w| { + *w = rp_pac::rtc::regs::Setup1(hms.0); + }); + + // Load the new datetime and re-enable RTC + self.inner.regs().ctrl().write(|w| w.set_load(true)); + self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true)); + while !self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + } + + /// Get the time in internal format + pub fn save(&mut self) -> (rp_pac::rtc::regs::Rtc1, rp_pac::rtc::regs::Rtc0) { + let rtc_0: rp_pac::rtc::regs::Rtc0 = self.inner.regs().rtc_0().read(); + let rtc_1 = self.inner.regs().rtc_1().read(); + (rtc_1, rtc_0) + } + + /// Checks to see if this Rtc is running + pub fn is_running(&self) -> bool { + self.inner.regs().ctrl().read().rtc_active() + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?; + + // disable RTC while we configure it + self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false)); + while self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + + self.inner.regs().setup_0().write(|w| { + self::datetime::write_setup_0(&t, w); + }); + self.inner.regs().setup_1().write(|w| { + self::datetime::write_setup_1(&t, w); + }); + + // Load the new datetime and re-enable RTC + self.inner.regs().ctrl().write(|w| w.set_load(true)); + self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true)); + while !self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + if !self.is_running() { + return Err(RtcError::NotRunning); + } + + let rtc_0 = self.inner.regs().rtc_0().read(); + let rtc_1 = self.inner.regs().rtc_1().read(); + + self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime) + } + + /// Disable the alarm that was scheduled with [`schedule_alarm`]. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn disable_alarm(&mut self) { + self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false)); + + while self.inner.regs().irq_setup_0().read().match_active() { + core::hint::spin_loop(); + } + } + + /// Schedule an alarm. The `filter` determines at which point in time this alarm is set. + /// + /// Keep in mind that the filter only triggers on the specified time. If you want to schedule this alarm every minute, you have to call: + /// ```no_run + /// # #[cfg(feature = "chrono")] + /// # fn main() { } + /// # #[cfg(not(feature = "chrono"))] + /// # fn main() { + /// # use embassy_rp::rtc::{Rtc, DateTimeFilter}; + /// # let mut real_time_clock: Rtc = unsafe { core::mem::zeroed() }; + /// let now = real_time_clock.now().unwrap(); + /// real_time_clock.schedule_alarm( + /// DateTimeFilter::default() + /// .minute(if now.minute == 59 { 0 } else { now.minute + 1 }) + /// ); + /// # } + /// ``` + pub fn schedule_alarm(&mut self, filter: DateTimeFilter) { + self.disable_alarm(); + + self.inner.regs().irq_setup_0().write(|w| { + filter.write_setup_0(w); + }); + self.inner.regs().irq_setup_1().write(|w| { + filter.write_setup_1(w); + }); + + self.inner.regs().inte().modify(|w| w.set_rtc(true)); + + // Set the enable bit and check if it is set + self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true)); + while !self.inner.regs().irq_setup_0().read().match_active() { + core::hint::spin_loop(); + } + } + + /// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered, + /// or the next [`schedule_alarm`] will never fire. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn clear_interrupt(&mut self) { + self.disable_alarm(); + } + + /// Check if an alarm is scheduled. + /// + /// This function checks if the RTC alarm is currently active. If it is, it returns the alarm configuration + /// as a [`DateTimeFilter`]. Otherwise, it returns `None`. + pub fn alarm_scheduled(&self) -> Option { + // Check if alarm is active + if !self.inner.regs().irq_setup_0().read().match_active() { + return None; + } + + // Get values from both alarm registers + let irq_0 = self.inner.regs().irq_setup_0().read(); + let irq_1 = self.inner.regs().irq_setup_1().read(); + + // Create a DateTimeFilter and populate it based on which fields are enabled + let mut filter = DateTimeFilter::default(); + + if irq_0.year_ena() { + filter.year = Some(irq_0.year()); + } + + if irq_0.month_ena() { + filter.month = Some(irq_0.month()); + } + + if irq_0.day_ena() { + filter.day = Some(irq_0.day()); + } + + if irq_1.dotw_ena() { + // Convert day of week value to DayOfWeek enum + let day_of_week = match irq_1.dotw() { + 0 => DayOfWeek::Sunday, + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + _ => return None, // Invalid day of week + }; + filter.day_of_week = Some(day_of_week); + } + + if irq_1.hour_ena() { + filter.hour = Some(irq_1.hour()); + } + + if irq_1.min_ena() { + filter.minute = Some(irq_1.min()); + } + + if irq_1.sec_ena() { + filter.second = Some(irq_1.sec()); + } + + Some(filter) + } + + /// Wait for an RTC alarm to trigger. + /// + /// This function will wait until the RTC alarm is triggered. If the alarm is already triggered, it will return immediately. + /// If no alarm is scheduled, it will wait indefinitely until one is scheduled and triggered. + pub async fn wait_for_alarm(&mut self) { + poll_fn(|cx| { + WAKER.register(cx.waker()); + + // Atomically check and clear the alarm occurred flag to prevent race conditions + if critical_section::with(|_| { + let occurred = ALARM_OCCURRED.load(Ordering::SeqCst); + if occurred { + ALARM_OCCURRED.store(false, Ordering::SeqCst); + } + occurred + }) { + // Clear the interrupt and disable the alarm + self.clear_interrupt(); + + compiler_fence(Ordering::SeqCst); + return Poll::Ready(()); + } else { + return Poll::Pending; + } + }) + .await; + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _empty: (), +} + +impl crate::interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + // Disable the alarm first thing, to prevent unexpected re-entry + let rtc = crate::pac::RTC; + rtc.irq_setup_0().modify(|w| w.set_match_ena(false)); + + // Set the alarm occurred flag and wake the waker + ALARM_OCCURRED.store(true, Ordering::SeqCst); + WAKER.wake(); + } +} + +/// Errors that can occur on methods on [Rtc] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The RTC clock is not running + NotRunning, +} + +trait SealedInstance { + fn regs(&self) -> crate::pac::rtc::Rtc; +} + +/// RTC peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType {} + +impl SealedInstance for crate::peripherals::RTC { + fn regs(&self) -> crate::pac::rtc::Rtc { + crate::pac::RTC + } +} +impl Instance for crate::peripherals::RTC {} diff --git a/embassy-rp-fork/src/spi.rs b/embassy-rp-fork/src/spi.rs new file mode 100644 index 0000000..39f1282 --- /dev/null +++ b/embassy-rp-fork/src/spi.rs @@ -0,0 +1,745 @@ +//! Serial Peripheral Interface +use core::marker::PhantomData; + +use embassy_embedded_hal::SetConfig; +use embassy_futures::join::join; +use embassy_hal_internal::{Peri, PeripheralType}; +pub use embedded_hal_02::spi::{Phase, Polarity}; + +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::{AnyPin, Pin as GpioPin, SealedPin as _}; +use crate::{pac, peripherals}; + +/// SPI errors. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now +} + +/// SPI configuration. +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + /// Frequency. + pub frequency: u32, + /// Phase. + pub phase: Phase, + /// Polarity. + pub polarity: Polarity, +} + +impl Default for Config { + fn default() -> Self { + Self { + frequency: 1_000_000, + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, + } + } +} + +/// SPI driver. +pub struct Spi<'d, T: Instance, M: Mode> { + inner: Peri<'d, T>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +fn div_roundup(a: u32, b: u32) -> u32 { + (a + b - 1) / b +} + +fn calc_prescs(freq: u32) -> (u8, u8) { + let clk_peri = crate::clocks::clk_peri_freq(); + + // final SPI frequency: spi_freq = clk_peri / presc / postdiv + // presc must be in 2..=254, and must be even + // postdiv must be in 1..=256 + + // divide extra by 2, so we get rid of the "presc must be even" requirement + let ratio = div_roundup(clk_peri, freq * 2); + if ratio > 127 * 256 { + panic!("Requested too low SPI frequency"); + } + + let presc = div_roundup(ratio, 256); + let postdiv = if presc == 1 { ratio } else { div_roundup(ratio, presc) }; + + ((presc * 2) as u8, (postdiv - 1) as u8) +} + +impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { + fn new_inner( + inner: Peri<'d, T>, + clk: Option>, + mosi: Option>, + miso: Option>, + cs: Option>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + Self::apply_config(&inner, &config); + + let p = inner.regs(); + + // Always enable DREQ signals -- harmless if DMA is not listening + p.dmacr().write(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + }); + + // finally, enable. + p.cr1().write(|w| w.set_sse(true)); + + if let Some(pin) = &clk { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + if let Some(pin) = &mosi { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + if let Some(pin) = &miso { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + if let Some(pin) = &cs { + pin.gpio().ctrl().write(|w| w.set_funcsel(1)); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_schmitt(true); + w.set_slewfast(false); + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(false); + }); + } + Self { + inner, + tx_dma, + rx_dma, + phantom: PhantomData, + } + } + + /// Private function to apply SPI configuration (phase, polarity, frequency) settings. + /// + /// Driver should be disabled before making changes and re-enabled after the modifications + /// are applied. + fn apply_config(inner: &Peri<'d, T>, config: &Config) { + let p = inner.regs(); + let (presc, postdiv) = calc_prescs(config.frequency); + + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().write(|w| { + w.set_dss(0b0111); // 8bit + w.set_spo(config.polarity == Polarity::IdleHigh); + w.set_sph(config.phase == Phase::CaptureOnSecondTransition); + w.set_scr(postdiv); + }); + } + + /// Write data to SPI blocking execution until done. + pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for &b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(b as _)); + while !p.sr().read().rne() {} + let _ = p.dr().read(); + } + self.flush()?; + Ok(()) + } + + /// Transfer data in place to SPI blocking execution until done. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(*b as _)); + while !p.sr().read().rne() {} + *b = p.dr().read().data() as u8; + } + self.flush()?; + Ok(()) + } + + /// Read data from SPI blocking execution until done. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(0)); + while !p.sr().read().rne() {} + *b = p.dr().read().data() as u8; + } + self.flush()?; + Ok(()) + } + + /// Transfer data to SPI blocking execution until done. + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + let p = self.inner.regs(); + let len = read.len().max(write.len()); + for i in 0..len { + let wb = write.get(i).copied().unwrap_or(0); + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(wb as _)); + while !p.sr().read().rne() {} + let rb = p.dr().read().data() as u8; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + self.flush()?; + Ok(()) + } + + /// Block execution until SPI is done. + pub fn flush(&mut self) -> Result<(), Error> { + let p = self.inner.regs(); + while p.sr().read().bsy() {} + Ok(()) + } + + /// Set SPI frequency. + pub fn set_frequency(&mut self, freq: u32) { + let (presc, postdiv) = calc_prescs(freq); + let p = self.inner.regs(); + // disable + p.cr1().write(|w| w.set_sse(false)); + + // change stuff + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().modify(|w| { + w.set_scr(postdiv); + }); + + // enable + p.cr1().write(|w| w.set_sse(true)); + } + + /// Set SPI config. + pub fn set_config(&mut self, config: &Config) { + let p = self.inner.regs(); + + // disable + p.cr1().write(|w| w.set_sse(false)); + + // change stuff + Self::apply_config(&self.inner, config); + + // enable + p.cr1().write(|w| w.set_sse(true)); + } +} + +impl<'d, T: Instance> Spi<'d, T, Blocking> { + /// Create an SPI driver in blocking mode. + pub fn new_blocking( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + Some(mosi.into()), + Some(miso.into()), + None, + None, + None, + config, + ) + } + + /// Create an SPI driver in blocking mode supporting writes only. + pub fn new_blocking_txonly( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + Some(mosi.into()), + None, + None, + None, + None, + config, + ) + } + + /// Create an SPI driver in blocking mode supporting writes only, without SCK pin. + pub fn new_blocking_txonly_nosck(inner: Peri<'d, T>, mosi: Peri<'d, impl MosiPin + 'd>, config: Config) -> Self { + Self::new_inner(inner, None, Some(mosi.into()), None, None, None, None, config) + } + + /// Create an SPI driver in blocking mode supporting reads only. + pub fn new_blocking_rxonly( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + None, + Some(miso.into()), + None, + None, + None, + config, + ) + } +} + +impl<'d, T: Instance> Spi<'d, T, Async> { + /// Create an SPI driver in async mode supporting DMA operations. + pub fn new( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + Some(mosi.into()), + Some(miso.into()), + None, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) + } + + /// Create an SPI driver in async mode supporting DMA write operations only. + pub fn new_txonly( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + mosi: Peri<'d, impl MosiPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + Some(mosi.into()), + None, + None, + Some(tx_dma.into()), + None, + config, + ) + } + + /// Create an SPI driver in async mode supporting DMA write operations only, + /// without SCK pin. + pub fn new_txonly_nosck( + inner: Peri<'d, T>, + mosi: Peri<'d, impl MosiPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + None, + Some(mosi.into()), + None, + None, + Some(tx_dma.into()), + None, + config, + ) + } + + /// Create an SPI driver in async mode supporting DMA read operations only. + pub fn new_rxonly( + inner: Peri<'d, T>, + clk: Peri<'d, impl ClkPin + 'd>, + miso: Peri<'d, impl MisoPin + 'd>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + inner, + Some(clk.into()), + None, + Some(miso.into()), + None, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) + } + + /// Write data to SPI using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(tx_ch, buffer, self.inner.regs().dr().as_ptr() as *mut _, T::TX_DREQ) + }; + tx_transfer.await; + + let p = self.inner.regs(); + while p.sr().read().bsy() {} + + // clear RX FIFO contents to prevent stale reads + while p.sr().read().rne() { + let _: u16 = p.dr().read().data(); + } + // clear RX overrun interrupt + p.icr().write(|w| w.set_roric(true)); + + Ok(()) + } + + /// Read data from SPI using DMA. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // Start RX first. Transfer starts when TX starts, if RX + // is not started yet we might lose bytes. + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, buffer, T::RX_DREQ) + }; + + let tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + let tx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write_repeated( + tx_ch, + self.inner.regs().dr().as_ptr() as *mut u8, + buffer.len(), + T::TX_DREQ, + ) + }; + join(tx_transfer, rx_transfer).await; + Ok(()) + } + + /// Transfer data to SPI using DMA. + pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> { + self.transfer_inner(rx_buffer, tx_buffer).await + } + + /// Transfer data in place to SPI using DMA. + pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_inner(words, words).await + } + + async fn transfer_inner(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { + // Start RX first. Transfer starts when TX starts, if RX + // is not started yet we might lose bytes. + let rx_ch = self.rx_dma.as_mut().unwrap().reborrow(); + let rx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, rx, T::RX_DREQ) + }; + + let mut tx_ch = self.tx_dma.as_mut().unwrap().reborrow(); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + let tx_transfer = async { + let p = self.inner.regs(); + unsafe { + crate::dma::write(tx_ch.reborrow(), tx, p.dr().as_ptr() as *mut _, T::TX_DREQ).await; + + if rx.len() > tx.len() { + let write_bytes_len = rx.len() - tx.len(); + // write dummy data + // this will disable incrementation of the buffers + crate::dma::write_repeated(tx_ch, p.dr().as_ptr() as *mut u8, write_bytes_len, T::TX_DREQ).await + } + } + }; + join(tx_transfer, rx_transfer).await; + + // if tx > rx we should clear any overflow of the FIFO SPI buffer + if tx.len() > rx.len() { + let p = self.inner.regs(); + while p.sr().read().bsy() {} + + // clear RX FIFO contents to prevent stale reads + while p.sr().read().rne() { + let _: u16 = p.dr().read().data(); + } + // clear RX overrun interrupt + p.icr().write(|w| w.set_roric(true)); + } + + Ok(()) + } +} + +trait SealedMode {} + +trait SealedInstance { + const TX_DREQ: pac::dma::vals::TreqSel; + const RX_DREQ: pac::dma::vals::TreqSel; + + fn regs(&self) -> pac::spi::Spi; +} + +/// Mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +/// SPI instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType {} + +macro_rules! impl_instance { + ($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl SealedInstance for peripherals::$type { + const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; + const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; + + fn regs(&self) -> pac::spi::Spi { + pac::$type + } + } + impl Instance for peripherals::$type {} + }; +} + +impl_instance!( + SPI0, + Spi0, + pac::dma::vals::TreqSel::SPI0_TX, + pac::dma::vals::TreqSel::SPI0_RX +); +impl_instance!( + SPI1, + Spi1, + pac::dma::vals::TreqSel::SPI1_TX, + pac::dma::vals::TreqSel::SPI1_RX +); + +/// CLK pin. +pub trait ClkPin: GpioPin {} +/// CS pin. +pub trait CsPin: GpioPin {} +/// MOSI pin. +pub trait MosiPin: GpioPin {} +/// MISO pin. +pub trait MisoPin: GpioPin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, SPI0, MisoPin); +impl_pin!(PIN_1, SPI0, CsPin); +impl_pin!(PIN_2, SPI0, ClkPin); +impl_pin!(PIN_3, SPI0, MosiPin); +impl_pin!(PIN_4, SPI0, MisoPin); +impl_pin!(PIN_5, SPI0, CsPin); +impl_pin!(PIN_6, SPI0, ClkPin); +impl_pin!(PIN_7, SPI0, MosiPin); +impl_pin!(PIN_8, SPI1, MisoPin); +impl_pin!(PIN_9, SPI1, CsPin); +impl_pin!(PIN_10, SPI1, ClkPin); +impl_pin!(PIN_11, SPI1, MosiPin); +impl_pin!(PIN_12, SPI1, MisoPin); +impl_pin!(PIN_13, SPI1, CsPin); +impl_pin!(PIN_14, SPI1, ClkPin); +impl_pin!(PIN_15, SPI1, MosiPin); +impl_pin!(PIN_16, SPI0, MisoPin); +impl_pin!(PIN_17, SPI0, CsPin); +impl_pin!(PIN_18, SPI0, ClkPin); +impl_pin!(PIN_19, SPI0, MosiPin); +impl_pin!(PIN_20, SPI0, MisoPin); +impl_pin!(PIN_21, SPI0, CsPin); +impl_pin!(PIN_22, SPI0, ClkPin); +impl_pin!(PIN_23, SPI0, MosiPin); +impl_pin!(PIN_24, SPI1, MisoPin); +impl_pin!(PIN_25, SPI1, CsPin); +impl_pin!(PIN_26, SPI1, ClkPin); +impl_pin!(PIN_27, SPI1, MosiPin); +impl_pin!(PIN_28, SPI1, MisoPin); +impl_pin!(PIN_29, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, SPI1, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, SPI0, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, SPI0, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, SPI0, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, SPI0, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, SPI0, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, SPI0, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, SPI0, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, SPI0, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, SPI1, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, SPI1, MosiPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, SPI1, MisoPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, SPI1, CsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, SPI1, ClkPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, SPI1, MosiPin); + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +// ==================== + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, T, M> { + type Error = Error; + fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { + self.blocking_transfer_in_place(words)?; + Ok(words) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Write for Spi<'d, T, M> { + type Error = Error; + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } +} + +impl embedded_hal_1::spi::Error for Error { + fn kind(&self) -> embedded_hal_1::spi::ErrorKind { + match *self {} + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBus for Spi<'d, T, M> { + fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer(words, &[]) + } + + fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(words) + } + + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.blocking_transfer(read, write) + } + + fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_transfer_in_place(words) + } +} + +impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spi<'d, T, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await + } +} + +impl<'d, T: Instance, M: Mode> SetConfig for Spi<'d, T, M> { + type Config = Config; + type ConfigError = (); + fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> { + self.set_config(config); + + Ok(()) + } +} diff --git a/embassy-rp-fork/src/spinlock.rs b/embassy-rp-fork/src/spinlock.rs new file mode 100644 index 0000000..7effd2a --- /dev/null +++ b/embassy-rp-fork/src/spinlock.rs @@ -0,0 +1,75 @@ +use crate::pac; + +pub struct Spinlock(core::marker::PhantomData<()>) +where + Spinlock: SpinlockValid; + +impl Spinlock +where + Spinlock: SpinlockValid, +{ + /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is + /// already in use somewhere else. + pub fn try_claim() -> Option { + let lock = pac::SIO.spinlock(N).read(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + // Write (any value): release the lock + pac::SIO.spinlock(N).write_value(1); + } +} + +impl Drop for Spinlock +where + Spinlock: SpinlockValid, +{ + fn drop(&mut self) { + // This is safe because we own the object, and hence hold the lock. + unsafe { Self::release() } + } +} + +pub trait SpinlockValid {} +impl SpinlockValid for Spinlock<0> {} +impl SpinlockValid for Spinlock<1> {} +impl SpinlockValid for Spinlock<2> {} +impl SpinlockValid for Spinlock<3> {} +impl SpinlockValid for Spinlock<4> {} +impl SpinlockValid for Spinlock<5> {} +impl SpinlockValid for Spinlock<6> {} +impl SpinlockValid for Spinlock<7> {} +impl SpinlockValid for Spinlock<8> {} +impl SpinlockValid for Spinlock<9> {} +impl SpinlockValid for Spinlock<10> {} +impl SpinlockValid for Spinlock<11> {} +impl SpinlockValid for Spinlock<12> {} +impl SpinlockValid for Spinlock<13> {} +impl SpinlockValid for Spinlock<14> {} +impl SpinlockValid for Spinlock<15> {} +impl SpinlockValid for Spinlock<16> {} +impl SpinlockValid for Spinlock<17> {} +impl SpinlockValid for Spinlock<18> {} +impl SpinlockValid for Spinlock<19> {} +impl SpinlockValid for Spinlock<20> {} +impl SpinlockValid for Spinlock<21> {} +impl SpinlockValid for Spinlock<22> {} +impl SpinlockValid for Spinlock<23> {} +impl SpinlockValid for Spinlock<24> {} +impl SpinlockValid for Spinlock<25> {} +impl SpinlockValid for Spinlock<26> {} +impl SpinlockValid for Spinlock<27> {} +impl SpinlockValid for Spinlock<28> {} +impl SpinlockValid for Spinlock<29> {} +impl SpinlockValid for Spinlock<30> {} +impl SpinlockValid for Spinlock<31> {} diff --git a/embassy-rp-fork/src/spinlock_mutex.rs b/embassy-rp-fork/src/spinlock_mutex.rs new file mode 100644 index 0000000..85174cf --- /dev/null +++ b/embassy-rp-fork/src/spinlock_mutex.rs @@ -0,0 +1,93 @@ +//! Mutex implementation utilizing an hardware spinlock + +use core::marker::PhantomData; +use core::sync::atomic::Ordering; + +use embassy_sync::blocking_mutex::raw::RawMutex; + +use crate::spinlock::{Spinlock, SpinlockValid}; + +/// A mutex that allows borrowing data across executors and interrupts by utilizing an hardware spinlock +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub struct SpinlockRawMutex { + _phantom: PhantomData<()>, +} +unsafe impl Send for SpinlockRawMutex {} +unsafe impl Sync for SpinlockRawMutex {} + +impl SpinlockRawMutex { + /// Create a new `SpinlockRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for SpinlockRawMutex +where + Spinlock: SpinlockValid, +{ + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + // Store the initial interrupt state in stack variable + let interrupts_active = cortex_m::register::primask::read().is_active(); + + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt or higher prio locks the spinlock after we acquire the lock + cortex_m::interrupt::disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + if let Some(lock) = Spinlock::::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + // safety: interrupts are only enabled, if they had been enabled before + unsafe { + cortex_m::interrupt::enable(); + } + } + } + + let retval = f(); + + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to lock mutex again + // safety: this point is only reached a spinlock was acquired before + unsafe { + Spinlock::::release(); + } + + // Re-enable interrupts if they were enabled before the mutex was locked + if interrupts_active { + // safety: interrupts are only enabled, if they had been enabled before + unsafe { + cortex_m::interrupt::enable(); + } + } + + retval + } +} + +pub mod blocking_mutex { + //! Mutex implementation utilizing an hardware spinlock + use embassy_sync::blocking_mutex::Mutex; + + use crate::spinlock_mutex::SpinlockRawMutex; + /// A mutex that allows borrowing data across executors and interrupts by utilizing an hardware spinlock. + /// + /// # Safety + /// + /// This mutex is safe to share between different executors and interrupts. + pub type SpinlockMutex = Mutex, T>; +} diff --git a/embassy-rp-fork/src/time_driver.rs b/embassy-rp-fork/src/time_driver.rs new file mode 100644 index 0000000..ec1c17e --- /dev/null +++ b/embassy-rp-fork/src/time_driver.rs @@ -0,0 +1,144 @@ +//! Timer driver. +use core::cell::{Cell, RefCell}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; +#[cfg(feature = "rp2040")] +use pac::TIMER; +#[cfg(feature = "_rp235x")] +use pac::TIMER0 as TIMER; + +use crate::interrupt::InterruptExt; +use crate::{interrupt, pac}; + +struct AlarmState { + timestamp: Cell, +} +unsafe impl Send for AlarmState {} + +struct TimerDriver { + alarms: Mutex, + queue: Mutex>, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ + alarms: Mutex::const_new(CriticalSectionRawMutex::new(), AlarmState { + timestamp: Cell::new(0), + }), + queue: Mutex::new(RefCell::new(Queue::new())) +}); + +impl Driver for TimerDriver { + fn now(&self) -> u64 { + loop { + let hi = TIMER.timerawh().read(); + let lo = TIMER.timerawl().read(); + let hi2 = TIMER.timerawh().read(); + if hi == hi2 { + return (hi as u64) << 32 | (lo as u64); + } + } + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + critical_section::with(|cs| { + let mut queue = self.queue.borrow(cs).borrow_mut(); + + if queue.schedule_wake(at, waker) { + let mut next = queue.next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = queue.next_expiration(self.now()); + } + } + }) + } +} + +impl TimerDriver { + fn set_alarm(&self, cs: CriticalSection, timestamp: u64) -> bool { + let n = 0; + let alarm = &self.alarms.borrow(cs); + alarm.timestamp.set(timestamp); + + // Arm it. + // Note that we're not checking the high bits at all. This means the irq may fire early + // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire + // it is checked if the alarm time has passed. + TIMER.alarm(n).write_value(timestamp as u32); + + let now = self.now(); + if timestamp <= now { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + TIMER.armed().write(|w| w.set_armed(1 << n)); + + alarm.timestamp.set(u64::MAX); + + false + } else { + true + } + } + + fn check_alarm(&self) { + let n = 0; + critical_section::with(|cs| { + // clear the irq + TIMER.intr().write(|w| w.set_alarm(n, true)); + + let alarm = &self.alarms.borrow(cs); + let timestamp = alarm.timestamp.get(); + if timestamp <= self.now() { + self.trigger_alarm(cs) + } else { + // Not elapsed, arm it again. + // This can happen if it was set more than 2^32 us in the future. + TIMER.alarm(n).write_value(timestamp as u32); + } + }); + } + + fn trigger_alarm(&self, cs: CriticalSection) { + let mut next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + while !self.set_alarm(cs, next) { + next = self.queue.borrow(cs).borrow_mut().next_expiration(self.now()); + } + } +} + +/// safety: must be called exactly once at bootup +pub unsafe fn init() { + // init alarms + critical_section::with(|cs| { + let alarm = DRIVER.alarms.borrow(cs); + alarm.timestamp.set(u64::MAX); + }); + + // enable irq + TIMER.inte().write(|w| { + w.set_alarm(0, true); + }); + #[cfg(feature = "rp2040")] + { + interrupt::TIMER_IRQ_0.enable(); + } + #[cfg(feature = "_rp235x")] + { + interrupt::TIMER0_IRQ_0.enable(); + } +} + +#[cfg(all(feature = "rt", feature = "rp2040"))] +#[interrupt] +fn TIMER_IRQ_0() { + DRIVER.check_alarm() +} + +#[cfg(all(feature = "rt", feature = "_rp235x"))] +#[interrupt] +fn TIMER0_IRQ_0() { + DRIVER.check_alarm() +} diff --git a/embassy-rp-fork/src/trng.rs b/embassy-rp-fork/src/trng.rs new file mode 100644 index 0000000..a3f23c1 --- /dev/null +++ b/embassy-rp-fork/src/trng.rs @@ -0,0 +1,449 @@ +//! True Random Number Generator (TRNG) driver. + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::Not; +use core::task::Poll; + +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::peripherals::TRNG; +use crate::{interrupt, pac}; + +trait SealedInstance { + fn regs() -> pac::trng::Trng; + fn waker() -> &'static AtomicWaker; +} + +/// TRNG peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType { + /// Interrupt for this peripheral. + type Interrupt: Interrupt; +} + +impl SealedInstance for TRNG { + fn regs() -> rp_pac::trng::Trng { + pac::TRNG + } + + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + &WAKER + } +} + +impl Instance for TRNG { + type Interrupt = interrupt::typelevel::TRNG_IRQ; +} + +#[derive(Copy, Clone, Debug)] +#[allow(missing_docs)] +/// TRNG ROSC Inverter chain length options. +pub enum InverterChainLength { + None = 0, + One, + Two, + Three, + Four, +} + +impl From for u8 { + fn from(value: InverterChainLength) -> Self { + value as u8 + } +} + +/// Configuration for the TRNG. +/// +/// - Three built in entropy checks +/// - ROSC frequency controlled by selecting one of ROSC chain lengths +/// - Sample period in terms of system clock ticks +/// +/// +/// Default configuration is based on the following from documentation: +/// +/// ---- +/// +/// RP2350 Datasheet 12.12.2 +/// +/// ... +/// +/// When configuring the TRNG block, consider the following principles: +/// • As average generation time increases, result quality increases and failed entropy checks decrease. +/// • A low sample count decreases average generation time, but increases the chance of NIST test-failing results and +/// failed entropy checks. +/// For acceptable results with an average generation time of about 2 milliseconds, use ROSC chain length settings of 0 or +/// 1 and sample count settings of 20-25. +/// Larger sample count settings (e.g. 100) provide proportionately slower average generation times. These settings +/// significantly reduce, but do not eliminate NIST test failures and entropy check failures. Results occasionally take an +/// especially long time to generate. +/// +/// --- +/// +/// Note, Pico SDK and Bootrom don't use any of the entropy checks and sample the ROSC directly +/// by setting the sample period to 0. Random data collected this way is then passed through +/// either hardware accelerated SHA256 (Bootrom) or xoroshiro128** (version 1.0!). +#[non_exhaustive] +#[derive(Copy, Clone, Debug)] +pub struct Config { + /// Bypass TRNG autocorrelation test + pub disable_autocorrelation_test: bool, + /// Bypass CRNGT test + pub disable_crngt_test: bool, + /// When set, the Von-Neuman balancer is bypassed (including the + /// 32 consecutive bits test) + pub disable_von_neumann_balancer: bool, + /// Sets the number of rng_clk cycles between two consecutive + /// ring oscillator samples. + /// Note: If the von Neumann decorrelator is bypassed, the minimum value for + /// sample counter must not be less than seventeen + pub sample_count: u32, + /// Selects the number of inverters (out of four possible + /// selections) in the ring oscillator (the entropy source). Higher values select + /// longer inverter chain lengths. + pub inverter_chain_length: InverterChainLength, +} + +impl Default for Config { + fn default() -> Self { + Config { + // WARNING: Disabling these tests increases likelihood of poor rng results. + disable_autocorrelation_test: false, + disable_crngt_test: false, + disable_von_neumann_balancer: false, + sample_count: 25, + inverter_chain_length: InverterChainLength::One, + } + } +} + +/// True Random Number Generator Driver for RP2350 +/// +/// This driver provides async and blocking options. +/// +/// See [Config] for configuration details. +/// +/// Usage example: +/// ```no_run +/// use embassy_executor::Spawner; +/// use embassy_rp::trng::Trng; +/// use embassy_rp::peripherals::TRNG; +/// use embassy_rp::bind_interrupts; +/// +/// bind_interrupts!(struct Irqs { +/// TRNG_IRQ => embassy_rp::trng::InterruptHandler; +/// }); +/// +/// #[embassy_executor::main] +/// async fn main(spawner: Spawner) { +/// let peripherals = embassy_rp::init(Default::default()); +/// let mut trng = Trng::new(peripherals.TRNG, Irqs, embassy_rp::trng::Config::default()); +/// +/// let mut randomness = [0u8; 58]; +/// loop { +/// trng.fill_bytes(&mut randomness).await; +/// assert_ne!(randomness, [0u8; 58]); +/// } +///} +/// ``` +pub struct Trng<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + config: Config, +} + +/// 12.12.1. Overview +/// On request, the TRNG block generates a block of 192 entropy bits generated by automatically processing a series of +/// periodic samples from the TRNG block’s internal Ring Oscillator (ROSC). +const TRNG_BLOCK_SIZE_BITS: usize = 192; +const TRNG_BLOCK_SIZE_BYTES: usize = TRNG_BLOCK_SIZE_BITS / 8; + +impl<'d, T: Instance> Trng<'d, T> { + /// Create a new TRNG driver. + pub fn new(_trng: Peri<'d, T>, _irq: impl Binding> + 'd, config: Config) -> Self { + let trng = Trng { + phantom: PhantomData, + config: config, + }; + trng.initialize_rng(); + trng + } + + fn start_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + // Enable TRNG ROSC + source_enable_register.write(|w| w.set_rnd_src_en(true)); + } + + fn stop_rng(&self) { + let regs = T::regs(); + let source_enable_register = regs.rnd_source_enable(); + source_enable_register.write(|w| w.set_rnd_src_en(false)); + let reset_bits_counter_register = regs.rst_bits_counter(); + reset_bits_counter_register.write(|w| w.set_rst_bits_counter(true)); + } + + fn initialize_rng(&self) { + let regs = T::regs(); + + regs.rng_imr().write(|w| w.set_ehr_valid_int_mask(false)); + + let trng_config_register = regs.trng_config(); + trng_config_register.write(|w| { + w.set_rnd_src_sel(self.config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = self.config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(self.config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(self.config.disable_crngt_test); + w.set_vnc_bypass(self.config.disable_von_neumann_balancer); + }); + } + + fn enable_irq(&self) { + unsafe { T::Interrupt::enable() } + } + + fn disable_irq(&self) { + T::Interrupt::disable(); + } + + fn blocking_wait_for_successful_generation(&self) { + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let mut success = false; + while success.not() { + while trng_busy_register.read().trng_busy() {} + if trng_valid_register.read().ehr_valid().not() { + if regs.rng_isr().read().autocorr_err() { + regs.trng_sw_reset().write(|w| w.set_trng_sw_reset(true)); + // Fixed delay is required after TRNG soft reset. This read is sufficient. + regs.trng_sw_reset().read(); + self.initialize_rng(); + self.start_rng(); + } else { + panic!("RNG not busy, but ehr is not valid!") + } + } else { + success = true + } + } + } + + fn read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + let regs = T::regs(); + let ehr_data_regs = [ + regs.ehr_data0(), + regs.ehr_data1(), + regs.ehr_data2(), + regs.ehr_data3(), + regs.ehr_data4(), + regs.ehr_data5(), + ]; + + for (i, reg) in ehr_data_regs.iter().enumerate() { + buffer[i * 4..i * 4 + 4].copy_from_slice(®.read().to_ne_bytes()); + } + } + + fn blocking_read_ehr_registers_into_array(&mut self, buffer: &mut [u8; TRNG_BLOCK_SIZE_BYTES]) { + self.blocking_wait_for_successful_generation(); + self.read_ehr_registers_into_array(buffer); + } + + /// Fill the buffer with random bytes, async version. + pub async fn fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + + self.start_rng(); + self.enable_irq(); + + let mut bytes_transferred = 0usize; + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + let regs = T::regs(); + + let trng_busy_register = regs.trng_busy(); + let trng_valid_register = regs.trng_valid(); + + let waker = T::waker(); + + let destination_length = destination.len(); + + poll_fn(|context| { + waker.register(context.waker()); + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + if trng_busy_register.read().trng_busy() { + Poll::Pending + } else { + // If woken up and EHR is *not* valid, assume the trng has been reset and reinitialize, restart. + if trng_valid_register.read().ehr_valid().not() { + self.initialize_rng(); + self.start_rng(); + return Poll::Pending; + } + self.read_ehr_registers_into_array(&mut buffer); + let remaining = destination_length - bytes_transferred; + if remaining > TRNG_BLOCK_SIZE_BYTES { + destination[bytes_transferred..bytes_transferred + TRNG_BLOCK_SIZE_BYTES] + .copy_from_slice(&buffer); + bytes_transferred += TRNG_BLOCK_SIZE_BYTES + } else { + destination[bytes_transferred..bytes_transferred + remaining] + .copy_from_slice(&buffer[0..remaining]); + bytes_transferred += remaining + } + if bytes_transferred == destination_length { + self.stop_rng(); + self.disable_irq(); + Poll::Ready(()) + } else { + Poll::Pending + } + } + } + }) + .await + } + + /// Fill the buffer with random bytes, blocking version. + pub fn blocking_fill_bytes(&mut self, destination: &mut [u8]) { + if destination.is_empty() { + return; // Nothing to fill + } + self.start_rng(); + + let mut buffer = [0u8; TRNG_BLOCK_SIZE_BYTES]; + + for chunk in destination.chunks_mut(TRNG_BLOCK_SIZE_BYTES) { + self.blocking_wait_for_successful_generation(); + self.blocking_read_ehr_registers_into_array(&mut buffer); + chunk.copy_from_slice(&buffer[..chunk.len()]) + } + self.stop_rng() + } + + /// Return a random u32, blocking. + pub fn blocking_next_u32(&mut self) -> u32 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = regs.ehr_data5().read(); + self.stop_rng(); + result + } + + /// Return a random u64, blocking. + pub fn blocking_next_u64(&mut self) -> u64 { + let regs = T::regs(); + self.start_rng(); + self.blocking_wait_for_successful_generation(); + + let low = regs.ehr_data4().read() as u64; + // 12.12.3 After successful generation, read the last result register, EHR_DATA[5] to + // clear all of the result registers. + let result = (regs.ehr_data5().read() as u64) << 32 | low; + self.stop_rng(); + result + } +} + +impl<'d, T: Instance> rand_core_06::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core_06::Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} + +impl<'d, T: Instance> rand_core_06::CryptoRng for Trng<'d, T> {} + +impl<'d, T: Instance> rand_core_09::RngCore for Trng<'d, T> { + fn next_u32(&mut self) -> u32 { + self.blocking_next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.blocking_next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.blocking_fill_bytes(dest); + } +} + +impl<'d, T: Instance> rand_core_09::CryptoRng for Trng<'d, T> {} + +/// TRNG interrupt handler. +pub struct InterruptHandler { + _trng: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.rng_isr().read(); + if isr.ehr_valid() { + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + T::waker().wake(); + } else if isr.crngt_err() { + warn!("TRNG CRNGT error! Increase sample count to reduce likelihood"); + regs.rng_icr().write(|w| { + w.set_crngt_err(true); + }); + } else if isr.vn_err() { + warn!("TRNG Von-Neumann balancer error! Increase sample count to reduce likelihood"); + regs.rng_icr().write(|w| { + w.set_vn_err(true); + }); + } else if isr.autocorr_err() { + // 12.12.5. List of Registers + // ... + // TRNG: RNG_ISR Register + // ... + // AUTOCORR_ERR: 1 indicates Autocorrelation test failed four times in a row. + // When set, RNG ceases functioning until next reset + warn!("TRNG Autocorrect error! Resetting TRNG. Increase sample count to reduce likelihood"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + // Fixed delay is required after TRNG soft reset, this read is sufficient. + regs.trng_sw_reset().read(); + // Wake up to reinitialize and restart the TRNG. + T::waker().wake(); + } + } +} diff --git a/embassy-rp-fork/src/uart/buffered.rs b/embassy-rp-fork/src/uart/buffered.rs new file mode 100644 index 0000000..fdb8ce7 --- /dev/null +++ b/embassy-rp-fork/src/uart/buffered.rs @@ -0,0 +1,846 @@ +//! Buffered UART driver. +use core::future::Future; +use core::slice; +use core::sync::atomic::{AtomicU8, Ordering}; + +use embassy_hal_internal::atomic_ring_buffer::RingBuffer; + +use super::*; + +pub struct State { + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + rx_error: AtomicU8, +} + +// these must match bits 8..11 in UARTDR +const RXE_OVERRUN: u8 = 8; +const RXE_BREAK: u8 = 4; +const RXE_PARITY: u8 = 2; +const RXE_FRAMING: u8 = 1; + +impl State { + pub const fn new() -> Self { + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + rx_error: AtomicU8::new(0), + } + } +} + +/// Buffered UART driver. +pub struct BufferedUart { + pub(super) rx: BufferedUartRx, + pub(super) tx: BufferedUartTx, +} + +/// Buffered UART RX handle. +pub struct BufferedUartRx { + pub(super) info: &'static Info, + pub(super) state: &'static State, +} + +/// Buffered UART TX handle. +pub struct BufferedUartTx { + pub(super) info: &'static Info, + pub(super) state: &'static State, +} + +pub(super) fn init_buffers<'d>( + info: &Info, + state: &State, + tx_buffer: Option<&'d mut [u8]>, + rx_buffer: Option<&'d mut [u8]>, +) { + if let Some(tx_buffer) = tx_buffer { + let len = tx_buffer.len(); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + } + + if let Some(rx_buffer) = rx_buffer { + let len = rx_buffer.len(); + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; + } + + // From the datasheet: + // "The transmit interrupt is based on a transition through a level, rather + // than on the level itself. When the interrupt and the UART is enabled + // before any data is written to the transmit FIFO the interrupt is not set. + // The interrupt is only set, after written data leaves the single location + // of the transmit FIFO and it becomes empty." + // + // This means we can leave the interrupt enabled the whole time as long as + // we clear it after it happens. The downside is that the we manually have + // to pend the ISR when we want data transmission to start. + info.regs.uartimsc().write(|w| { + w.set_rxim(true); + w.set_rtim(true); + w.set_txim(true); + }); + + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; +} + +impl BufferedUart { + /// Create a buffered UART instance. + pub fn new<'d, T: Instance>( + _uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), Some(rx.into()), None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); + + Self { + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, + } + } + + /// Create a buffered UART instance with flow control. + pub fn new_with_rtscts<'d, T: Instance>( + _uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + super::Uart::<'d, Async>::init( + T::info(), + Some(tx.into()), + Some(rx.into()), + Some(rts.into()), + Some(cts.into()), + config, + ); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); + + Self { + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, + } + } + + /// Write to UART TX buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { + self.tx.blocking_write(buffer) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Read from UART RX buffer blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { + self.rx.blocking_read(buffer) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + self.tx.busy() + } + + /// Wait until TX is empty and send break condition. + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + /// sets baudrate on runtime + pub fn set_baudrate<'d>(&mut self, baudrate: u32) { + super::Uart::<'d, Async>::set_baudrate_inner(self.rx.info, baudrate); + } + + /// Split into separate RX and TX handles. + pub fn split(self) -> (BufferedUartTx, BufferedUartRx) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut BufferedUartTx, &mut BufferedUartRx) { + (&mut self.tx, &mut self.rx) + } +} + +impl BufferedUartRx { + /// Create a new buffered UART RX. + pub fn new<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + rx: Peri<'d, impl RxPin>, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + super::Uart::<'d, Async>::init(T::info(), None, Some(rx.into()), None, None, config); + init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); + + Self { + info: T::info(), + state: T::buffered_state(), + } + } + + /// Create a new buffered UART RX with flow control. + pub fn new_with_rts<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + super::Uart::<'d, Async>::init(T::info(), None, Some(rx.into()), Some(rts.into()), None, config); + init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); + + Self { + info: T::info(), + state: T::buffered_state(), + } + } + + fn read<'a>( + info: &'static Info, + state: &'static State, + buf: &'a mut [u8], + ) -> impl Future> + 'a { + poll_fn(move |cx| { + if let Poll::Ready(r) = Self::try_read(info, state, buf) { + return Poll::Ready(r); + } + state.rx_waker.register(cx.waker()); + Poll::Pending + }) + } + + fn get_rx_error(state: &State) -> Option { + let errs = critical_section::with(|_| { + let val = state.rx_error.load(Ordering::Relaxed); + state.rx_error.store(0, Ordering::Relaxed); + val + }); + if errs & RXE_OVERRUN != 0 { + Some(Error::Overrun) + } else if errs & RXE_BREAK != 0 { + Some(Error::Break) + } else if errs & RXE_PARITY != 0 { + Some(Error::Parity) + } else if errs & RXE_FRAMING != 0 { + Some(Error::Framing) + } else { + None + } + } + + fn try_read(info: &Info, state: &State, buf: &mut [u8]) -> Poll> { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let n = rx_reader.pop(|data| { + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + n + }); + + let result = if n == 0 { + match Self::get_rx_error(state) { + None => return Poll::Pending, + Some(e) => Err(e), + } + } else { + Ok(n) + }; + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + info.regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + + Poll::Ready(result) + } + + /// Read from UART RX buffer blocking execution until done. + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { + loop { + match Self::try_read(self.info, self.state, buf) { + Poll::Ready(res) => return res, + Poll::Pending => continue, + } + } + } + + fn fill_buf<'a>(state: &'static State) -> impl Future> { + poll_fn(move |cx| { + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let (p, n) = rx_reader.pop_buf(); + let result = if n == 0 { + match Self::get_rx_error(state) { + None => { + state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + Some(e) => Err(e), + } + } else { + let buf = unsafe { slice::from_raw_parts(p, n) }; + Ok(buf) + }; + + Poll::Ready(result) + }) + } + + fn consume(info: &Info, state: &State, amt: usize) { + let mut rx_reader = unsafe { state.rx_buf.reader() }; + rx_reader.pop_done(amt); + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + info.regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } + + /// we are ready to read if there is data in the buffer + fn read_ready(state: &State) -> Result { + Ok(!state.rx_buf.is_empty()) + } +} + +impl BufferedUartTx { + /// Create a new buffered UART TX. + pub fn new<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + tx: Peri<'d, impl TxPin>, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, None, config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); + + Self { + info: T::info(), + state: T::buffered_state(), + } + } + + /// Create a new buffered UART TX with flow control. + pub fn new_with_cts<'d, T: Instance>( + _uart: Peri<'d, T>, + _irq: impl Binding>, + tx: Peri<'d, impl TxPin>, + cts: Peri<'d, impl CtsPin>, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + super::Uart::<'d, Async>::init(T::info(), Some(tx.into()), None, None, Some(cts.into()), config); + init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); + + Self { + info: T::info(), + state: T::buffered_state(), + } + } + + fn write<'d>( + info: &'static Info, + state: &'static State, + buf: &'d [u8], + ) -> impl Future> + 'd { + poll_fn(move |cx| { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + if n == 0 { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + // The TX interrupt only triggers when the there was data in the + // FIFO and the number of bytes drops below a threshold. When the + // FIFO was empty we have to manually pend the interrupt to shovel + // TX data from the buffer into the FIFO. + info.interrupt.pend(); + Poll::Ready(Ok(n)) + }) + } + + fn flush(state: &'static State) -> impl Future> { + poll_fn(move |cx| { + if !state.tx_buf.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + } + + /// Write to UART TX buffer blocking execution until done. + pub fn blocking_write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + loop { + let mut tx_writer = unsafe { self.state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + + if n != 0 { + // The TX interrupt only triggers when the there was data in the + // FIFO and the number of bytes drops below a threshold. When the + // FIFO was empty we have to manually pend the interrupt to shovel + // TX data from the buffer into the FIFO. + self.info.interrupt.pend(); + return Ok(n); + } + } + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + loop { + if self.state.tx_buf.is_empty() { + return Ok(()); + } + } + } + + /// Check if UART is busy. + pub fn busy(&self) -> bool { + self.info.regs.uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = self.info.regs; + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + Self::flush(self.state).await.unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after_micros(wait_usecs).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl Drop for BufferedUartRx { + fn drop(&mut self) { + unsafe { self.state.rx_buf.deinit() } + + // TX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if !self.state.tx_buf.is_available() { + self.info.interrupt.disable(); + } + } +} + +impl Drop for BufferedUartTx { + fn drop(&mut self) { + unsafe { self.state.tx_buf.deinit() } + + // RX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if !self.state.rx_buf.is_available() { + self.info.interrupt.disable(); + } + } +} + +/// Interrupt handler. +pub struct BufferedInterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + let r = T::info().regs; + if r.uartdmacr().read().rxdmae() { + return; + } + + let s = T::buffered_state(); + + // Clear TX and error interrupt flags + // RX interrupt flags are cleared by reading from the FIFO. + let ris = r.uartris().read(); + r.uarticr().write(|w| { + w.set_txic(ris.txris()); + w.set_feic(ris.feris()); + w.set_peic(ris.peris()); + w.set_beic(ris.beris()); + w.set_oeic(ris.oeris()); + }); + + // Errors + if ris.feris() { + warn!("Framing error"); + } + if ris.peris() { + warn!("Parity error"); + } + if ris.beris() { + warn!("Break error"); + } + if ris.oeris() { + warn!("Overrun error"); + } + + // RX + if s.rx_buf.is_available() { + let mut rx_writer = unsafe { s.rx_buf.writer() }; + let rx_buf = rx_writer.push_slice(); + let mut n_read = 0; + let mut error = false; + for rx_byte in rx_buf { + if r.uartfr().read().rxfe() { + break; + } + let dr = r.uartdr().read(); + if (dr.0 >> 8) != 0 { + critical_section::with(|_| { + let val = s.rx_error.load(Ordering::Relaxed); + s.rx_error.store(val | ((dr.0 >> 8) as u8), Ordering::Relaxed); + }); + error = true; + // only fill the buffer with valid characters. the current character is fine + // if the error is an overrun, but if we add it to the buffer we'll report + // the overrun one character too late. drop it instead and pretend we were + // a bit slower at draining the rx fifo than we actually were. + // this is consistent with blocking uart error reporting. + break; + } + *rx_byte = dr.data(); + n_read += 1; + } + if n_read > 0 { + rx_writer.push_done(n_read); + s.rx_waker.wake(); + } else if error { + s.rx_waker.wake(); + } + // Disable any further RX interrupts when the buffer becomes full or + // errors have occurred. This lets us buffer additional errors in the + // fifo without needing more error storage locations, and most applications + // will want to do a full reset of their uart state anyway once an error + // has happened. + if s.rx_buf.is_full() || error { + r.uartimsc().write_clear(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } + } + + // TX + if s.tx_buf.is_available() { + let mut tx_reader = unsafe { s.tx_buf.reader() }; + let tx_buf = tx_reader.pop_slice(); + let mut n_written = 0; + for tx_byte in tx_buf.iter_mut() { + if r.uartfr().read().txff() { + break; + } + r.uartdr().write(|w| w.set_data(*tx_byte)); + n_written += 1; + } + if n_written > 0 { + tx_reader.pop_done(n_written); + s.tx_waker.wake(); + } + // The TX interrupt only triggers once when the FIFO threshold is + // crossed. No need to disable it when the buffer becomes empty + // as it does re-trigger anymore once we have cleared it. + } + } +} + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl embedded_io_async::ErrorType for BufferedUart { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedUartRx { + type Error = Error; +} + +impl embedded_io_async::ErrorType for BufferedUartTx { + type Error = Error; +} + +impl embedded_io_async::Read for BufferedUart { + async fn read(&mut self, buf: &mut [u8]) -> Result { + BufferedUartRx::read(self.rx.info, self.rx.state, buf).await + } +} + +impl embedded_io_async::Read for BufferedUartRx { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(self.info, self.state, buf).await + } +} + +impl embedded_io_async::ReadReady for BufferedUart { + fn read_ready(&mut self) -> Result { + BufferedUartRx::read_ready(self.rx.state) + } +} + +impl embedded_io_async::ReadReady for BufferedUartRx { + fn read_ready(&mut self) -> Result { + Self::read_ready(self.state) + } +} + +impl embedded_io_async::BufRead for BufferedUart { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + BufferedUartRx::fill_buf(self.rx.state).await + } + + fn consume(&mut self, amt: usize) { + BufferedUartRx::consume(self.rx.info, self.rx.state, amt) + } +} + +impl embedded_io_async::BufRead for BufferedUartRx { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf(self.state).await + } + + fn consume(&mut self, amt: usize) { + Self::consume(self.info, self.state, amt) + } +} + +impl embedded_io_async::Write for BufferedUart { + async fn write(&mut self, buf: &[u8]) -> Result { + BufferedUartTx::write(self.tx.info, self.tx.state, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + BufferedUartTx::flush(self.tx.state).await + } +} + +impl embedded_io_async::Write for BufferedUartTx { + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(self.info, self.state, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush(self.state).await + } +} + +impl embedded_io::Read for BufferedUart { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.blocking_read(buf) + } +} + +impl embedded_io::Read for BufferedUartRx { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read(buf) + } +} + +impl embedded_io::Write for BufferedUart { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl embedded_io::Write for BufferedUartTx { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_02::serial::Read for BufferedUartRx { + type Error = Error; + + fn read(&mut self) -> Result> { + let r = self.info.regs; + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl embedded_hal_02::blocking::serial::Write for BufferedUartTx { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_02::serial::Read for BufferedUart { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_02::blocking::serial::Write for BufferedUart { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::ErrorType for BufferedUartRx { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for BufferedUartTx { + type Error = Error; +} + +impl embedded_hal_nb::serial::ErrorType for BufferedUart { + type Error = Error; +} + +impl embedded_hal_nb::serial::Read for BufferedUartRx { + fn read(&mut self) -> nb::Result { + embedded_hal_02::serial::Read::read(self) + } +} + +impl embedded_hal_nb::serial::Write for BufferedUartTx { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl embedded_hal_nb::serial::Read for BufferedUart { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl embedded_hal_nb::serial::Write for BufferedUart { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} diff --git a/embassy-rp-fork/src/uart/mod.rs b/embassy-rp-fork/src/uart/mod.rs new file mode 100644 index 0000000..b7b569d --- /dev/null +++ b/embassy-rp-fork/src/uart/mod.rs @@ -0,0 +1,1541 @@ +//! UART driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicU16, Ordering}; +use core::task::Poll; + +use embassy_futures::select::{Either, select}; +use embassy_hal_internal::{Peri, PeripheralType}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::{Delay, Timer}; +use pac::uart::regs::Uartris; + +use crate::clocks::clk_peri_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::{AnyPin, SealedPin}; +use crate::interrupt::typelevel::{Binding, Interrupt as _}; +use crate::interrupt::{Interrupt, InterruptExt}; +use crate::pac::io::vals::{Inover, Outover}; +use crate::{RegExt, interrupt, pac, peripherals}; + +mod buffered; +pub use buffered::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, BufferedUartTx}; + +/// Word length. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DataBits { + /// 5 bits. + DataBits5, + /// 6 bits. + DataBits6, + /// 7 bits. + DataBits7, + /// 8 bits. + DataBits8, +} + +impl DataBits { + fn bits(&self) -> u8 { + match self { + Self::DataBits5 => 0b00, + Self::DataBits6 => 0b01, + Self::DataBits7 => 0b10, + Self::DataBits8 => 0b11, + } + } +} + +/// Parity bit. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Parity { + /// No parity. + ParityNone, + /// Even parity. + ParityEven, + /// Odd parity. + ParityOdd, +} + +/// Stop bits. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum StopBits { + #[doc = "1 stop bit"] + STOP1, + #[doc = "2 stop bits"] + STOP2, +} + +/// UART config. +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + /// Baud rate. + pub baudrate: u32, + /// Word length. + pub data_bits: DataBits, + /// Stop bits. + pub stop_bits: StopBits, + /// Parity bit. + pub parity: Parity, + /// Invert the tx pin output + pub invert_tx: bool, + /// Invert the rx pin input + pub invert_rx: bool, + /// Invert the rts pin + pub invert_rts: bool, + /// Invert the cts pin + pub invert_cts: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate: 115200, + data_bits: DataBits::DataBits8, + stop_bits: StopBits::STOP1, + parity: Parity::ParityNone, + invert_rx: false, + invert_tx: false, + invert_rts: false, + invert_cts: false, + } + } +} + +/// Serial error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + /// Triggered when a break is received + Break, + /// Triggered when there is a parity mismatch between what's received and + /// our settings. + Parity, + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +/// Read To Break error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ReadToBreakError { + /// Read this many bytes, but never received a line break. + MissingBreak(usize), + /// Other, standard issue with the serial request + Other(Error), +} + +/// Internal DMA state of UART RX. +pub struct DmaState { + rx_err_waker: AtomicWaker, + rx_errs: AtomicU16, +} + +/// UART driver. +pub struct Uart<'d, M: Mode> { + tx: UartTx<'d, M>, + rx: UartRx<'d, M>, +} + +/// UART TX driver. +pub struct UartTx<'d, M: Mode> { + info: &'static Info, + tx_dma: Option>, + phantom: PhantomData, +} + +/// UART RX driver. +pub struct UartRx<'d, M: Mode> { + info: &'static Info, + dma_state: &'static DmaState, + rx_dma: Option>, + phantom: PhantomData, +} + +impl<'d, M: Mode> UartTx<'d, M> { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + tx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner(T::info(), Some(tx_dma.into())) + } + + fn new_inner(info: &'static Info, tx_dma: Option>) -> Self { + Self { + info, + tx_dma, + phantom: PhantomData, + } + } + + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = self.info.regs; + for &b in buffer { + while r.uartfr().read().txff() {} + r.uartdr().write(|w| w.set_data(b)); + } + Ok(()) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + while !self.info.regs.uartfr().read().txfe() {} + Ok(()) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + self.info.regs.uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = self.info.regs; + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + self.blocking_flush().unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after_micros(wait_usecs).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl<'d> UartTx<'d, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking(_uart: Peri<'d, T>, tx: Peri<'d, impl TxPin>, config: Config) -> Self { + Uart::::init(T::info(), Some(tx.into()), None, None, None, config); + Self::new_inner(T::info(), None) + } + + /// Convert this uart TX instance into a buffered uart using the provided + /// irq and transmit buffer. + pub fn into_buffered( + self, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + ) -> BufferedUartTx { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), None); + + BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + } + } +} + +impl<'d> UartTx<'d, Async> { + /// Write to UART TX from the provided buffer using DMA. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let ch = self.tx_dma.as_mut().unwrap().reborrow(); + let transfer = unsafe { + self.info.regs.uartdmacr().write_set(|reg| { + reg.set_txdmae(true); + }); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write( + ch, + buffer, + self.info.regs.uartdr().as_ptr() as *mut _, + self.info.tx_dreq.into(), + ) + }; + transfer.await; + Ok(()) + } +} + +impl<'d, M: Mode> UartRx<'d, M> { + /// Create a new DMA-enabled UART which can only receive data + pub fn new( + _uart: Peri<'d, T>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner(T::info(), T::dma_state(), true, Some(rx_dma.into())) + } + + fn new_inner( + info: &'static Info, + dma_state: &'static DmaState, + has_irq: bool, + rx_dma: Option>, + ) -> Self { + debug_assert_eq!(has_irq, rx_dma.is_some()); + if has_irq { + // disable all error interrupts initially + info.regs.uartimsc().write(|w| w.0 = 0); + info.interrupt.unpend(); + unsafe { info.interrupt.enable() }; + } + Self { + info, + dma_state, + rx_dma, + phantom: PhantomData, + } + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> { + while !buffer.is_empty() { + let received = self.drain_fifo(buffer).map_err(|(_i, e)| e)?; + buffer = &mut buffer[received..]; + } + Ok(()) + } + + /// Returns Ok(len) if no errors occurred. Returns Err((len, err)) if an error was + /// encountered. In both cases, `len` is the number of *good* bytes copied into + /// `buffer`. + fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { + let r = self.info.regs; + for (i, b) in buffer.iter_mut().enumerate() { + if r.uartfr().read().rxfe() { + return Ok(i); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + return Err((i, Error::Overrun)); + } else if dr.be() { + return Err((i, Error::Break)); + } else if dr.pe() { + return Err((i, Error::Parity)); + } else if dr.fe() { + return Err((i, Error::Framing)); + } else { + *b = dr.data(); + } + } + Ok(buffer.len()) + } +} + +impl<'d, M: Mode> Drop for UartRx<'d, M> { + fn drop(&mut self) { + if self.rx_dma.is_some() { + self.info.interrupt.disable(); + // clear dma flags. irq handlers use these to disambiguate among themselves. + self.info.regs.uartdmacr().write_clear(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + reg.set_dmaonerr(true); + }); + } + } +} + +impl<'d> UartRx<'d, Blocking> { + /// Create a new UART RX instance for blocking mode operations. + pub fn new_blocking(_uart: Peri<'d, T>, rx: Peri<'d, impl RxPin>, config: Config) -> Self { + Uart::::init(T::info(), None, Some(rx.into()), None, None, config); + Self::new_inner(T::info(), T::dma_state(), false, None) + } + + /// Convert this uart RX instance into a buffered uart using the provided + /// irq and receive buffer. + pub fn into_buffered( + self, + _irq: impl Binding>, + rx_buffer: &'d mut [u8], + ) -> BufferedUartRx { + buffered::init_buffers(T::info(), T::buffered_state(), None, Some(rx_buffer)); + + BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let uart = T::info().regs; + if !uart.uartdmacr().read().rxdmae() { + return; + } + + let state = T::dma_state(); + let errs = uart.uartris().read(); + state.rx_errs.store(errs.0 as u16, Ordering::Relaxed); + state.rx_err_waker.wake(); + // disable the error interrupts instead of clearing the flags. clearing the + // flags would allow the dma transfer to continue, potentially signaling + // completion before we can check for errors that happened *during* the transfer. + uart.uartimsc().write_clear(|w| w.0 = errs.0); + } +} + +impl<'d> UartRx<'d, Async> { + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let buffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + Ok(len) if len < buffer.len() => &mut buffer[len..], + Ok(_) => return Ok(()), + Err((_i, e)) => return Err(e), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let ch = self.rx_dma.as_mut().unwrap().reborrow(); + self.info.regs.uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + self.info.regs.uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read( + ch, + self.info.regs.uartdr().as_ptr() as *const _, + buffer, + self.info.rx_dreq.into(), + ) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + self.dma_state.rx_err_waker.register(cx.waker()); + let rx_errs = critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }); + match rx_errs { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + // If we got no error, just return at this point + if errors.0 == 0 { + return Ok(()); + } + + // If we DID get an error, we need to figure out which one it was. + if errors.oeris() { + return Err(Error::Overrun); + } else if errors.beris() { + return Err(Error::Break); + } else if errors.peris() { + return Err(Error::Parity); + } else if errors.feris() { + return Err(Error::Framing); + } + unreachable!("unrecognized rx error"); + } + + /// Read from the UART, waiting for a break. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n` bytes then a break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" break + /// * If you expect a message of 20 bytes + break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + /// + /// **NOTE**: In the UART context, a break refers to a break condition (the line being held low for + /// for longer than a single character), not an ASCII line break. + pub async fn read_to_break(&mut self, buffer: &mut [u8]) -> Result { + self.read_to_break_with_count(buffer, 0).await + } + + /// Read from the UART, waiting for a break as soon as at least `min_count` bytes have been read. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n > min_count` bytes then a break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// If a break occurs before `min_count` bytes have been read, the break will be ignored and the read will continue + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + /// + /// **NOTE**: In the UART context, a break refers to a break condition (the line being held low for + /// for longer than a single character), not an ASCII line break. + pub async fn read_to_break_with_count( + &mut self, + buffer: &mut [u8], + min_count: usize, + ) -> Result { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + self.info.regs.uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let mut sbuffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + // Drained fifo, still some room left! + Ok(len) if len < buffer.len() => &mut buffer[len..], + // Drained (some/all of the fifo), no room left + Ok(len) => return Err(ReadToBreakError::MissingBreak(len)), + // We got a break WHILE draining the FIFO, return what we did get before the break + Err((len, Error::Break)) => { + if len < min_count && len < buffer.len() { + &mut buffer[len..] + } else { + return Ok(len); + } + } + // Some other error, just return the error + Err((_i, e)) => return Err(ReadToBreakError::Other(e)), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let ch = self.rx_dma.as_mut().unwrap(); + self.info.regs.uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + self.info.regs.uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + + loop { + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read( + ch.reborrow(), + self.info.regs.uartdr().as_ptr() as *const _, + sbuffer, + self.info.rx_dreq.into(), + ) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + self.dma_state.rx_err_waker.register(cx.waker()); + let rx_errs = critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }); + match rx_errs { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + // Figure out our error state + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(critical_section::with(|_| { + let val = self.dma_state.rx_errs.load(Ordering::Relaxed); + self.dma_state.rx_errs.store(0, Ordering::Relaxed); + val + }) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + if errors.0 == 0 { + // No errors? That means we filled the buffer without a line break. + // For THIS function, that's a problem. + return Err(ReadToBreakError::MissingBreak(buffer.len())); + } else if errors.beris() { + // We got a Line Break! By this point, we've finished/aborted the DMA + // transaction, which means that we need to figure out where it left off + // by looking at the write_addr. + // + // First, we do a sanity check to make sure the write value is within the + // range of DMA we just did. + let sval = buffer.as_ptr() as usize; + let eval = sval + buffer.len(); + + // This is the address where the DMA would write to next + let next_addr = ch.regs().write_addr().read() as usize; + + // If we DON'T end up inside the range, something has gone really wrong. + // Note that it's okay that `eval` is one past the end of the slice, as + // this is where the write pointer will end up at the end of a full + // transfer. + if (next_addr < sval) || (next_addr > eval) { + unreachable!("UART DMA reported invalid `write_addr`"); + } + + if (next_addr - sval) < min_count { + sbuffer = &mut buffer[(next_addr - sval)..]; + continue; + } + + let regs = self.info.regs; + let all_full = next_addr == eval; + + // NOTE: This is off label usage of RSR! See the issue below for + // why I am not checking if there is an "extra" FIFO byte, and why + // I am checking RSR directly (it seems to report the status of the LAST + // POPPED value, rather than the NEXT TO POP value like the datasheet + // suggests!) + // + // issue: https://github.com/raspberrypi/pico-feedback/issues/367 + let last_was_break = regs.uartrsr().read().be(); + + return match (all_full, last_was_break) { + (true, true) | (false, _) => { + // We got less than the full amount + a break, or the full amount + // and the last byte was a break. Subtract the break off by adding one to sval. + Ok(next_addr.saturating_sub(1 + sval)) + } + (true, false) => { + // We finished the whole DMA, and the last DMA'd byte was NOT a break + // character. This is an error. + // + // NOTE: we COULD potentially return Ok(buffer.len()) here, since we + // know a line break occured at SOME POINT after the DMA completed. + // + // However, we have no way of knowing if there was extra data BEFORE + // that line break, so instead return an Err to signal to the caller + // that there are "leftovers", and they'll catch the actual line break + // on the next call. + // + // Doing it like this also avoids racyness: now whether you finished + // the full read BEFORE the line break occurred or AFTER the line break + // occurs, you still get `MissingBreak(buffer.len())` instead of sometimes + // getting `Ok(buffer.len())` if you were "late enough" to observe the + // line break. + Err(ReadToBreakError::MissingBreak(buffer.len())) + } + }; + } else if errors.oeris() { + return Err(ReadToBreakError::Other(Error::Overrun)); + } else if errors.peris() { + return Err(ReadToBreakError::Other(Error::Parity)); + } else if errors.feris() { + return Err(ReadToBreakError::Other(Error::Framing)); + } + unreachable!("unrecognized rx error"); + } + } +} + +impl<'d> Uart<'d, Blocking> { + /// Create a new UART without hardware flow control + pub fn new_blocking( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + config: Config, + ) -> Self { + Self::new_inner(uart, tx.into(), rx.into(), None, None, false, None, None, config) + } + + /// Create a new UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts_blocking( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + config: Config, + ) -> Self { + Self::new_inner( + uart, + tx.into(), + rx.into(), + Some(rts.into()), + Some(cts.into()), + false, + None, + None, + config, + ) + } + + /// Convert this uart instance into a buffered uart using the provided + /// irq, transmit and receive buffers. + pub fn into_buffered( + self, + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + ) -> BufferedUart { + buffered::init_buffers(T::info(), T::buffered_state(), Some(tx_buffer), Some(rx_buffer)); + + BufferedUart { + rx: BufferedUartRx { + info: T::info(), + state: T::buffered_state(), + }, + tx: BufferedUartTx { + info: T::info(), + state: T::buffered_state(), + }, + } + } +} + +impl<'d> Uart<'d, Async> { + /// Create a new DMA enabled UART without hardware flow control + pub fn new( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + _irq: impl Binding>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + uart, + tx.into(), + rx.into(), + None, + None, + true, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) + } + + /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts( + uart: Peri<'d, T>, + tx: Peri<'d, impl TxPin>, + rx: Peri<'d, impl RxPin>, + rts: Peri<'d, impl RtsPin>, + cts: Peri<'d, impl CtsPin>, + _irq: impl Binding>, + tx_dma: Peri<'d, impl Channel>, + rx_dma: Peri<'d, impl Channel>, + config: Config, + ) -> Self { + Self::new_inner( + uart, + tx.into(), + rx.into(), + Some(rts.into()), + Some(cts.into()), + true, + Some(tx_dma.into()), + Some(rx_dma.into()), + config, + ) + } +} + +impl<'d, M: Mode> Uart<'d, M> { + fn new_inner( + _uart: Peri<'d, T>, + mut tx: Peri<'d, AnyPin>, + mut rx: Peri<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, + has_irq: bool, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + Self::init( + T::info(), + Some(tx.reborrow()), + Some(rx.reborrow()), + rts.as_mut().map(|x| x.reborrow()), + cts.as_mut().map(|x| x.reborrow()), + config, + ); + + Self { + tx: UartTx::new_inner(T::info(), tx_dma), + rx: UartRx::new_inner(T::info(), T::dma_state(), has_irq, rx_dma), + } + } + + fn init( + info: &Info, + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) { + let r = info.regs; + if let Some(pin) = &tx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if (pin_number % 4) == 0 { 2 } else { 11 } + }; + pin.gpio().ctrl().write(|w| { + w.set_funcsel(funcsel); + w.set_outover(if config.invert_tx { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &rx { + let funcsel = { + let pin_number = ((pin.gpio().as_ptr() as u32) & 0x1FF) / 8; + if ((pin_number - 1) % 4) == 0 { 2 } else { 11 } + }; + pin.gpio().ctrl().write(|w| { + w.set_funcsel(funcsel); + w.set_inover(if config.invert_rx { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &cts { + pin.gpio().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_cts { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + if let Some(pin) = &rts { + pin.gpio().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_rts { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| { + #[cfg(feature = "_rp235x")] + w.set_iso(false); + w.set_ie(true); + }); + } + + Self::set_baudrate_inner(info, config.baudrate); + + let (pen, eps) = match config.parity { + Parity::ParityNone => (false, false), + Parity::ParityOdd => (true, false), + Parity::ParityEven => (true, true), + }; + + r.uartlcr_h().write(|w| { + w.set_wlen(config.data_bits.bits()); + w.set_stp2(config.stop_bits == StopBits::STOP2); + w.set_pen(pen); + w.set_eps(eps); + w.set_fen(true); + }); + + r.uartifls().write(|w| { + w.set_rxiflsel(0b100); + w.set_txiflsel(0b000); + }); + + r.uartcr().write(|w| { + w.set_uarten(true); + w.set_rxe(true); + w.set_txe(true); + w.set_ctsen(cts.is_some()); + w.set_rtsen(rts.is_some()); + }); + } + + fn lcr_modify(info: &Info, f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = info.regs; + + // Notes from PL011 reference manual: + // + // - Before writing the LCR, if the UART is enabled it needs to be + // disabled and any current TX + RX activity has to be completed + // + // - There is a BUSY flag which waits for the current TX char, but this is + // OR'd with TX FIFO !FULL, so not usable when FIFOs are enabled and + // potentially nonempty + // + // - FIFOs can't be set to disabled whilst a character is in progress + // (else "FIFO integrity is not guaranteed") + // + // Combination of these means there is no general way to halt and poll for + // end of TX character, if FIFOs may be enabled. Either way, there is no + // way to poll for end of RX character. + // + // So, insert a 15 Baud period delay before changing the settings. + // 15 Baud is comfortably higher than start + max data + parity + stop. + // Anything else would require API changes to permit a non-enabled UART + // state after init() where settings can be changed safely. + let clk_base = crate::clocks::clk_peri_freq(); + + let cr = r.uartcr().read(); + if cr.uarten() { + r.uartcr().modify(|w| { + w.set_uarten(false); + w.set_txe(false); + w.set_rxe(false); + }); + + // Note: Maximise precision here. Show working, the compiler will mop this up. + // Create a 16.6 fixed-point fractional division ratio; then scale to 32-bits. + let mut brdiv_ratio = 64 * r.uartibrd().read().0 + r.uartfbrd().read().0; + brdiv_ratio <<= 10; + // 3662 is ~(15 * 244.14) where 244.14 is 16e6 / 2^16 + let scaled_freq = clk_base / 3662; + let wait_time_us = brdiv_ratio / scaled_freq; + embedded_hal_1::delay::DelayNs::delay_us(&mut Delay, wait_time_us); + } + + let res = r.uartlcr_h().modify(f); + + r.uartcr().write_value(cr); + + res + } + + /// sets baudrate on runtime + pub fn set_baudrate(&mut self, baudrate: u32) { + Self::set_baudrate_inner(self.tx.info, baudrate); + } + + fn set_baudrate_inner(info: &Info, baudrate: u32) { + let r = info.regs; + + let clk_base = crate::clocks::clk_peri_freq(); + + let baud_rate_div = (8 * clk_base) / baudrate; + let mut baud_ibrd = baud_rate_div >> 7; + let mut baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2; + + if baud_ibrd == 0 { + baud_ibrd = 1; + baud_fbrd = 0; + } else if baud_ibrd >= 65535 { + baud_ibrd = 65535; + baud_fbrd = 0; + } + + // Load PL011's baud divisor registers + r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); + r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); + + Self::lcr_modify(info, |_| {}); + } +} + +impl<'d, M: Mode> Uart<'d, M> { + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write(buffer) + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + /// Read from UART RX blocking execution until done. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.blocking_read(buffer) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + self.tx.busy() + } + + /// Wait until TX is empty and send break condition. + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + /// Split the Uart into a transmitter and receiver, which is particularly + /// useful when having two tasks correlating to transmitting and receiving. + pub fn split(self) -> (UartTx<'d, M>, UartRx<'d, M>) { + (self.tx, self.rx) + } + + /// Split the Uart into a transmitter and receiver by mutable reference, + /// which is particularly useful when having two tasks correlating to + /// transmitting and receiving. + pub fn split_ref(&mut self) -> (&mut UartTx<'d, M>, &mut UartRx<'d, M>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'d> Uart<'d, Async> { + /// Write to UART TX from the provided buffer. + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.write(buffer).await + } + + /// Read from UART RX into the provided buffer. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.read(buffer).await + } + + /// Read until the buffer is full or a line break occurs. + /// + /// See [`UartRx::read_to_break()`] for more details + pub async fn read_to_break<'a>(&mut self, buf: &'a mut [u8]) -> Result { + self.rx.read_to_break(buf).await + } + + /// Read until the buffer is full or a line break occurs after at least `min_count` bytes have been read. + /// + /// See [`UartRx::read_to_break_with_count()`] for more details + pub async fn read_to_break_with_count<'a>( + &mut self, + buf: &'a mut [u8], + min_count: usize, + ) -> Result { + self.rx.read_to_break_with_count(buf, min_count).await + } +} + +impl<'d, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, M> { + type Error = Error; + fn read(&mut self) -> Result> { + let r = self.info.regs; + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + let r = self.info.regs; + if r.uartfr().read().txff() { + return Err(nb::Error::WouldBlock); + } + + r.uartdr().write(|w| w.set_data(word)); + Ok(()) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + let r = self.info.regs; + if !r.uartfr().read().txfe() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, M: Mode> embedded_hal_02::serial::Read for Uart<'d, M> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, M: Mode> embedded_hal_02::serial::Write for Uart<'d, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } +} + +impl<'d, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl embedded_hal_nb::serial::Error for Error { + fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_nb::serial::ErrorKind::FrameFormat, + Self::Break => embedded_hal_nb::serial::ErrorKind::Other, + Self::Overrun => embedded_hal_nb::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_nb::serial::ErrorKind::Parity, + } + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, M> { + type Error = Error; +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, M> { + fn read(&mut self) -> nb::Result { + let r = self.info.regs; + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d> embedded_io::ErrorType for UartTx<'d, Blocking> { + type Error = Error; +} + +impl<'d> embedded_io::Write for UartTx<'d, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, M> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } +} + +impl<'d> embedded_io::ErrorType for Uart<'d, Blocking> { + type Error = Error; +} + +impl<'d> embedded_io::Write for Uart<'d, Blocking> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf).map(|_| buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +struct Info { + regs: pac::uart::Uart, + tx_dreq: pac::dma::vals::TreqSel, + rx_dreq: pac::dma::vals::TreqSel, + interrupt: Interrupt, +} + +trait SealedMode {} + +trait SealedInstance { + fn info() -> &'static Info; + + fn buffered_state() -> &'static buffered::State; + + fn dma_state() -> &'static DmaState; +} + +/// UART mode. +#[allow(private_bounds)] +pub trait Mode: SealedMode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl SealedMode for $name {} + impl Mode for $name {} + }; +} + +/// Blocking mode. +pub struct Blocking; +/// Async mode. +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +/// UART instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType { + /// Interrupt for this instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_instance { + ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl SealedInstance for peripherals::$inst { + fn info() -> &'static Info { + static INFO: Info = Info { + regs: pac::$inst, + tx_dreq: $tx_dreq, + rx_dreq: $rx_dreq, + interrupt: crate::interrupt::typelevel::$irq::IRQ, + }; + &INFO + } + + fn buffered_state() -> &'static buffered::State { + static STATE: buffered::State = buffered::State::new(); + &STATE + } + + fn dma_state() -> &'static DmaState { + static STATE: DmaState = DmaState { + rx_err_waker: AtomicWaker::new(), + rx_errs: AtomicU16::new(0), + }; + &STATE + } + } + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +impl_instance!( + UART0, + UART0_IRQ, + pac::dma::vals::TreqSel::UART0_TX, + pac::dma::vals::TreqSel::UART0_RX +); +impl_instance!( + UART1, + UART1_IRQ, + pac::dma::vals::TreqSel::UART1_TX, + pac::dma::vals::TreqSel::UART1_RX +); + +/// Trait for TX pins. +pub trait TxPin: crate::gpio::Pin {} +/// Trait for RX pins. +pub trait RxPin: crate::gpio::Pin {} +/// Trait for Clear To Send (CTS) pins. +pub trait CtsPin: crate::gpio::Pin {} +/// Trait for Request To Send (RTS) pins. +pub trait RtsPin: crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, UART0, TxPin); +impl_pin!(PIN_1, UART0, RxPin); +impl_pin!(PIN_2, UART0, CtsPin); +impl_pin!(PIN_3, UART0, RtsPin); +impl_pin!(PIN_4, UART1, TxPin); +impl_pin!(PIN_5, UART1, RxPin); +impl_pin!(PIN_6, UART1, CtsPin); +impl_pin!(PIN_7, UART1, RtsPin); +impl_pin!(PIN_8, UART1, TxPin); +impl_pin!(PIN_9, UART1, RxPin); +impl_pin!(PIN_10, UART1, CtsPin); +impl_pin!(PIN_11, UART1, RtsPin); +impl_pin!(PIN_12, UART0, TxPin); +impl_pin!(PIN_13, UART0, RxPin); +impl_pin!(PIN_14, UART0, CtsPin); +impl_pin!(PIN_15, UART0, RtsPin); +impl_pin!(PIN_16, UART0, TxPin); +impl_pin!(PIN_17, UART0, RxPin); +impl_pin!(PIN_18, UART0, CtsPin); +impl_pin!(PIN_19, UART0, RtsPin); +impl_pin!(PIN_20, UART1, TxPin); +impl_pin!(PIN_21, UART1, RxPin); +impl_pin!(PIN_22, UART1, CtsPin); +impl_pin!(PIN_23, UART1, RtsPin); +impl_pin!(PIN_24, UART1, TxPin); +impl_pin!(PIN_25, UART1, RxPin); +impl_pin!(PIN_26, UART1, CtsPin); +impl_pin!(PIN_27, UART1, RtsPin); +impl_pin!(PIN_28, UART0, TxPin); +impl_pin!(PIN_29, UART0, RxPin); + +// Additional functions added by all 2350s +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_2, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_3, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_6, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_7, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_10, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_11, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_14, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_15, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_18, UART0, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_19, UART0, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_22, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_23, UART1, RxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_26, UART1, TxPin); +#[cfg(feature = "_rp235x")] +impl_pin!(PIN_27, UART1, RxPin); + +// Additional pins added by larger 2350 packages. +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_32, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_33, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_36, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_37, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_40, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_41, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_44, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_45, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, CtsPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RtsPin); + +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_30, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_31, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_34, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_35, UART0, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_38, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_39, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_42, UART1, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_43, UART1, RxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_46, UART0, TxPin); +#[cfg(feature = "rp235xb")] +impl_pin!(PIN_47, UART0, RxPin); diff --git a/embassy-rp-fork/src/usb.rs b/embassy-rp-fork/src/usb.rs new file mode 100644 index 0000000..e8273c3 --- /dev/null +++ b/embassy-rp-fork/src/usb.rs @@ -0,0 +1,836 @@ +//! USB driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{Ordering, compiler_fence}; +use core::task::Poll; + +use embassy_hal_internal::PeripheralType; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver as driver; +use embassy_usb_driver::{ + Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, +}; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{Peri, RegExt, interrupt, pac, peripherals}; + +trait SealedInstance { + fn regs() -> crate::pac::usb::Usb; + fn dpram() -> crate::pac::usb_dpram::UsbDpram; +} + +/// USB peripheral instance. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + PeripheralType + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +impl crate::usb::SealedInstance for peripherals::USB { + fn regs() -> pac::usb::Usb { + pac::USB + } + fn dpram() -> crate::pac::usb_dpram::UsbDpram { + pac::USB_DPRAM + } +} + +impl crate::usb::Instance for peripherals::USB { + type Interrupt = crate::interrupt::typelevel::USBCTRL_IRQ; +} + +const EP_COUNT: usize = 16; +const EP_MEMORY_SIZE: usize = 4096; +const EP_MEMORY: *mut u8 = pac::USB_DPRAM.as_ptr() as *mut u8; + +static BUS_WAKER: AtomicWaker = AtomicWaker::new(); +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [const { AtomicWaker::new() }; EP_COUNT]; + +struct EndpointBuffer { + addr: u16, + len: u16, + _phantom: PhantomData, +} + +impl EndpointBuffer { + const fn new(addr: u16, len: u16) -> Self { + Self { + addr, + len, + _phantom: PhantomData, + } + } + + fn read(&mut self, buf: &mut [u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts(EP_MEMORY.add(self.addr as _), buf.len()) }; + buf.copy_from_slice(mem); + compiler_fence(Ordering::SeqCst); + } + + fn write(&mut self, buf: &[u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts_mut(EP_MEMORY.add(self.addr as _), buf.len()) }; + mem.copy_from_slice(buf); + compiler_fence(Ordering::SeqCst); + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct EndpointData { + ep_type: EndpointType, // only valid if used + max_packet_size: u16, + used: bool, +} + +impl EndpointData { + const fn new() -> Self { + Self { + ep_type: EndpointType::Bulk, + max_packet_size: 0, + used: false, + } + } +} + +/// RP2040 USB driver handle. +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_in: [EndpointData; EP_COUNT], + ep_out: [EndpointData; EP_COUNT], + ep_mem_free: u16, // first free address in EP mem, in bytes. +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Create a new USB driver. + pub fn new(_usb: Peri<'d, T>, _irq: impl Binding>) -> Self { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let regs = T::regs(); + unsafe { + // zero fill regs + let p = regs.as_ptr() as *mut u32; + for i in 0..0x9c / 4 { + p.add(i).write_volatile(0) + } + + // zero fill epmem + let p = EP_MEMORY as *mut u32; + for i in 0..0x100 / 4 { + p.add(i).write_volatile(0) + } + } + + regs.usb_muxing().write(|w| { + w.set_to_phy(true); + w.set_softcon(true); + }); + regs.usb_pwr().write(|w| { + w.set_vbus_detect(true); + w.set_vbus_detect_override_en(true); + }); + regs.main_ctrl().write(|w| { + w.set_controller_en(true); + }); + + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + + Self { + phantom: PhantomData, + ep_in: [EndpointData::new(); EP_COUNT], + ep_out: [EndpointData::new(); EP_COUNT], + ep_mem_free: 0x180, // data buffer region + } + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + ep_addr: Option, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, driver::EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + let alloc = match D::dir() { + Direction::Out => &mut self.ep_out, + Direction::In => &mut self.ep_in, + }; + + let index = if let Some(addr) = ep_addr { + // Use the specified endpoint address + let requested_index = addr.index(); + if requested_index == 0 || requested_index >= EP_COUNT { + return Err(EndpointAllocError); + } + if alloc[requested_index].used { + return Err(EndpointAllocError); + } + Some((requested_index, &mut alloc[requested_index])) + } else { + // Find any available endpoint + alloc.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 { + return false; // reserved for control pipe + } + !ep.used + }) + }; + + let (index, ep) = index.ok_or(EndpointAllocError)?; + assert!(!ep.used); + + // as per datasheet, the maximum buffer size is 64, except for isochronous + // endpoints, which are allowed to be up to 1023 bytes. + if (ep_type != EndpointType::Isochronous && max_packet_size > 64) || max_packet_size > 1023 { + warn!("max_packet_size too high: {}", max_packet_size); + return Err(EndpointAllocError); + } + + // ep mem addrs must be 64-byte aligned, so there's no point in trying + // to allocate smaller chunks to save memory. + let len = (max_packet_size + 63) / 64 * 64; + + let addr = self.ep_mem_free; + if addr + len > EP_MEMORY_SIZE as u16 { + warn!("Endpoint memory full"); + return Err(EndpointAllocError); + } + self.ep_mem_free += len; + + let buf = EndpointBuffer { + addr, + len, + _phantom: PhantomData, + }; + + trace!(" index={} addr={} len={}", index, buf.addr, buf.len); + + ep.ep_type = ep_type; + ep.used = true; + ep.max_packet_size = max_packet_size; + + let ep_type_reg = match ep_type { + EndpointType::Bulk => pac::usb_dpram::vals::EpControlEndpointType::BULK, + EndpointType::Control => pac::usb_dpram::vals::EpControlEndpointType::CONTROL, + EndpointType::Interrupt => pac::usb_dpram::vals::EpControlEndpointType::INTERRUPT, + EndpointType::Isochronous => pac::usb_dpram::vals::EpControlEndpointType::ISOCHRONOUS, + }; + + match D::dir() { + Direction::Out => T::dpram().ep_out_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }), + Direction::In => T::dpram().ep_in_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }), + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + buf, + }) + } +} + +/// USB interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + //let x = regs.istr().read().0; + //trace!("USB IRQ: {:08x}", x); + + let ints = regs.ints().read(); + + if ints.bus_reset() { + regs.inte().write_clear(|w| w.set_bus_reset(true)); + BUS_WAKER.wake(); + } + if ints.dev_resume_from_host() { + regs.inte().write_clear(|w| w.set_dev_resume_from_host(true)); + BUS_WAKER.wake(); + } + if ints.dev_suspend() { + regs.inte().write_clear(|w| w.set_dev_suspend(true)); + BUS_WAKER.wake(); + } + if ints.setup_req() { + regs.inte().write_clear(|w| w.set_setup_req(true)); + EP_OUT_WAKERS[0].wake(); + } + + if ints.buff_status() { + let s = regs.buff_status().read(); + regs.buff_status().write_value(s); + + for i in 0..EP_COUNT { + if s.ep_in(i) { + EP_IN_WAKERS[i].wake(); + } + if s.ep_out(i) { + EP_OUT_WAKERS[i].wake(); + } + } + } + } +} + +impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + ep_addr: Option, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, ep_addr, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + ep_addr: Option, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, ep_addr, max_packet_size, interval_ms) + } + + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let regs = T::regs(); + regs.inte().write(|w| { + w.set_bus_reset(true); + w.set_buff_status(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + w.set_setup_req(true); + }); + regs.int_ep_ctrl().write(|w| { + w.set_int_ep_active(0xFFFE); // all EPs + }); + regs.sie_ctrl().write(|w| { + w.set_ep0_int_1buf(true); + w.set_pullup_en(true); + }); + + trace!("enabled"); + + ( + Bus { + phantom: PhantomData, + inited: false, + ep_out: self.ep_out, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + }, + ) + } +} + +/// Type representing the RP USB bus. +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_out: [EndpointData; EP_COUNT], + inited: bool, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + BUS_WAKER.register(cx.waker()); + + // TODO: implement VBUS detection. + if !self.inited { + self.inited = true; + return Poll::Ready(Event::PowerDetected); + } + + let regs = T::regs(); + let siestatus = regs.sie_status().read(); + let intrstatus = regs.intr().read(); + + if siestatus.resume() || intrstatus.dev_resume_from_host() { + regs.sie_status().write(|w| w.set_resume(true)); + return Poll::Ready(Event::Resume); + } + + if siestatus.bus_reset() { + regs.sie_status().write(|w| { + w.set_bus_reset(true); + w.set_setup_rec(true); + }); + regs.buff_status().write(|w| w.0 = 0xFFFF_FFFF); + regs.addr_endp().write(|w| w.set_address(0)); + + for i in 1..EP_COUNT { + T::dpram().ep_in_control(i - 1).modify(|w| w.set_enable(false)); + T::dpram().ep_out_control(i - 1).modify(|w| w.set_enable(false)); + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + return Poll::Ready(Event::Reset); + } + + if siestatus.suspended() && intrstatus.dev_suspend() { + regs.sie_status().write(|w| w.set_suspended(true)); + return Poll::Ready(Event::Suspend); + } + + // no pending event. Reenable all irqs. + regs.inte().write_set(|w| { + w.set_bus_reset(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + }); + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + let n = ep_addr.index(); + + if n == 0 { + T::regs().ep_stall_arm().modify(|w| { + if ep_addr.is_in() { + w.set_ep0_in(stalled); + } else { + w.set_ep0_out(stalled); + } + }); + } + + let ctrl = if ep_addr.is_in() { + T::dpram().ep_in_buffer_control(n) + } else { + T::dpram().ep_out_buffer_control(n) + }; + + ctrl.modify(|w| w.set_stall(stalled)); + + let wakers = if ep_addr.is_in() { &EP_IN_WAKERS } else { &EP_OUT_WAKERS }; + wakers[n].wake(); + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + let n = ep_addr.index(); + + let ctrl = if ep_addr.is_in() { + T::dpram().ep_in_buffer_control(n) + } else { + T::dpram().ep_out_buffer_control(n) + }; + + ctrl.read().stall() + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("set_enabled {:?} {}", ep_addr, enabled); + if ep_addr.index() == 0 { + return; + } + + let n = ep_addr.index(); + match ep_addr.direction() { + Direction::In => { + T::dpram().ep_in_control(n - 1).modify(|w| w.set_enable(enabled)); + T::dpram().ep_in_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, true); // first packet is DATA0, but PID is flipped before + }); + EP_IN_WAKERS[n].wake(); + } + Direction::Out => { + T::dpram().ep_out_control(n - 1).modify(|w| w.set_enable(enabled)); + + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + w.set_available(0, true); + }); + EP_OUT_WAKERS[n].wake(); + } + } + } + + async fn enable(&mut self) {} + + async fn disable(&mut self) {} + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +trait Dir { + fn dir() -> Direction; +} + +/// Type for In direction. +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Type for Out direction. +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +/// Endpoint for RP USB driver. +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, + buf: EndpointBuffer, +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + trace!("wait_enabled IN WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_in_control(self.info.addr.index() - 1).read(); + if val.enable() { Poll::Ready(()) } else { Poll::Pending } + }) + .await; + trace!("wait_enabled IN OK"); + } +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_out_control(self.info.addr.index() - 1).read(); + if val.enable() { Poll::Ready(()) } else { Poll::Pending } + }) + .await; + trace!("wait_enabled OUT OK"); + } +} + +impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let val = poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as usize; + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + self.buf.read(&mut buf[..rx_len]); + + trace!("READ OK, rx_len = {}", rx_len); + + let pid = !val.pid(0); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + w.set_available(0, true); + }); + + Ok(rx_len) + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + trace!("WRITE WAITING"); + + let index = self.info.addr.index(); + let val = poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_in_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + self.buf.write(buf); + + let pid = !val.pid(0); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + w.set_available(0, true); + }); + + trace!("WRITE OK"); + + Ok(()) + } +} + +/// Control pipe for RP USB driver. +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, +} + +impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + 64 + } + + async fn setup(&mut self) -> [u8; 8] { + loop { + trace!("SETUP read waiting"); + let regs = T::regs(); + regs.inte().write_set(|w| w.set_setup_req(true)); + + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.sie_status().read().setup_rec() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + let mut buf = [0; 8]; + EndpointBuffer::::new(0, 8).read(&mut buf); + + let regs = T::regs(); + regs.sie_status().write(|w| w.set_setup_rec(true)); + + // set PID to 0, so (after toggling) first DATA is PID 1 + T::dpram().ep_in_buffer_control(0).write(|w| w.set_pid(0, false)); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_pid(0, false)); + + trace!("SETUP read ok"); + return buf; + } + } + + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + let bufcontrol = T::dpram().ep_out_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + w.set_available(0, true); + }); + + trace!("control: data_out len={} first={} last={}", buf.len(), first, last); + let val = poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(0).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as _; + trace!("control data_out DONE, rx_len = {}", rx_len); + + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).read(&mut buf[..rx_len]); + + Ok(rx_len) + } + + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in len={} first={} last={}", data.len(), first, last); + + if data.len() > 64 { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).write(data); + + let bufcontrol = T::dpram().ep_in_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, data.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, data.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + w.set_available(0, true); + }); + + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let bufcontrol = T::dpram().ep_in_buffer_control(0); + if bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("control: data_in DONE"); + + if last { + // prepare status phase right away. + let bufcontrol = T::dpram().ep_out_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_available(0, true); + }); + } + + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + let bufcontrol = T::dpram().ep_in_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + w.set_available(0, true); + }); + + // wait for completion before returning, needed so + // set_address() doesn't happen early. + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + if bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } + + async fn reject(&mut self) { + trace!("control: reject"); + + let regs = T::regs(); + regs.ep_stall_arm().write_set(|w| { + w.set_ep0_in(true); + w.set_ep0_out(true); + }); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_stall(true)); + T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + + let regs = T::regs(); + trace!("setting addr: {}", addr); + regs.addr_endp().write(|w| w.set_address(addr)) + } +} diff --git a/embassy-rp-fork/src/watchdog.rs b/embassy-rp-fork/src/watchdog.rs new file mode 100644 index 0000000..d426017 --- /dev/null +++ b/embassy-rp-fork/src/watchdog.rs @@ -0,0 +1,173 @@ +//! Watchdog +//! +//! The watchdog is a countdown timer that can restart parts of the chip if it reaches zero. This can be used to restart the +//! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to +//! stop it from reaching zero. +//! +//! Credit: based on `rp-hal` implementation (also licensed Apache+MIT) + +use core::marker::PhantomData; + +use embassy_time::Duration; + +use crate::peripherals::WATCHDOG; +use crate::{Peri, pac}; + +/// The reason for a system reset from the watchdog. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ResetReason { + /// The reset was forced. + Forced, + /// The watchdog was not fed in time. + TimedOut, +} + +/// Watchdog peripheral +pub struct Watchdog { + phantom: PhantomData, + load_value: u32, // decremented by 2 per tick (µs) +} + +impl Watchdog { + /// Create a new `Watchdog` + pub fn new(_watchdog: Peri<'static, WATCHDOG>) -> Self { + Self { + phantom: PhantomData, + load_value: 0, + } + } + + /// Start tick generation on clk_tick which is driven from clk_ref. + /// + /// # Arguments + /// + /// * `cycles` - Total number of tick cycles before the next tick is generated. + /// It is expected to be the frequency in MHz of clk_ref. + #[cfg(feature = "rp2040")] + pub fn enable_tick_generation(&mut self, cycles: u8) { + let watchdog = pac::WATCHDOG; + watchdog.tick().write(|w| { + w.set_enable(true); + w.set_cycles(cycles.into()) + }); + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + pub fn pause_on_debug(&mut self, pause: bool) { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().modify(|w| { + w.set_pause_dbg0(pause); + w.set_pause_dbg1(pause); + w.set_pause_jtag(pause); + }) + } + + fn load_counter(&self, counter: u32) { + let watchdog = pac::WATCHDOG; + watchdog.load().write_value(pac::watchdog::regs::Load(counter)); + } + + fn enable(&self, bit: bool) { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().modify(|w| w.set_enable(bit)) + } + + // Configure which hardware will be reset by the watchdog + // (everything except ROSC, XOSC) + fn configure_wdog_reset_triggers(&self) { + let psm = pac::PSM; + psm.wdsel().write_value(pac::psm::regs::Wdsel( + 0x0001ffff & !(0x01 << 0usize) & !(0x01 << 1usize), + )); + } + + /// Feed the watchdog timer + pub fn feed(&mut self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog timer + pub fn start(&mut self, period: Duration) { + #[cfg(feature = "rp2040")] + const MAX_PERIOD: u32 = 0xFFFFFF / 2; + #[cfg(feature = "_rp235x")] + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.as_micros(); + if delay_us > (MAX_PERIOD) as u64 { + panic!("Period cannot exceed {} microseconds", MAX_PERIOD); + } + let delay_us = delay_us as u32; + + // Due to a logic error, the watchdog decrements by 2 and + // the load value must be compensated; see RP2040-E1 + // This errata is fixed in the RP235x + if cfg!(feature = "rp2040") { + self.load_value = delay_us * 2; + } else { + self.load_value = delay_us; + } + + self.enable(false); + self.configure_wdog_reset_triggers(); + self.load_counter(self.load_value); + self.enable(true); + } + + /// Trigger a system reset + pub fn trigger_reset(&mut self) { + self.configure_wdog_reset_triggers(); + self.pause_on_debug(false); + self.enable(true); + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| { + w.set_trigger(true); + }) + } + + /// Store data in scratch register + pub fn set_scratch(&mut self, index: usize, value: u32) { + let watchdog = pac::WATCHDOG; + match index { + 0 => watchdog.scratch0().write(|w| *w = value), + 1 => watchdog.scratch1().write(|w| *w = value), + 2 => watchdog.scratch2().write(|w| *w = value), + 3 => watchdog.scratch3().write(|w| *w = value), + 4 => watchdog.scratch4().write(|w| *w = value), + 5 => watchdog.scratch5().write(|w| *w = value), + 6 => watchdog.scratch6().write(|w| *w = value), + 7 => watchdog.scratch7().write(|w| *w = value), + _ => panic!("Invalid watchdog scratch index"), + } + } + + /// Read data from scratch register + pub fn get_scratch(&mut self, index: usize) -> u32 { + let watchdog = pac::WATCHDOG; + match index { + 0 => watchdog.scratch0().read(), + 1 => watchdog.scratch1().read(), + 2 => watchdog.scratch2().read(), + 3 => watchdog.scratch3().read(), + 4 => watchdog.scratch4().read(), + 5 => watchdog.scratch5().read(), + 6 => watchdog.scratch6().read(), + 7 => watchdog.scratch7().read(), + _ => panic!("Invalid watchdog scratch index"), + } + } + + /// Get the reason for the last system reset, if it was caused by the watchdog. + pub fn reset_reason(&self) -> Option { + let watchdog = pac::WATCHDOG; + let reason = watchdog.reason().read(); + if reason.force() { + Some(ResetReason::Forced) + } else if reason.timer() { + Some(ResetReason::TimedOut) + } else { + None + } + } +} diff --git a/hwsigner-secure/.cargo/config.toml b/hwsigner-secure/.cargo/config.toml new file mode 100644 index 0000000..4e9fa9c --- /dev/null +++ b/hwsigner-secure/.cargo/config.toml @@ -0,0 +1,20 @@ +[target.thumbv8m.main-none-eabihf] +runner = "probe-rs run --chip RP2350" +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "target-cpu=cortex-m33", + + # Use GNU linker for CMSE veneer generation + "-C", "linker=arm-none-eabi-ld", + "-C", "link-arg=--allow-multiple-definition", + "-C", "link-arg=--cmse-implib", + "-C", "link-arg=--out-implib=target/veneers.o", +] + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/hwsigner-secure/Cargo.lock b/hwsigner-secure/Cargo.lock new file mode 100644 index 0000000..6c029d5 --- /dev/null +++ b/hwsigner-secure/Cargo.lock @@ -0,0 +1,1006 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6eca0aae8aa2cf8333200ecbd236274697bc0a394765c858b3d9372eb1abcfa" +dependencies = [ + "critical-section", + "defmt 0.3.100", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "signature", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "embedded-alloc" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db634e07f552215f7a3572dc0a4d29760c637fcea5a8c18d2cc291f782c216c" +dependencies = [ + "const-default", + "critical-section", + "linked_list_allocator", + "rlsf", +] + +[[package]] +name = "embedded-dma" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "994f7e5b5cb23521c22304927195f236813053eb9c065dd2226a32ba64695446" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "frunk" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28aef0f9aa070bce60767c12ba9cb41efeaf1a2bc6427f87b7d83f11239a16d7" +dependencies = [ + "frunk_core", + "frunk_derives", +] + +[[package]] +name = "frunk_core" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476eeaa382e3462b84da5d6ba3da97b5786823c2d0d3a0d04ef088d073da225c" + +[[package]] +name = "frunk_derives" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0b4095fc99e1d858e5b8c7125d2638372ec85aa0fe6c807105cf10b0265ca6c" +dependencies = [ + "frunk_proc_macro_helpers", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "frunk_proc_macro_helpers" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1952b802269f2db12ab7c0bd328d0ae8feaabf19f352a7b0af7bb0c5693abfce" +dependencies = [ + "frunk_core", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hwsigner-secure" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 0.3.100", + "defmt-rtt", + "embedded-alloc", + "embedded-hal 1.0.0", + "k256", + "panic-probe", + "rand_core", + "rp235x-hal", + "serde", + "serde_json", + "threshold", + "zeroize", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", + "signature", +] + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "linked_list_allocator" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afa463f5405ee81cdb9cc2baf37e08ec7e4c8209442b5d72c04cfb2cd6e6286" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "panic-probe" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0" +dependencies = [ + "cortex-m", + "defmt 0.3.100", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pio" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e09694b50f89f302ed531c1f2a7569f0be5867aee4ab4f8f729bbeec0078e3" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f5c1b8bf41ea746266cdee443d1d1e9125c86ce1447e1a2615abd34330d33a9" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", +] + +[[package]] +name = "riscv-rt" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d35e32cf1383183e8885d8a9aa4402a087fd094dc34c2cb6df6687d0229dfe" +dependencies = [ + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", +] + +[[package]] +name = "rp-binary-info" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f582261945fa215d40e2988b4df595d11c0908c0fff97a0fe23df766d117b790" + +[[package]] +name = "rp-hal-common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288358786b1458fb2caac8c4b40fb529ef4200d6c46467e2695b7a8ba573ae8" +dependencies = [ + "fugit", +] + +[[package]] +name = "rp235x-hal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2939c82776b0b4ae110168b4298b5adf831e6cff249b057bf2a2187453b959c" +dependencies = [ + "bitfield 0.14.0", + "cortex-m", + "cortex-m-rt", + "critical-section", + "embedded-dma", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "frunk", + "fugit", + "gcd", + "itertools", + "nb 1.1.0", + "paste", + "pio", + "rand_core", + "riscv", + "riscv-rt", + "rp-binary-info", + "rp-hal-common", + "rp235x-hal-macros", + "rp235x-pac", + "sha2-const-stable", + "usb-device", + "vcell", + "void", +] + +[[package]] +name = "rp235x-hal-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74edd7a5979e9763bbb98e9746e711bac7464ee3397af7288e6c288ff0d3c764" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rp235x-pac" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffcb6931deee4242886b5a1df62db5e2555b0eb6ae1e8be101f3ea3e58e65c6" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "critical-section", + "vcell", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "threshold" +version = "0.1.0" +dependencies = [ + "elliptic-curve", + "hex-conservative", + "k256", + "rand_core", + "serde", + "serde_json", + "sha2", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/pico-signer/Cargo.toml b/hwsigner-secure/Cargo.toml similarity index 55% rename from pico-signer/Cargo.toml rename to hwsigner-secure/Cargo.toml index 8a99289..5e0e643 100644 --- a/pico-signer/Cargo.toml +++ b/hwsigner-secure/Cargo.toml @@ -1,25 +1,25 @@ [package] -name = "pico-signer" +name = "hwsigner-secure" version = "0.1.0" edition = "2021" +[[bin]] +name = "hwsigner-secure" +path = "src/main.rs" + [dependencies] -# Embassy framework for RP2350 -embassy-executor = { version = "0.9", features = ["arch-cortex-m", "executor-thread", "defmt"] } -embassy-rp = { version = "0.9", features = ["defmt", "rp235xa", "time-driver"] } -embassy-time = { version = "0.5", features = ["defmt"] } -embassy-usb = { version = "0.5", features = ["defmt"] } -embassy-futures = "0.1" -embassy-sync = { version = "0.7", features = ["defmt"] } +# RP2350 HAL for clock init (same as ~/blinker) +rp235x-hal = { version = "0.3", features = ["rt", "critical-section-impl"] } # Cortex-M support -cortex-m = { version = "0.7", features = ["critical-section-single-core"] } -cortex-m-rt = "0.7" +# NOTE: critical-section-impl provided by rp235x-hal, don't enable it here +cortex-m = "0.7" +cortex-m-rt = { version = "0.7", features = ["device"] } # Threshold crypto (no_std, no default features) threshold = { path = "../threshold", default-features = false } -# Crypto primitives (match threshold-rs versions) +# Crypto primitives k256 = { version = "0.13", default-features = false, features = ["arithmetic"] } rand_core = { version = "0.6", default-features = false } @@ -30,6 +30,12 @@ serde_json = { version = "1", default-features = false, features = ["alloc"] } # Allocator embedded-alloc = "0.7" +# HAL traits (for delay, GPIO) +embedded-hal = "1.0.0" + +# Zeroize secrets in RAM +zeroize = { version = "1", default-features = false, features = ["zeroize_derive"] } + # Logging defmt = "0.3" defmt-rtt = "0.4" @@ -37,9 +43,6 @@ defmt-rtt = "0.4" # Panic handler panic-probe = { version = "0.3", features = ["print-defmt"] } -# Flash storage -embedded-storage = "0.3" - [profile.release] opt-level = "s" lto = true diff --git a/hwsigner-secure/build.rs b/hwsigner-secure/build.rs new file mode 100644 index 0000000..6818148 --- /dev/null +++ b/hwsigner-secure/build.rs @@ -0,0 +1,16 @@ +use std::env; +use std::fs; +use std::path::PathBuf; + +fn main() { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + + // Copy linker scripts + fs::copy("secure.x", out.join("memory.x")).unwrap(); + fs::copy("device.x", out.join("device.x")).unwrap(); + + println!("cargo:rerun-if-changed=secure.x"); + println!("cargo:rerun-if-changed=device.x"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rustc-link-search={}", out.display()); +} diff --git a/hwsigner-secure/device.x b/hwsigner-secure/device.x new file mode 100644 index 0000000..bd09cb3 --- /dev/null +++ b/hwsigner-secure/device.x @@ -0,0 +1,51 @@ +PROVIDE(TIMER0_IRQ_0 = DefaultHandler); +PROVIDE(TIMER0_IRQ_1 = DefaultHandler); +PROVIDE(TIMER0_IRQ_2 = DefaultHandler); +PROVIDE(TIMER0_IRQ_3 = DefaultHandler); +PROVIDE(TIMER1_IRQ_0 = DefaultHandler); +PROVIDE(TIMER1_IRQ_1 = DefaultHandler); +PROVIDE(TIMER1_IRQ_2 = DefaultHandler); +PROVIDE(TIMER1_IRQ_3 = DefaultHandler); +PROVIDE(PWM_IRQ_WRAP_0 = DefaultHandler); +PROVIDE(PWM_IRQ_WRAP_1 = DefaultHandler); +PROVIDE(DMA_IRQ_0 = DefaultHandler); +PROVIDE(DMA_IRQ_1 = DefaultHandler); +PROVIDE(DMA_IRQ_2 = DefaultHandler); +PROVIDE(DMA_IRQ_3 = DefaultHandler); +PROVIDE(USBCTRL_IRQ = DefaultHandler); +PROVIDE(PIO0_IRQ_0 = DefaultHandler); +PROVIDE(PIO0_IRQ_1 = DefaultHandler); +PROVIDE(PIO1_IRQ_0 = DefaultHandler); +PROVIDE(PIO1_IRQ_1 = DefaultHandler); +PROVIDE(PIO2_IRQ_0 = DefaultHandler); +PROVIDE(PIO2_IRQ_1 = DefaultHandler); +PROVIDE(IO_IRQ_BANK0 = DefaultHandler); +PROVIDE(IO_IRQ_BANK0_NS = DefaultHandler); +PROVIDE(IO_IRQ_QSPI = DefaultHandler); +PROVIDE(IO_IRQ_QSPI_NS = DefaultHandler); +PROVIDE(SIO_IRQ_FIFO = DefaultHandler); +PROVIDE(SIO_IRQ_BELL = DefaultHandler); +PROVIDE(SIO_IRQ_FIFO_NS = DefaultHandler); +PROVIDE(SIO_IRQ_BELL_NS = DefaultHandler); +PROVIDE(SIO_IRQ_MTIMECMP = DefaultHandler); +PROVIDE(CLOCKS_IRQ = DefaultHandler); +PROVIDE(SPI0_IRQ = DefaultHandler); +PROVIDE(SPI1_IRQ = DefaultHandler); +PROVIDE(UART0_IRQ = DefaultHandler); +PROVIDE(UART1_IRQ = DefaultHandler); +PROVIDE(ADC_IRQ_FIFO = DefaultHandler); +PROVIDE(I2C0_IRQ = DefaultHandler); +PROVIDE(I2C1_IRQ = DefaultHandler); +PROVIDE(OTP_IRQ = DefaultHandler); +PROVIDE(TRNG_IRQ = DefaultHandler); +PROVIDE(PLL_SYS_IRQ = DefaultHandler); +PROVIDE(PLL_USB_IRQ = DefaultHandler); +PROVIDE(POWMAN_IRQ_POW = DefaultHandler); +PROVIDE(POWMAN_IRQ_TIMER = DefaultHandler); +PROVIDE(SW0_IRQ = DefaultHandler); +PROVIDE(SW1_IRQ = DefaultHandler); +PROVIDE(SW2_IRQ = DefaultHandler); +PROVIDE(SW3_IRQ = DefaultHandler); +PROVIDE(SW4_IRQ = DefaultHandler); +PROVIDE(SW5_IRQ = DefaultHandler); + diff --git a/hwsigner-secure/secure.x b/hwsigner-secure/secure.x new file mode 100644 index 0000000..35d44d3 --- /dev/null +++ b/hwsigner-secure/secure.x @@ -0,0 +1,43 @@ +/* + * RP2350 Secure world linker script. + * + * Secure boot image: 0x10000000 - 0x1007FFFF (512K) + * Includes boot code, crypto library, NSC veneers + * Key flash: 0x103FF000 - 0x103FFFFF (4K, SAU-protected) + * Secure RAM: 0x20060000 - 0x2007FFFF (128K) + * + * NS flash starts at 0x10080000. + */ + +MEMORY { + FLASH : ORIGIN = 0x10000000, LENGTH = 512K + RAM : ORIGIN = 0x20060000, LENGTH = 128K +} + +SECTIONS { + .start_block : ALIGN(4) { + __start_block_addr = .; + KEEP(*(.start_block)); + } > FLASH +} INSERT AFTER .vector_table; + +_stext = ADDR(.start_block) + SIZEOF(.start_block); + +SECTIONS { + .bi_entries : ALIGN(4) { + __bi_entries_start = .; + KEEP(*(.bi_entries)); + . = ALIGN(4); + __bi_entries_end = .; + } > FLASH +} INSERT AFTER .text; + +SECTIONS { + .end_block : ALIGN(4) { + __end_block_addr = .; + KEEP(*(.end_block)); + } > FLASH +} INSERT AFTER .uninit; + +PROVIDE(start_to_end = __end_block_addr - __start_block_addr); +PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/hwsigner-secure/src/allocator.rs b/hwsigner-secure/src/allocator.rs new file mode 100644 index 0000000..92d0751 --- /dev/null +++ b/hwsigner-secure/src/allocator.rs @@ -0,0 +1,15 @@ +//! Secure world heap allocator (64KB from Secure RAM). + +use embedded_alloc::LlffHeap as Heap; + +#[global_allocator] +static HEAP: Heap = Heap::empty(); + +const HEAP_SIZE: usize = 64 * 1024; +static mut HEAP_MEM: [u8; HEAP_SIZE] = [0u8; HEAP_SIZE]; + +pub fn init() { + unsafe { + HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE); + } +} diff --git a/hwsigner-secure/src/handler.rs b/hwsigner-secure/src/handler.rs new file mode 100644 index 0000000..ff953f4 --- /dev/null +++ b/hwsigner-secure/src/handler.rs @@ -0,0 +1,302 @@ +//! Signer state and command handlers (Secure world). +//! +//! All secret operations — DKG, nonce generation, signing — happen here. +//! The Non-Secure world never has access to secret key material. + +extern crate alloc; +use alloc::collections::BTreeMap; +use alloc::format; +use alloc::string::String; +use alloc::vec::Vec; + +use k256::Scalar; +use threshold::dkg::{self, Round1Package, Round1SecretPackage, Round2Package, Round2SecretPackage}; +use threshold::identifier::Identifier; +use threshold::keys::{KeyPackage, PublicKeyPackage}; +use threshold::nonce::{self, SigningNonce}; +use threshold::point; +use threshold::scalar::scalar_to_bytes; +use zeroize::Zeroize; + +use crate::rng::SecureRng; + +use crate::protocol; + +// --------------------------------------------------------------------------- +// Signer state +// --------------------------------------------------------------------------- + +pub struct SignerState { + rng: SecureRng, + r1_secret: Option, + r2_secret: Option, + key_package: Option, + public_key_package: Option, + pending_nonce: Option, + dkg_secret: Option<[u8; 32]>, +} + +impl Drop for SignerState { + fn drop(&mut self) { + // Zero all secret material on drop + if let Some(ref mut secret) = self.dkg_secret { + secret.zeroize(); + } + self.r1_secret = None; + self.r2_secret = None; + self.pending_nonce = None; + } +} + +impl SignerState { + pub fn new(rng: SecureRng) -> Self { + Self { + rng, + r1_secret: None, + r2_secret: None, + key_package: None, + public_key_package: None, + pending_nonce: None, + dkg_secret: None, + } + } + + /// Restore key material loaded from flash. + pub fn restore_keys(&mut self, kp: KeyPackage, pkp: PublicKeyPackage, dkg_secret: Option<[u8; 32]>) { + self.key_package = Some(kp); + self.public_key_package = Some(pkp); + self.dkg_secret = dkg_secret; + } + + pub fn key_package(&self) -> Option<&KeyPackage> { + self.key_package.as_ref() + } + + pub fn public_key_package(&self) -> Option<&PublicKeyPackage> { + self.public_key_package.as_ref() + } + + pub fn dkg_secret(&self) -> Option<&[u8; 32]> { + self.dkg_secret.as_ref() + } + + pub fn handle(&mut self, req: protocol::Request) -> protocol::Response { + use protocol::{Request, Response}; + match req { + Request::DkgInit { max_signers, min_signers } => { + self.handle_dkg_init(max_signers, min_signers) + } + Request::RestoreInit { max_signers, min_signers } => { + self.handle_restore_init(max_signers, min_signers) + } + Request::DkgRound2 { round1_packages, receiver_identifiers } => { + self.handle_dkg_round2(round1_packages, receiver_identifiers) + } + Request::DkgRound3 { round1_packages, round2_packages, receiver_identifiers } => { + self.handle_dkg_round3(round1_packages, round2_packages, receiver_identifiers) + } + Request::GenerateNonce => self.handle_generate_nonce(), + Request::Sign { message_hex, commitments, apply_tweak, merkle_root_hex } => { + self.handle_sign(message_hex, commitments, apply_tweak, merkle_root_hex) + } + Request::GetInfo => self.handle_get_info(), + } + } + + fn handle_dkg_init(&mut self, max_signers: usize, min_signers: usize) -> protocol::Response { + let secret = random_scalar(&mut self.rng); + let mut coefficients = Vec::with_capacity(min_signers - 1); + for _ in 0..(min_signers - 1) { + coefficients.push(random_scalar(&mut self.rng)); + } + self.dkg_secret = Some(scalar_to_bytes(&secret)); + + match dkg::dkg_part1(max_signers, min_signers, &secret, &coefficients, &mut self.rng) { + Ok((secret_pkg, pub_pkg)) => { + let id_hex = hex_encode(&secret_pkg.identifier.serialize()); + let vk_hex = hex_encode(&pub_pkg.verifying_key.serialize()); + let r1_json = pub_pkg.to_json_value(); + self.r1_secret = Some(secret_pkg); + protocol::Response::DkgInit { round1_package_json: r1_json, verifying_key_hex: vk_hex, identifier_hex: id_hex } + } + Err(e) => protocol::Response::Error { error: format!("dkg_init failed: {}", e) }, + } + } + + fn handle_restore_init(&mut self, max_signers: usize, min_signers: usize) -> protocol::Response { + let secret_bytes = match &self.dkg_secret { + Some(s) => *s, + None => return protocol::Response::Error { error: "no DKG secret stored; cannot restore".into() }, + }; + let secret = match scalar_from_bytes(&secret_bytes) { + Ok(s) => s, + Err(e) => return protocol::Response::Error { error: e }, + }; + let mut coefficients = Vec::with_capacity(min_signers - 1); + for _ in 0..(min_signers - 1) { + coefficients.push(random_scalar(&mut self.rng)); + } + match dkg::dkg_part1(max_signers, min_signers, &secret, &coefficients, &mut self.rng) { + Ok((secret_pkg, pub_pkg)) => { + let id_hex = hex_encode(&secret_pkg.identifier.serialize()); + let vk_hex = hex_encode(&pub_pkg.verifying_key.serialize()); + let r1_json = pub_pkg.to_json_value(); + self.r1_secret = Some(secret_pkg); + protocol::Response::DkgInit { round1_package_json: r1_json, verifying_key_hex: vk_hex, identifier_hex: id_hex } + } + Err(e) => protocol::Response::Error { error: format!("restore_init failed: {}", e) }, + } + } + + fn handle_dkg_round2(&mut self, round1_packages: BTreeMap, receiver_identifier_hexes: Vec) -> protocol::Response { + let r1_secret = match &self.r1_secret { + Some(s) => s, + None => return protocol::Response::Error { error: "no round 1 secret package".into() }, + }; + let mut r1_pkgs: BTreeMap = BTreeMap::new(); + for (id_hex, pkg_json) in &round1_packages { + let id = match parse_identifier(id_hex) { Ok(id) => id, Err(e) => return protocol::Response::Error { error: e } }; + let pkg = match Round1Package::from_json_value(pkg_json) { Ok(p) => p, Err(e) => return protocol::Response::Error { error: format!("failed to parse Round1Package: {}", e) } }; + r1_pkgs.insert(id, pkg); + } + let mut receiver_ids = Vec::new(); + for hex_str in &receiver_identifier_hexes { + match parse_identifier(hex_str) { Ok(id) => receiver_ids.push(id), Err(e) => return protocol::Response::Error { error: e } } + } + match dkg::dkg_part2(r1_secret, &r1_pkgs, &receiver_ids) { + Ok((r2_secret, r2_out)) => { + let mut r2_map: BTreeMap = BTreeMap::new(); + for (id, pkg) in &r2_out { r2_map.insert(hex_encode(&id.serialize()), pkg.to_json_value()); } + self.r2_secret = Some(r2_secret); + protocol::Response::DkgRound2 { round2_packages: r2_map } + } + Err(e) => protocol::Response::Error { error: format!("dkg_round2 failed: {}", e) }, + } + } + + fn handle_dkg_round3(&mut self, round1_packages: BTreeMap, round2_packages: BTreeMap, receiver_identifier_hexes: Vec) -> protocol::Response { + let r1_secret = match &self.r1_secret { Some(s) => s, None => return protocol::Response::Error { error: "no round 1 secret package".into() } }; + let r2_secret = match &self.r2_secret { Some(s) => s, None => return protocol::Response::Error { error: "no round 2 secret package".into() } }; + let mut r1_pkgs: BTreeMap = BTreeMap::new(); + for (id_hex, pkg_json) in &round1_packages { + let id = match parse_identifier(id_hex) { Ok(id) => id, Err(e) => return protocol::Response::Error { error: e } }; + let pkg = match Round1Package::from_json_value(pkg_json) { Ok(p) => p, Err(e) => return protocol::Response::Error { error: format!("failed to parse Round1Package: {}", e) } }; + r1_pkgs.insert(id, pkg); + } + let mut r2_pkgs: BTreeMap = BTreeMap::new(); + for (id_hex, pkg_json) in &round2_packages { + let id = match parse_identifier(id_hex) { Ok(id) => id, Err(e) => return protocol::Response::Error { error: e } }; + let pkg = match Round2Package::from_json_value(pkg_json) { Ok(p) => p, Err(e) => return protocol::Response::Error { error: format!("failed to parse Round2Package: {}", e) } }; + r2_pkgs.insert(id, pkg); + } + let mut receiver_ids = Vec::new(); + for hex_str in &receiver_identifier_hexes { match parse_identifier(hex_str) { Ok(id) => receiver_ids.push(id), Err(e) => return protocol::Response::Error { error: e } } } + match dkg::dkg_part3(r1_secret, r2_secret, &r1_pkgs, &r2_pkgs, &receiver_ids) { + Ok((kp, pkp)) => { + let id_hex = hex_encode(&kp.identifier.serialize()); + let pk_hex = hex_encode(&pkp.verifying_key.serialize()); + self.key_package = Some(kp); + self.public_key_package = Some(pkp); + self.r1_secret = None; + self.r2_secret = None; + protocol::Response::DkgRound3 { ok: true, identifier_hex: id_hex, public_key_hex: pk_hex } + } + Err(e) => protocol::Response::Error { error: format!("dkg_round3 failed: {}", e) }, + } + } + + fn handle_generate_nonce(&mut self) -> protocol::Response { + let kp = match &self.key_package { Some(kp) => kp, None => return protocol::Response::Error { error: "no key package".into() } }; + let signing_nonce = nonce::new_nonce(&mut self.rng, &kp.secret_share); + let hiding_hex = hex_encode(&point::serialize_compressed(&signing_nonce.commitments.hiding)); + let binding_hex = hex_encode(&point::serialize_compressed(&signing_nonce.commitments.binding)); + self.pending_nonce = Some(signing_nonce); + protocol::Response::GenerateNonce { hiding_hex, binding_hex } + } + + fn handle_sign(&mut self, message_hex: String, commitments: BTreeMap, apply_tweak: bool, merkle_root_hex: Option) -> protocol::Response { + let kp = match &self.key_package { Some(kp) => kp.clone(), None => return protocol::Response::Error { error: "no key package".into() } }; + let pkp = match &self.public_key_package { Some(pkp) => pkp.clone(), None => return protocol::Response::Error { error: "no public key package".into() } }; + let signing_nonce = match self.pending_nonce.take() { Some(n) => n, None => return protocol::Response::Error { error: "no pending nonce".into() } }; + let message = match hex_decode(&message_hex) { Ok(m) => m, Err(e) => return protocol::Response::Error { error: e } }; + let mut signing_commitments = BTreeMap::new(); + for (id_hex, comm_json) in &commitments { + let id = match parse_identifier(id_hex) { Ok(id) => id, Err(e) => return protocol::Response::Error { error: e } }; + let hiding_hex = match comm_json["hiding"].as_str() { Some(h) => h, None => return protocol::Response::Error { error: "missing hiding commitment".into() } }; + let binding_hex = match comm_json["binding"].as_str() { Some(b) => b, None => return protocol::Response::Error { error: "missing binding commitment".into() } }; + let hiding = match parse_point(hiding_hex) { Ok(p) => p, Err(e) => return protocol::Response::Error { error: e } }; + let binding = match parse_point(binding_hex) { Ok(p) => p, Err(e) => return protocol::Response::Error { error: e } }; + signing_commitments.insert(id, threshold::nonce::SigningCommitments { binding, hiding }); + } + let (final_kp, _final_pkp) = if apply_tweak { + let merkle_root = merkle_root_hex.as_ref().and_then(|h| hex_decode(h).ok()); + let mr_ref = merkle_root.as_deref(); + (kp.tweak(mr_ref), pkp.tweak(mr_ref)) + } else { (kp, pkp) }; + let signing_package = threshold::commitment::SigningPackage::new(signing_commitments, message); + match threshold::signing::sign(&signing_package, &signing_nonce, &final_kp) { + Ok(share) => protocol::Response::Sign { share_hex: hex_encode(&scalar_to_bytes(&share.s)) }, + Err(e) => protocol::Response::Error { error: format!("sign failed: {}", e) }, + } + } + + fn handle_get_info(&self) -> protocol::Response { + let identifier_hex = self.key_package.as_ref().map(|kp| hex_encode(&kp.identifier.serialize())); + protocol::Response::Info { has_key_package: self.key_package.is_some(), has_pending_nonce: self.pending_nonce.is_some(), identifier_hex } + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +fn scalar_from_bytes(bytes: &[u8; 32]) -> Result { + use k256::elliptic_curve::scalar::FromUintUnchecked; + use k256::U256; + let uint = U256::from_be_slice(bytes); + let s = Scalar::from_uint_unchecked(uint); + if bool::from(s.is_zero()) { return Err("stored DKG secret is zero".into()); } + Ok(s) +} + +fn random_scalar(rng: &mut impl rand_core::RngCore) -> Scalar { + use k256::elliptic_curve::ops::Reduce; + use k256::U256; + loop { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + let wide = U256::from_be_slice(&bytes); + let s = >::reduce(wide); + if !bool::from(s.is_zero()) { return s; } + } +} + +fn hex_encode(bytes: &[u8]) -> String { + bytes.iter().map(|b| format!("{:02x}", b)).collect() +} + +fn hex_decode(s: &str) -> Result, String> { + if s.len() % 2 != 0 { return Err("odd-length hex string".into()); } + let mut out = Vec::with_capacity(s.len() / 2); + for i in (0..s.len()).step_by(2) { + let byte = u8::from_str_radix(&s[i..i + 2], 16).map_err(|_| format!("invalid hex at position {}", i))?; + out.push(byte); + } + Ok(out) +} + +fn parse_identifier(hex_str: &str) -> Result { + let bytes = hex_decode(hex_str)?; + if bytes.len() != 32 { return Err(format!("identifier must be 32 bytes, got {}", bytes.len())); } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Identifier::deserialize(&arr).map_err(|e| format!("invalid identifier: {}", e)) +} + +fn parse_point(hex_str: &str) -> Result { + let bytes = hex_decode(hex_str)?; + if bytes.len() != 33 { return Err(format!("point must be 33 bytes, got {}", bytes.len())); } + let mut arr = [0u8; 33]; + arr.copy_from_slice(&bytes); + point::deserialize_compressed(&arr).map_err(|e| format!("invalid point: {}", e)) +} diff --git a/hwsigner-secure/src/main.rs b/hwsigner-secure/src/main.rs new file mode 100644 index 0000000..709db48 --- /dev/null +++ b/hwsigner-secure/src/main.rs @@ -0,0 +1,317 @@ +//! Secure world boot image for RP2350 TrustZone HW Signer. +//! +//! Boots first from ROM, initializes clocks/PLLs via rp235x-hal, +//! configures SAU + ACCESSCTRL, initializes the crypto library, +//! then hands off to the Non-Secure world via BXNS. + +#![no_std] +#![no_main] +#![feature(abi_cmse_nonsecure_call)] +#![feature(cmse_nonsecure_entry)] + +extern crate alloc; + +mod allocator; +mod handler; +pub mod nsc; +pub mod protocol; +mod rng; +mod storage; + +use cortex_m::peripheral::sau::{Ctrl, Rbar, Rlar, Rnr}; +use defmt::*; +use defmt_rtt as _; +use panic_probe as _; + +use rp235x_hal as hal; +use hal::entry; + +/// Non-Secure world entry address (start of NS flash). +const NS_FLASH_BASE: u32 = 0x1008_0000; + +/// Top of NS RAM (NS MSP initial value). +const NS_RAM_TOP: u32 = 0x2006_0000; + +/// External crystal frequency (Pico 2 = 12 MHz). +const XTAL_FREQ_HZ: u32 = 12_000_000; + +/// PICOBIN image definition — tells boot ROM this is a Secure ARM RP2350 image. +#[unsafe(link_section = ".start_block")] +#[used] +pub static IMAGE_DEF: hal::block::ImageDef = hal::block::ImageDef::secure_exe(); + +#[entry] +fn main() -> ! { + info!("Secure world booting"); + + // 1. Initialize Secure heap + allocator::init(); + + // 2. Initialize RP2350 hardware via rp235x-hal + let mut pac = hal::pac::Peripherals::take().unwrap(); + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + let _clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + info!("Clocks initialized: sys=150MHz, usb=48MHz"); + + // 3. Initialize crypto library + rng::init_trng(); + let key_material = storage::load(); + nsc::init_signer_state(key_material); + info!("Crypto library initialized"); + + // 4. Deassert NS peripherals from reset (Secure world owns RESETS) + // The rp235x-hal clock init only deasserts clock-related peripherals. + // The NS world needs IO_BANK0, PADS_BANK0, DMA, USB, etc. + { + let peris_to_deassert: u32 = + (1 << 2) | // DMA + (1 << 5) | // IO_BANK0 + (1 << 8) | // PADS_BANK0 + (1 << 14) | // SPI0 + (1 << 24); // USBCTRL + // RESETS base = 0x40020000 + // Atomic CLR alias at +0x3000 (clears bits = deasserts reset) + unsafe { + core::ptr::write_volatile(0x4002_3000 as *mut u32, peris_to_deassert); + // Wait for reset done (RESET_DONE at +0x08) + while core::ptr::read_volatile(0x4002_0008 as *const u32) & peris_to_deassert + != peris_to_deassert {} + } + info!("NS peripherals deasserted from reset"); + } + + // 5. Configure DMA internal security — allow NS access to all channels/IRQs + unsafe { configure_dma_security() }; + + // 6. Configure SAU — mark NS regions, protect Secure flash/RAM/keys + unsafe { configure_sau() }; + + // 7. Configure ACCESSCTRL — grant NS access to USB/clocks/timers, keep TRNG Secure + unsafe { configure_accessctrl() }; + + // 8. Lock ACCESSCTRL — prevent NS code from reconfiguring peripheral security. + // Bit 4 = lock Core0. Once set, only a full chip reset can change ACCESSCTRL. + unsafe { + core::ptr::write_volatile(0x4006_0000 as *mut u32, 0xACCE_0010); + } + + // 9. Retarget NS-needed interrupts to Non-Secure via NVIC_ITNS + unsafe { retarget_interrupts_to_ns() }; + + // 10. Allow NS access to FPU coprocessor (NSACR) + unsafe { + let nsacr = core::ptr::read_volatile(0xE000_ED8C as *const u32); + core::ptr::write_volatile(0xE000_ED8C as *mut u32, nsacr | (3 << 10)); + } + + // 11. Disable FPU automatic state preservation for TrustZone transitions + unsafe { + core::ptr::write_volatile(0xE000_EF34 as *mut u32, 0x0000_0000); // FPCCR_S + core::ptr::write_volatile(0xE002_EF34 as *mut u32, 0x0000_0000); // FPCCR_NS + } + + // 12. Set NS VTOR and MSP from NS vector table + unsafe { + core::ptr::write_volatile(0xE002_ED08 as *mut u32, NS_FLASH_BASE); + let ns_sp = core::ptr::read_volatile(NS_FLASH_BASE as *const u32); + cortex_m::register::msp::write_ns(ns_sp); + } + + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + + info!("TrustZone configured — BLXNS to Non-Secure world"); + + // 8. Read NS Reset handler and branch via BLXNS + unsafe { + let ns_reset = core::ptr::read_volatile((NS_FLASH_BASE + 4) as *const u32); + // BLXNS: bit 0 must be 0 for NS target on Cortex-M33. + // The CPU stays in Thumb mode (Cortex-M only does Thumb). + // Bit 0 = 0 signals "branch to Non-Secure state". + let ns_target = ns_reset & !1u32; + core::arch::asm!( + "blxns {entry}", + entry = in(reg) ns_target, + options(noreturn), + ); + } +} + +/// Initialize NS world .bss (zero) and .data (copy from flash). +/// The NS world has no cortex-m-rt, so we do this from the Secure side. +/// +/// Addresses are hardcoded from NS ELF build output (arm-none-eabi-objdump -h). +/// TODO: auto-generate from NS build artifacts. +unsafe fn init_ns_memory() { + // NS .data: VMA=0x20000000, LMA=from flash, size from ELF + // NS .bss: VMA=after .data, size from ELF + // These will be populated after first successful NS build. + // For now, read the actual values from the NS ELF after building. + + // From NS ELF: arm-none-eabi-objdump -h hwsigner/target/.../hwsigner + let ns_sbss: *mut u8 = 0x2000_0438 as *mut u8; + let ns_bss_len: usize = 0x0002_07C4; + let ns_sdata: *mut u8 = 0x2000_0000 as *mut u8; + let ns_sidata: *const u8 = 0x1008_5FB4 as *const u8; + let ns_data_len: usize = 0x38; + + // Zero .bss + core::ptr::write_bytes(ns_sbss, 0, ns_bss_len); + // Copy .data + core::ptr::copy_nonoverlapping(ns_sidata, ns_sdata, ns_data_len); + + info!("NS .bss zeroed, .data copied"); +} + +/// Configure DMA internal security — allow NS access to all channels and IRQs. +/// +/// The DMA peripheral has its own per-channel and per-IRQ security registers +/// (SECCFG_CH, SECCFG_IRQ, SECCFG_MISC) that are separate from ACCESSCTRL. +/// Without this, NS code cannot write DMA interrupt enable registers. +unsafe fn configure_dma_security() { + const DMA_BASE: u32 = 0x5000_0000; + + // SECCFG_CH[0..15] at offset 0x480, stride 4 + // Set all channels to NS (bit 1 = S flag, clear it; bit 0 = P flag) + for i in 0..16u32 { + core::ptr::write_volatile((DMA_BASE + 0x480 + i * 4) as *mut u32, 0x0); + } + + // SECCFG_IRQ[0..3] at offset 0x4C0, stride 4 + // Set all DMA IRQs to NS accessible + for i in 0..4u32 { + core::ptr::write_volatile((DMA_BASE + 0x4C0 + i * 4) as *mut u32, 0x0); + } + + // SECCFG_MISC at offset 0x4D0 + // Set sniff, timer, multi-channel trigger to NS + core::ptr::write_volatile((DMA_BASE + 0x4D0) as *mut u32, 0x0); +} + +/// Configure SAU regions. +/// +/// NS regions: main firmware flash/RAM, peripherals, SIO, boot ROM. +/// Everything else (crypto library, key flash, crypto RAM) stays Secure. +unsafe fn configure_sau() { + let sau = &*cortex_m::peripheral::SAU::PTR; + + // Region 0: NS firmware flash (0x10080000 - 0x103FEFFF) → NS + sau.rnr.write(Rnr(0)); + sau.rbar.write(Rbar(0x1008_0000)); + sau.rlar.write(Rlar(0x103F_EFE0 | 1)); + + // Region 1: NS firmware RAM (0x20000000 - 0x2005FFFF) → NS + sau.rnr.write(Rnr(1)); + sau.rbar.write(Rbar(0x2000_0000)); + sau.rlar.write(Rlar(0x2005_FFE0 | 1)); + + // Region 2: SG veneers — crypto NSC entry points at 0x1002ABC0 + // Address from: arm-none-eabi-nm target/veneers.o + // NOTE: Must update if Secure binary layout changes + sau.rnr.write(Rnr(2)); + sau.rbar.write(Rbar(0x1002_AB00)); + sau.rlar.write(Rlar(0x1002_ABE0 | 3)); // Enable + NSC + + // Region 3: Peripherals + USB DPRAM (0x40000000 - 0x50FFFFFF) → NS + sau.rnr.write(Rnr(3)); + sau.rbar.write(Rbar(0x4000_0000)); + sau.rlar.write(Rlar(0x50FF_FFE0 | 1)); + + // Region 4: SIO (0xD0000000 - 0xD0020FFF) → NS + sau.rnr.write(Rnr(4)); + sau.rbar.write(Rbar(0xD000_0000)); + sau.rlar.write(Rlar(0xD002_0FE0 | 1)); + + // Region 5: Boot ROM (0x00000000 - 0x00007DFF) → NS + // Embassy calls ROM functions for clock queries. + // ROM NSC gateway (region 7 at 0x7E00+) preserved from boot ROM. + sau.rnr.write(Rnr(5)); + sau.rbar.write(Rbar(0x0000_0000)); + sau.rlar.write(Rlar(0x0000_7DE0 | 1)); + + // NOT listed → Secure by default: + // Secure flash (0x10000000 - 0x1007FFFF) — boot image + crypto + // Key flash (0x103FF000 - 0x103FFFFF) — key storage + // Secure RAM (0x20060000 - 0x2007FFFF) — crypto state + heap + + // Enable SAU + let ctrl = sau.ctrl.read(); + sau.ctrl.write(Ctrl(ctrl.0 | 1)); +} + +/// Configure ACCESSCTRL — grant NS access to USB/clocks/timers, keep TRNG Secure. +unsafe fn configure_accessctrl() { + #[inline(always)] + unsafe fn grant_ns(offset: u32) { + // ACCESSCTRL registers require password 0xACCE0000 in the upper 16 bits + // Lower 8 bits are the access control value + // 0xFF = all masters, all security levels (S + NS + DMA + debug) + core::ptr::write_volatile( + (0x4006_0000 + offset) as *mut u32, + 0xACCE_00FF, + ); + } + + grant_ns(0x0C0); // CLOCKS + grant_ns(0x0C4); // XOSC + grant_ns(0x0C8); // ROSC + grant_ns(0x0CC); // PLL_SYS + grant_ns(0x0D0); // PLL_USB + grant_ns(0x064); // RESETS + grant_ns(0x048); // USBCTRL + grant_ns(0x044); // DMA + grant_ns(0x068); // IO_BANK0 + grant_ns(0x06C); // IO_BANK1 + grant_ns(0x070); // PADS_BANK0 + grant_ns(0x098); // TIMER0 + grant_ns(0x09C); // TIMER1 + grant_ns(0x0D4); // TICKS + grant_ns(0x0D8); // WATCHDOG + grant_ns(0x060); // SYSINFO + grant_ns(0x078); // BUSCTRL + grant_ns(0x0BC); // SYSCFG + grant_ns(0x0AC); // TBMAN + grant_ns(0x0B0); // POWMAN + + // Keep Secure: TRNG (0x0B4), OTP (0x0A8), SHA256 (0x0B8), XIP_CTRL/QMI +} + +/// Retarget TIMER0_IRQ_0 and USBCTRL_IRQ to Non-Secure. +unsafe fn retarget_interrupts_to_ns() { + const NVIC_ITNS0: *mut u32 = 0xE000_E380 as *mut u32; + const NVIC_ICER0: *mut u32 = 0xE000_E180 as *mut u32; + const NVIC_ICPR0: *mut u32 = 0xE000_E280 as *mut u32; + const NVIC_ISER0: *mut u32 = 0xE000_E100 as *mut u32; + + // TIMER0_IRQ_0 = bit 0, USBCTRL_IRQ = bit 14 + // DMA_IRQ_0 = bit 10, IO_IRQ_BANK0 = bit 21 + let irq_bits: u32 = (1 << 0) | (1 << 10) | (1 << 14) | (1 << 21); + + core::ptr::write_volatile(NVIC_ICER0, irq_bits); + core::ptr::write_volatile(NVIC_ICPR0, irq_bits); + + let itns = core::ptr::read_volatile(NVIC_ITNS0); + core::ptr::write_volatile(NVIC_ITNS0, itns | irq_bits); + + core::ptr::write_volatile(NVIC_ISER0, irq_bits); +} + +/// Program metadata for picotool. +#[unsafe(link_section = ".bi_entries")] +#[used] +pub static PICOTOOL_ENTRIES: [hal::binary_info::EntryAddr; 3] = [ + hal::binary_info::rp_cargo_bin_name!(), + hal::binary_info::rp_cargo_version!(), + hal::binary_info::rp_program_description!(c"HW Signer Secure World"), +]; diff --git a/hwsigner-secure/src/nsc.rs b/hwsigner-secure/src/nsc.rs new file mode 100644 index 0000000..0de543e --- /dev/null +++ b/hwsigner-secure/src/nsc.rs @@ -0,0 +1,168 @@ +//! Non-Secure Callable (NSC) gateway functions. +//! +//! These are the only entry points from the Non-Secure world into Secure. +//! The NS world writes JSON request bytes into `NSC_IN_BUF`, calls +//! `nsc_process()`, and reads JSON response bytes from `NSC_OUT_BUF`. + +extern crate alloc; + +use core::sync::atomic::{AtomicBool, Ordering}; +use defmt::*; +use threshold::keys::{KeyPackage, PublicKeyPackage}; +use zeroize::Zeroize; + +use crate::handler::SignerState; +use crate::rng::SecureRng; +use crate::storage; + +const BUF_SIZE: usize = 4096; + +static INITIALIZED: AtomicBool = AtomicBool::new(false); + +// Secure-side input buffer — NS data is copied here for processing. +// Output goes directly to the NS buffer pointer (no Secure output buffer needed). +static mut NSC_IN_BUF: [u8; BUF_SIZE] = [0u8; BUF_SIZE]; + +// SignerState and KeyStorage live in Secure static memory. +static mut SIGNER_STATE: Option = None; +static mut KEY_STORAGE: Option = None; + +/// Initialize signer state from loaded key material. +/// Called by Secure main.rs after allocator + TRNG are already initialized. +pub fn init_signer_state( + keys: Option<(threshold::keys::KeyPackage, threshold::keys::PublicKeyPackage, Option<[u8; 32]>)>, +) { + unsafe { + let mut state = SignerState::new(SecureRng); + if let Some((kp, pkp, secret)) = keys { + info!("Restored key material from Secure flash"); + state.restore_keys(kp, pkp, secret); + } else { + info!("No key material — awaiting DKG"); + } + SIGNER_STATE = Some(state); + KEY_STORAGE = Some(storage::KeyStorage::new()); + INITIALIZED.store(true, Ordering::SeqCst); + } +} + +/// NSC entry point: Initialize the Secure crypto library. +/// Can be called from NS world through SG veneer. +/// Returns 0 on success, negative on error. +#[no_mangle] +#[inline(never)] +pub extern "cmse-nonsecure-entry" fn nsc_init() -> i32 { + if INITIALIZED.load(Ordering::SeqCst) { + return 0; // Already initialized + } + + crate::allocator::init(); + crate::rng::init_trng(); + let keys = storage::load(); + + unsafe { + let mut state = SignerState::new(SecureRng); + if let Some((kp, pkp, secret)) = keys { + state.restore_keys(kp, pkp, secret); + } + SIGNER_STATE = Some(state); + KEY_STORAGE = Some(storage::KeyStorage::new()); + INITIALIZED.store(true, Ordering::SeqCst); + } + + 0 +} + +/// Main NSC entry point. +/// +/// NS world passes pointers to its own buffers (in NS RAM). +/// Secure world copies request in, processes it, copies response out. +/// +/// Args: +/// ns_in_ptr: pointer to NS input buffer (JSON request bytes) +/// in_len: length of input +/// ns_out_ptr: pointer to NS output buffer (JSON response will be written here) +/// out_cap: capacity of output buffer +/// +/// Returns the response length (>0) on success, or a negative error code. +#[no_mangle] +#[inline(never)] +pub extern "cmse-nonsecure-entry" fn nsc_process( + ns_in_ptr: *const u8, + in_len: u32, + ns_out_ptr: *mut u8, + out_cap: u32, +) -> i32 { + if !INITIALIZED.load(Ordering::SeqCst) { + return -1; + } + let in_len = in_len as usize; + let out_cap = out_cap as usize; + if in_len > BUF_SIZE || out_cap > BUF_SIZE { + return -2; + } + + unsafe { + // Copy request from NS buffer into Secure buffer + core::ptr::copy_nonoverlapping(ns_in_ptr, NSC_IN_BUF.as_mut_ptr(), in_len); + + let request_bytes = &NSC_IN_BUF[..in_len]; + + let state = SIGNER_STATE.as_mut().unwrap(); + let key_storage = KEY_STORAGE.as_mut().unwrap(); + + let response_bytes = process_message(request_bytes, state, key_storage); + let resp_len = response_bytes.len().min(out_cap); + + // Copy response to NS buffer + core::ptr::copy_nonoverlapping(response_bytes.as_ptr(), ns_out_ptr, resp_len); + + // Zero Secure input buffer (may contain DKG secrets) + NSC_IN_BUF[..in_len].zeroize(); + + resp_len as i32 + } +} + +/// Parse JSON request, dispatch to handler, persist keys if needed, return JSON response. +fn process_message( + msg: &[u8], + state: &mut SignerState, + key_storage: &mut storage::KeyStorage, +) -> alloc::vec::Vec { + use crate::protocol::{Request, Response}; + + let json_str = match core::str::from_utf8(msg) { + Ok(s) => s, + Err(_) => return br#"{"error":"invalid UTF-8"}"#.to_vec(), + }; + + let response = match serde_json::from_str::(json_str) { + Ok(req) => { + let is_dkg_round3 = matches!(&req, Request::DkgRound3 { .. }); + let resp = state.handle(req); + + // Persist keys after successful DKG round 3 + if is_dkg_round3 { + if let (Some(kp), Some(pkp)) = (state.key_package(), state.public_key_package()) { + if let Err(()) = key_storage.save(kp, pkp, state.dkg_secret()) { + warn!("Failed to persist key material to Secure flash"); + } + } + } + + resp + } + Err(e) => { + warn!("Invalid request: {}", defmt::Debug2Format(&e)); + Response::Error { + error: alloc::format!("invalid request: {}", e), + } + } + }; + + match serde_json::to_vec(&response) { + Ok(bytes) => bytes, + Err(_) => br#"{"error":"serialization failed"}"#.to_vec(), + } +} diff --git a/pico-signer/src/protocol.rs b/hwsigner-secure/src/protocol.rs similarity index 100% rename from pico-signer/src/protocol.rs rename to hwsigner-secure/src/protocol.rs diff --git a/hwsigner-secure/src/rng.rs b/hwsigner-secure/src/rng.rs new file mode 100644 index 0000000..95a5980 --- /dev/null +++ b/hwsigner-secure/src/rng.rs @@ -0,0 +1,79 @@ +//! Hardware TRNG wrapper using raw register access. +//! +//! Uses the RP2350's true random number generator peripheral directly, +//! without the Embassy HAL or PAC. Runs in Secure world only. + +use rand_core::{CryptoRng, RngCore}; + +// RP2350 TRNG register addresses (base: 0x400D0000) +const TRNG_BASE: u32 = 0x400D_0000; +const RNG_IMR: u32 = TRNG_BASE + 0x100; +const TRNG_CONFIG: u32 = TRNG_BASE + 0x104; +const RND_SOURCE_ENABLE: u32 = TRNG_BASE + 0x110; +const TRNG_VALID: u32 = TRNG_BASE + 0x114; +const EHR_DATA0: u32 = TRNG_BASE + 0x118; + +/// Initialize the TRNG peripheral. +pub fn init_trng() { + unsafe { + core::ptr::write_volatile(RNG_IMR as *mut u32, 0); // Mask interrupts (we poll) + core::ptr::write_volatile(TRNG_CONFIG as *mut u32, 0); // Default config + core::ptr::write_volatile(RND_SOURCE_ENABLE as *mut u32, 1); // Enable entropy source + } +} + +/// Secure world RNG backed by the RP2350 hardware TRNG. +pub struct SecureRng; + +impl RngCore for SecureRng { + fn next_u32(&mut self) -> u32 { + let mut buf = [0u8; 4]; + self.fill_bytes(&mut buf); + u32::from_le_bytes(buf) + } + + fn next_u64(&mut self) -> u64 { + let mut buf = [0u8; 8]; + self.fill_bytes(&mut buf); + u64::from_le_bytes(buf) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + for chunk in dest.chunks_mut(24) { + // Wait for valid random data + unsafe { + while core::ptr::read_volatile(TRNG_VALID as *const u32) == 0 { + cortex_m::asm::nop(); + } + } + + // Read from EHR (Entropy Holding Register) data registers. + // 6 x 32-bit registers = 192 bits (24 bytes) per sample. + let mut ehr_data = [0u32; 6]; + for (i, word) in ehr_data.iter_mut().enumerate() { + *word = unsafe { + core::ptr::read_volatile((EHR_DATA0 + i as u32 * 4) as *const u32) + }; + } + + let mut offset = 0; + for word in &ehr_data { + let bytes = word.to_le_bytes(); + let remaining = chunk.len() - offset; + let to_copy = remaining.min(4); + chunk[offset..offset + to_copy].copy_from_slice(&bytes[..to_copy]); + offset += to_copy; + if offset >= chunk.len() { + break; + } + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +impl CryptoRng for SecureRng {} diff --git a/hwsigner-secure/src/storage.rs b/hwsigner-secure/src/storage.rs new file mode 100644 index 0000000..f6a14e0 --- /dev/null +++ b/hwsigner-secure/src/storage.rs @@ -0,0 +1,252 @@ +//! Secure flash persistence for key material. +//! +//! Uses raw XIP memory-mapped reads and RP2350 boot ROM flash functions +//! for erase/write. The last 4KB flash sector at 0x103FF000 is SAU-protected +//! and only accessible from the Secure world. +//! +//! Layout v2 (same as original hwsigner): +//! [magic:1=0xA5][version:1=0x02] +//! [secret_len:2 BE][DKG secret bytes (32)] +//! [kp_len:2 BE][KeyPackage JSON bytes] +//! [pkp_len:2 BE][PublicKeyPackage JSON bytes] +//! [crc32:4 BE] (over all preceding bytes) + +extern crate alloc; + +use defmt::*; +use threshold::keys::{KeyPackage, PublicKeyPackage}; +use zeroize::Zeroize; + +/// Flash offset of the key storage sector (last 4KB of 4MB flash). +const STORAGE_OFFSET: u32 = 0x3FF000; +/// Sector size for erase operations. +const SECTOR_SIZE: usize = 4096; +/// XIP base address — flash is memory-mapped here. +const XIP_BASE: u32 = 0x1000_0000; + +const MAGIC: u8 = 0xA5; +const VERSION: u8 = 0x02; + +pub struct KeyStorage; + +impl KeyStorage { + pub fn new() -> Self { + Self + } +} + +/// Load key material from Secure flash via XIP memory-mapped read. +/// Returns None if no valid data found. +pub fn load() -> Option<(KeyPackage, PublicKeyPackage, Option<[u8; 32]>)> { + // Read the flash sector via XIP (memory-mapped, read-only) + let flash_ptr = (XIP_BASE + STORAGE_OFFSET) as *const u8; + let mut buf = [0u8; SECTOR_SIZE]; + unsafe { + core::ptr::copy_nonoverlapping(flash_ptr, buf.as_mut_ptr(), SECTOR_SIZE); + } + + if buf[0] != MAGIC { + info!("No key material in Secure flash (magic mismatch)"); + buf.zeroize(); + return None; + } + + if buf[1] != VERSION { + info!("No key material in Secure flash (version mismatch)"); + buf.zeroize(); + return None; + } + + let result = load_v2(&buf); + buf.zeroize(); + result +} + +fn load_v2(buf: &[u8; SECTOR_SIZE]) -> Option<(KeyPackage, PublicKeyPackage, Option<[u8; 32]>)> { + // Parse DKG secret + let secret_len = u16::from_be_bytes([buf[2], buf[3]]) as usize; + if secret_len != 32 { + warn!("Invalid secret_len in flash: {}", secret_len); + return None; + } + let mut dkg_secret = [0u8; 32]; + dkg_secret.copy_from_slice(&buf[4..36]); + + // Parse KeyPackage + let kp_offset = 36; + let kp_len = u16::from_be_bytes([buf[kp_offset], buf[kp_offset + 1]]) as usize; + let kp_start = kp_offset + 2; + if kp_start + kp_len + 2 > SECTOR_SIZE - 4 { + warn!("Invalid kp_len in flash"); + return None; + } + let kp_json = core::str::from_utf8(&buf[kp_start..kp_start + kp_len]).ok()?; + + // Parse PublicKeyPackage + let pkp_offset = kp_start + kp_len; + let pkp_len = u16::from_be_bytes([buf[pkp_offset], buf[pkp_offset + 1]]) as usize; + let pkp_start = pkp_offset + 2; + if pkp_start + pkp_len > SECTOR_SIZE - 4 { + warn!("Invalid pkp_len in flash"); + return None; + } + let pkp_json = core::str::from_utf8(&buf[pkp_start..pkp_start + pkp_len]).ok()?; + + // Verify CRC32 + let crc_offset = pkp_start + pkp_len; + let stored_crc = u32::from_be_bytes([ + buf[crc_offset], + buf[crc_offset + 1], + buf[crc_offset + 2], + buf[crc_offset + 3], + ]); + let computed_crc = crc32(&buf[..crc_offset]); + if stored_crc != computed_crc { + warn!("Flash CRC mismatch"); + return None; + } + + let kp = KeyPackage::from_json(kp_json).ok()?; + let pkp = PublicKeyPackage::from_json(pkp_json).ok()?; + + info!("Loaded key material from Secure flash"); + Some((kp, pkp, Some(dkg_secret))) +} + +impl KeyStorage { + /// Save key material to Secure flash. Erases the sector first. + pub fn save( + &mut self, + kp: &KeyPackage, + pkp: &PublicKeyPackage, + dkg_secret: Option<&[u8; 32]>, + ) -> Result<(), ()> { + let kp_json = kp.to_json(); + let pkp_json = pkp.to_json(); + + let kp_bytes = kp_json.as_bytes(); + let pkp_bytes = pkp_json.as_bytes(); + + let secret_bytes: &[u8; 32] = match dkg_secret { + Some(s) => s, + None => { + error!("DKG secret required for v2 save"); + return Err(()); + } + }; + + // Build buffer + let total = 2 + 2 + 32 + 2 + kp_bytes.len() + 2 + pkp_bytes.len() + 4; + if total > SECTOR_SIZE { + error!("Key material too large for flash sector"); + return Err(()); + } + + let mut buf = [0xFFu8; SECTOR_SIZE]; + buf[0] = MAGIC; + buf[1] = VERSION; + + // DKG secret + buf[2..4].copy_from_slice(&32u16.to_be_bytes()); + buf[4..36].copy_from_slice(secret_bytes); + + // KeyPackage + let kp_offset = 36; + buf[kp_offset..kp_offset + 2].copy_from_slice(&(kp_bytes.len() as u16).to_be_bytes()); + let kp_start = kp_offset + 2; + buf[kp_start..kp_start + kp_bytes.len()].copy_from_slice(kp_bytes); + + // PublicKeyPackage + let pkp_offset = kp_start + kp_bytes.len(); + buf[pkp_offset..pkp_offset + 2].copy_from_slice(&(pkp_bytes.len() as u16).to_be_bytes()); + let pkp_start = pkp_offset + 2; + buf[pkp_start..pkp_start + pkp_bytes.len()].copy_from_slice(pkp_bytes); + + // CRC32 + let crc_offset = pkp_start + pkp_bytes.len(); + let crc = crc32(&buf[..crc_offset]); + buf[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_be_bytes()); + + // Erase and write via RP2350 boot ROM functions + flash_erase(STORAGE_OFFSET, SECTOR_SIZE as u32)?; + flash_write(STORAGE_OFFSET, &buf)?; + + info!("Saved key material to Secure flash"); + Ok(()) + } +} + +/// Erase flash using RP2350 boot ROM `flash_range_erase`. +fn flash_erase(offset: u32, len: u32) -> Result<(), ()> { + // The RP2350 boot ROM exposes flash_range_erase and flash_range_program + // functions. We call them through the ROM function table. + // + // Safety: Must be called with interrupts disabled and from Secure world. + // The flash controller is not re-entrant. + cortex_m::interrupt::free(|_| unsafe { + // ROM function table lookup for flash_range_erase + // ROM table pointer is at 0x00000014 (RP2350 ROM table) + let rom_table: *const u16 = *(0x0000_0014 as *const *const u16); + let flash_erase_fn = rom_func_lookup(rom_table, b'R', b'E'); + if flash_erase_fn == 0 { + error!("Flash erase ROM function not found"); + return Err(()); + } + let erase: extern "C" fn(u32, usize, u32, u8) = + core::mem::transmute(flash_erase_fn as *const ()); + erase(offset, len as usize, 1 << 16, 0xD8); // 64KB block erase command + Ok(()) + }) +} + +/// Write flash using RP2350 boot ROM `flash_range_program`. +fn flash_write(offset: u32, data: &[u8]) -> Result<(), ()> { + cortex_m::interrupt::free(|_| unsafe { + let rom_table: *const u16 = *(0x0000_0014 as *const *const u16); + let flash_program_fn = rom_func_lookup(rom_table, b'R', b'P'); + if flash_program_fn == 0 { + error!("Flash program ROM function not found"); + return Err(()); + } + let program: extern "C" fn(u32, *const u8, usize) = + core::mem::transmute(flash_program_fn as *const ()); + program(offset, data.as_ptr(), data.len()); + Ok(()) + }) +} + +/// Look up a ROM function by its two-character code. +unsafe fn rom_func_lookup(table: *const u16, c1: u8, c2: u8) -> u32 { + let code = (c2 as u16) << 8 | c1 as u16; + let mut ptr = table; + loop { + let entry = *ptr; + if entry == 0 { + return 0; + } + let fn_code = *ptr.add(1); + if fn_code == code { + // The function pointer is stored as a 16-bit offset in older RP chips, + // but RP2350 uses the Bootrom API. This is a simplified placeholder — + // the actual lookup uses the rp2350 ROM table format. + return *ptr as u32; + } + ptr = ptr.add(2); + } +} + +/// Simple CRC32 (no lookup table to save flash space). +fn crc32(data: &[u8]) -> u32 { + let mut crc: u32 = 0xFFFF_FFFF; + for &byte in data { + crc ^= byte as u32; + for _ in 0..8 { + if crc & 1 != 0 { + crc = (crc >> 1) ^ 0xEDB8_8320; + } else { + crc >>= 1; + } + } + } + !crc +} diff --git a/pico-signer/.cargo/config.toml b/hwsigner/.cargo/config.toml similarity index 69% rename from pico-signer/.cargo/config.toml rename to hwsigner/.cargo/config.toml index 0486605..f0b5c44 100644 --- a/pico-signer/.cargo/config.toml +++ b/hwsigner/.cargo/config.toml @@ -4,8 +4,10 @@ rustflags = [ "-C", "link-arg=-Tlink.x", "-C", "link-arg=-Tdefmt.x", "-C", "target-cpu=cortex-m33", + + # Link CMSE veneer stubs from Secure world build + "-C", "link-arg=../hwsigner-secure/target/veneers.o", ] -runner = "probe-rs run --chip RP2350" [build] target = "thumbv8m.main-none-eabihf" diff --git a/pico-signer/.gitignore b/hwsigner/.gitignore similarity index 100% rename from pico-signer/.gitignore rename to hwsigner/.gitignore diff --git a/hwsigner/Cargo.toml b/hwsigner/Cargo.toml new file mode 100644 index 0000000..77cb018 --- /dev/null +++ b/hwsigner/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "hwsigner" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Embassy framework — using local fork with init_ns() for TrustZone NS support +embassy-executor = { version = "0.9", features = ["arch-cortex-m", "executor-thread", "defmt"] } +embassy-rp = { path = "../embassy-rp-fork", features = ["defmt", "rp235xa", "time-driver", "trustzone-ns"] } +embassy-time = { version = "0.5", features = ["defmt"] } +embassy-usb = { version = "0.5", features = ["defmt"] } +embassy-futures = "0.1" +embassy-sync = { version = "0.7", features = ["defmt"] } + +# Cortex-M support +cortex-m = { version = "0.7", features = ["critical-section-single-core"] } +# cortex-m-rt for vector table + interrupt dispatch (Secure world handles actual boot) +cortex-m-rt = "0.7" + +# Allocator +embedded-alloc = "0.7" + +# Logging +defmt = "0.3" +defmt-rtt = "0.4" + +# Panic handler +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +opt-level = "s" +lto = true +debug = 2 +codegen-units = 1 diff --git a/pico-signer/build.rs b/hwsigner/build.rs similarity index 84% rename from pico-signer/build.rs rename to hwsigner/build.rs index 2d467b4..33e2168 100644 --- a/pico-signer/build.rs +++ b/hwsigner/build.rs @@ -3,10 +3,10 @@ use std::io::Write; use std::path::PathBuf; fn main() { - // Copy rp2350.x as memory.x into OUT_DIR so cortex-m-rt's link.x can find it let out = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); println!("cargo:rustc-link-search={}", out.display()); + // Copy rp2350.x as memory.x for cortex-m-rt's link.x let memory_x = include_bytes!("rp2350.x"); let mut f = File::create(out.join("memory.x")).unwrap(); f.write_all(memory_x).unwrap(); diff --git a/hwsigner/rp2350.x b/hwsigner/rp2350.x new file mode 100644 index 0000000..4d4d9e2 --- /dev/null +++ b/hwsigner/rp2350.x @@ -0,0 +1,21 @@ +/* +* RP2350 Non-Secure world memory layout. +* +* This provides memory.x for cortex-m-rt (vector table + interrupt dispatch). +* The Secure world initializes .bss/.data and calls main() via BXNS. +* cortex-m-rt's Reset handler is NOT used (Secure boots first). +* +* Flash: +* Secure (0x10000000, 512K): hwsigner-secure +* NS (0x10080000, 3584K): this image +* Keys (0x103FF000, 4K): SAU-protected +* +* RAM: +* NS (0x20000000, 384K): this image +* Secure (0x20060000, 128K): SAU-protected +*/ + +MEMORY { + FLASH : ORIGIN = 0x10080000, LENGTH = 3584K + RAM : ORIGIN = 0x20000000, LENGTH = 384K +} diff --git a/pico-signer/src/allocator.rs b/hwsigner/src/allocator.rs similarity index 100% rename from pico-signer/src/allocator.rs rename to hwsigner/src/allocator.rs diff --git a/pico-signer/src/chunking.rs b/hwsigner/src/chunking.rs similarity index 100% rename from pico-signer/src/chunking.rs rename to hwsigner/src/chunking.rs diff --git a/hwsigner/src/main.rs b/hwsigner/src/main.rs new file mode 100644 index 0000000..c138825 --- /dev/null +++ b/hwsigner/src/main.rs @@ -0,0 +1,158 @@ +//! Non-Secure world firmware for RP2350 TrustZone HW Signer. +//! +//! The Secure world boots first (rp235x-hal), initializes clocks/PLLs, +//! configures SAU/ACCESSCTRL, initializes the crypto library, then +//! BLXNS to this NS world's Reset handler. +//! +//! cortex-m-rt provides the vector table and .bss/.data init. +//! embassy_rp::init_ns() skips clock setup (already done by Secure world). + +#![no_std] +#![no_main] + +extern crate alloc; + +mod allocator; +mod chunking; +mod nsc_client; +mod usb_hid; + +use defmt::*; +use defmt_rtt as _; +use panic_probe as _; + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::InterruptHandler; +use embassy_rp::NsClockConfig; +use embassy_usb::class::hid::{HidReaderWriter, State as HidState}; +use embassy_usb::Builder; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[cortex_m_rt::entry] +fn main() -> ! { + // DEBUG BISECT: uncomment one line at a time to find crash point + // CHECKPOINT 1: NS main reached + // loop { cortex_m::asm::nop(); } + + // 1. Initialize NS heap + allocator::init(); + + // CHECKPOINT 2: allocator done + // loop { cortex_m::asm::nop(); } + + // 2. Initialize Embassy (clocks already configured by Secure world) + let p = unsafe { + embassy_rp::init_ns(NsClockConfig::rp2350_default()) + }; + + info!("NS world running — Embassy initialized via init_ns()"); + + // 3. Create and run Embassy executor + let mut executor = embassy_executor::Executor::new(); + let executor = unsafe { + core::mem::transmute::<&mut embassy_executor::Executor, &'static mut embassy_executor::Executor>(&mut executor) + }; + executor.run(|spawner| { + spawner.must_spawn(run_usb(spawner, p)); + }) +} + +/// Main USB HID task. +#[embassy_executor::task] +async fn run_usb(_spawner: Spawner, p: embassy_rp::Peripherals) { + let driver = embassy_rp::usb::Driver::new(p.USB, Irqs); + + let mut config = embassy_usb::Config::new(usb_hid::VID, usb_hid::PID); + config.manufacturer = Some("MPCWallet"); + config.product = Some("HW Signer"); + config.serial_number = Some("001"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut hid_state = HidState::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + let hid_config = embassy_usb::class::hid::Config { + report_descriptor: usb_hid::REPORT_DESCRIPTOR, + request_handler: None, + poll_ms: 5, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 64, 64>::new(&mut builder, &mut hid_state, hid_config); + + let mut usb = builder.build(); + let (reader, writer) = hid.split(); + + let usb_fut = usb.run(); + let hid_fut = run_hid_loop(reader, writer); + + info!("HW Signer ready — USB HID active, crypto via Secure NSC"); + embassy_futures::join::join(usb_fut, hid_fut).await; +} + +/// HID request-response loop. +async fn run_hid_loop<'d, D: embassy_usb::driver::Driver<'d>>( + mut reader: embassy_usb::class::hid::HidReader<'d, D, 64>, + mut writer: embassy_usb::class::hid::HidWriter<'d, D, 64>, +) { + let mut reassembler = chunking::Reassembler::new(); + let mut report_buf = [0u8; 64]; + + loop { + match reader.read(&mut report_buf).await { + Ok(_) => { + let report: &[u8; 64] = &report_buf; + + match reassembler.feed(report) { + Ok(Some(message)) => { + let response_bytes = match nsc_client::call_secure(&message) { + Ok(resp) => resp, + Err(code) => { + warn!("NSC call failed: {}", code); + b"{\"error\":\"secure world error\"}" + } + }; + + let reports = chunking::chunk_message(response_bytes); + for report in &reports { + if let Err(e) = writer.write(report).await { + error!("HID write error: {:?}", e); + break; + } + } + } + Ok(None) => {} + Err(e) => { + warn!("Reassembly error: {}", e); + reassembler.reset(); + let err_json = br#"{"error":"protocol framing error"}"#; + let reports = chunking::chunk_message(err_json); + for report in &reports { + let _ = writer.write(report).await; + } + } + } + } + Err(e) => { + error!("HID read error: {:?}", e); + embassy_time::Timer::after_millis(100).await; + } + } + } +} diff --git a/hwsigner/src/nsc_client.rs b/hwsigner/src/nsc_client.rs new file mode 100644 index 0000000..2548a2a --- /dev/null +++ b/hwsigner/src/nsc_client.rs @@ -0,0 +1,44 @@ +//! Non-Secure world client for calling into the Secure world via NSC veneers. + +extern "C" { + fn nsc_init() -> i32; + fn nsc_process(ns_in_ptr: *const u8, in_len: u32, ns_out_ptr: *mut u8, out_cap: u32) -> i32; +} + +/// Initialize the Secure crypto library. +pub fn init() -> Result<(), i32> { + let result = unsafe { nsc_init() }; + if result < 0 { Err(result) } else { Ok(()) } +} + +const BUF_SIZE: usize = 4096; + +/// NS-side buffers (in NS RAM — accessible from both NS and Secure) +static mut NS_IN_BUF: [u8; BUF_SIZE] = [0u8; BUF_SIZE]; +static mut NS_OUT_BUF: [u8; BUF_SIZE] = [0u8; BUF_SIZE]; + +/// Send a JSON request to the Secure world, get JSON response back. +pub fn call_secure(request_json: &[u8]) -> Result<&'static [u8], i32> { + if request_json.len() > BUF_SIZE { + return Err(-2); + } + + unsafe { + // Copy request into NS buffer + NS_IN_BUF[..request_json.len()].copy_from_slice(request_json); + + // Call Secure world — it reads from NS_IN_BUF, writes to NS_OUT_BUF + let result = nsc_process( + NS_IN_BUF.as_ptr(), + request_json.len() as u32, + NS_OUT_BUF.as_mut_ptr(), + BUF_SIZE as u32, + ); + + if result < 0 { + return Err(result); + } + + Ok(&NS_OUT_BUF[..result as usize]) + } +} diff --git a/pico-signer/src/usb_hid.rs b/hwsigner/src/usb_hid.rs similarity index 100% rename from pico-signer/src/usb_hid.rs rename to hwsigner/src/usb_hid.rs diff --git a/pico-signer/rp2350.x b/pico-signer/rp2350.x deleted file mode 100644 index 696d23f..0000000 --- a/pico-signer/rp2350.x +++ /dev/null @@ -1,69 +0,0 @@ -/* -* SPDX-License-Identifier: MIT OR Apache-2.0 -* -* Copyright (c) 2021-2024 The rp-rs Developers -* Copyright (c) 2021 rp-rs organization -* Copyright (c) 2025 Raspberry Pi Ltd. -* -* RP2350 linker script for Pico Signer firmware. -* 4MB flash, last 4KB reserved for key storage. -*/ - -MEMORY { - FLASH : ORIGIN = 0x10000000, LENGTH = 4096K - 4K - RAM : ORIGIN = 0x20000000, LENGTH = 512K - SRAM4 : ORIGIN = 0x20080000, LENGTH = 4K - SRAM5 : ORIGIN = 0x20081000, LENGTH = 4K -} - -SECTIONS { - /* ### Boot ROM info - * - * Goes after .vector_table, to keep it in the first 4K of flash - * where the Boot ROM (and picotool) can find it - */ - .start_block : ALIGN(4) - { - __start_block_addr = .; - KEEP(*(.start_block)); - } > FLASH - -} INSERT AFTER .vector_table; - -/* move .text to start /after/ the boot info */ -_stext = ADDR(.start_block) + SIZEOF(.start_block); - -SECTIONS { - /* ### Picotool 'Binary Info' Entries - * - * Picotool looks through this block (as we have pointers to it in our - * header) to find interesting information. - */ - .bi_entries : ALIGN(4) - { - /* We put this in the header */ - __bi_entries_start = .; - /* Here are the entries */ - KEEP(*(.bi_entries)); - /* Keep this block a nice round size */ - . = ALIGN(4); - /* We put this in the header */ - __bi_entries_end = .; - } > FLASH -} INSERT AFTER .text; - -SECTIONS { - /* ### Boot ROM extra info - * - * Goes after everything in our program, so it can contain a signature. - */ - .end_block : ALIGN(4) - { - __end_block_addr = .; - KEEP(*(.end_block)); - } > FLASH - -} INSERT AFTER .uninit; - -PROVIDE(start_to_end = __end_block_addr - __start_block_addr); -PROVIDE(end_to_start = __start_block_addr - __end_block_addr); diff --git a/pico-signer/src/handler.rs b/pico-signer/src/handler.rs deleted file mode 100644 index 76d934c..0000000 --- a/pico-signer/src/handler.rs +++ /dev/null @@ -1,520 +0,0 @@ -//! Signer state and command handlers. -//! -//! Ported from signer-server/src/handler.rs with: -//! - HashMap -> BTreeMap (no_std) -//! - OsRng -> PicoRng (RP2350 TRNG) -//! - std::format! -> alloc::format! - -extern crate alloc; -use alloc::collections::BTreeMap; -use alloc::format; -use alloc::string::String; -use alloc::vec::Vec; - -use k256::Scalar; -use threshold::dkg::{self, Round1Package, Round1SecretPackage, Round2Package, Round2SecretPackage}; -use threshold::identifier::Identifier; -use threshold::keys::{KeyPackage, PublicKeyPackage}; -use threshold::nonce::{self, SigningNonce}; -use threshold::point; -use threshold::scalar::scalar_to_bytes; - -use crate::protocol::{Request, Response}; -use crate::rng::PicoRng; - -// --------------------------------------------------------------------------- -// Signer state -// --------------------------------------------------------------------------- - -pub struct SignerState { - rng: PicoRng, - r1_secret: Option, - r2_secret: Option, - key_package: Option, - public_key_package: Option, - pending_nonce: Option, - dkg_secret: Option<[u8; 32]>, -} - -impl SignerState { - pub fn new(rng: PicoRng) -> Self { - Self { - rng, - r1_secret: None, - r2_secret: None, - key_package: None, - public_key_package: None, - pending_nonce: None, - dkg_secret: None, - } - } - - /// Restore key material loaded from flash. - pub fn restore_keys(&mut self, kp: KeyPackage, pkp: PublicKeyPackage, dkg_secret: Option<[u8; 32]>) { - self.key_package = Some(kp); - self.public_key_package = Some(pkp); - self.dkg_secret = dkg_secret; - } - - pub fn key_package(&self) -> Option<&KeyPackage> { - self.key_package.as_ref() - } - - pub fn public_key_package(&self) -> Option<&PublicKeyPackage> { - self.public_key_package.as_ref() - } - - pub fn dkg_secret(&self) -> Option<&[u8; 32]> { - self.dkg_secret.as_ref() - } - - pub fn handle(&mut self, req: Request) -> Response { - match req { - Request::DkgInit { - max_signers, - min_signers, - } => self.handle_dkg_init(max_signers, min_signers), - Request::RestoreInit { - max_signers, - min_signers, - } => self.handle_restore_init(max_signers, min_signers), - Request::DkgRound2 { round1_packages, receiver_identifiers } => { - self.handle_dkg_round2(round1_packages, receiver_identifiers) - } - Request::DkgRound3 { - round1_packages, - round2_packages, - receiver_identifiers, - } => self.handle_dkg_round3(round1_packages, round2_packages, receiver_identifiers), - Request::GenerateNonce => self.handle_generate_nonce(), - Request::Sign { - message_hex, - commitments, - apply_tweak, - merkle_root_hex, - } => self.handle_sign(message_hex, commitments, apply_tweak, merkle_root_hex), - Request::GetInfo => self.handle_get_info(), - } - } - - fn handle_dkg_init(&mut self, max_signers: usize, min_signers: usize) -> Response { - let secret = random_scalar(&mut self.rng); - let mut coefficients = Vec::with_capacity(min_signers - 1); - for _ in 0..(min_signers - 1) { - coefficients.push(random_scalar(&mut self.rng)); - } - - // Store the DKG secret for potential future restore - self.dkg_secret = Some(scalar_to_bytes(&secret)); - - match dkg::dkg_part1(max_signers, min_signers, &secret, &coefficients, &mut self.rng) { - Ok((secret_pkg, pub_pkg)) => { - let id_hex = hex_encode(&secret_pkg.identifier.serialize()); - let vk_hex = hex_encode(&pub_pkg.verifying_key.serialize()); - let r1_json = pub_pkg.to_json_value(); - - self.r1_secret = Some(secret_pkg); - - Response::DkgInit { - round1_package_json: r1_json, - verifying_key_hex: vk_hex, - identifier_hex: id_hex, - } - } - Err(e) => Response::Error { - error: format!("dkg_init failed: {}", e), - }, - } - } - - fn handle_restore_init(&mut self, max_signers: usize, min_signers: usize) -> Response { - let secret_bytes = match &self.dkg_secret { - Some(s) => *s, - None => { - return Response::Error { - error: "no DKG secret stored; cannot restore (run initial DKG first)".into(), - } - } - }; - - // Reconstruct the same secret scalar from stored bytes - let secret = match scalar_from_bytes(&secret_bytes) { - Ok(s) => s, - Err(e) => return Response::Error { error: e }, - }; - - // Generate fresh random coefficients (different polynomial, same constant term) - let mut coefficients = Vec::with_capacity(min_signers - 1); - for _ in 0..(min_signers - 1) { - coefficients.push(random_scalar(&mut self.rng)); - } - - match dkg::dkg_part1(max_signers, min_signers, &secret, &coefficients, &mut self.rng) { - Ok((secret_pkg, pub_pkg)) => { - let id_hex = hex_encode(&secret_pkg.identifier.serialize()); - let vk_hex = hex_encode(&pub_pkg.verifying_key.serialize()); - let r1_json = pub_pkg.to_json_value(); - - self.r1_secret = Some(secret_pkg); - - Response::DkgInit { - round1_package_json: r1_json, - verifying_key_hex: vk_hex, - identifier_hex: id_hex, - } - } - Err(e) => Response::Error { - error: format!("restore_init failed: {}", e), - }, - } - } - - fn handle_dkg_round2( - &mut self, - round1_packages: BTreeMap, - receiver_identifier_hexes: Vec, - ) -> Response { - let r1_secret = match &self.r1_secret { - Some(s) => s, - None => { - return Response::Error { - error: "no round 1 secret package (call dkg_init first)".into(), - } - } - }; - - let mut r1_pkgs: BTreeMap = BTreeMap::new(); - for (id_hex, pkg_json) in &round1_packages { - let id = match parse_identifier(id_hex) { - Ok(id) => id, - Err(e) => return Response::Error { error: e }, - }; - let pkg = match Round1Package::from_json_value(pkg_json) { - Ok(p) => p, - Err(e) => { - return Response::Error { - error: format!("failed to parse Round1Package: {}", e), - } - } - }; - r1_pkgs.insert(id, pkg); - } - - let mut receiver_ids = Vec::new(); - for hex_str in &receiver_identifier_hexes { - match parse_identifier(hex_str) { - Ok(id) => receiver_ids.push(id), - Err(e) => return Response::Error { error: e }, - } - } - - match dkg::dkg_part2(r1_secret, &r1_pkgs, &receiver_ids) { - Ok((r2_secret, r2_out)) => { - let mut r2_map: BTreeMap = BTreeMap::new(); - for (id, pkg) in &r2_out { - let id_hex = hex_encode(&id.serialize()); - r2_map.insert(id_hex, pkg.to_json_value()); - } - - self.r2_secret = Some(r2_secret); - - Response::DkgRound2 { - round2_packages: r2_map, - } - } - Err(e) => Response::Error { - error: format!("dkg_round2 failed: {}", e), - }, - } - } - - fn handle_dkg_round3( - &mut self, - round1_packages: BTreeMap, - round2_packages: BTreeMap, - receiver_identifier_hexes: Vec, - ) -> Response { - let r1_secret = match &self.r1_secret { - Some(s) => s, - None => { - return Response::Error { - error: "no round 1 secret package".into(), - } - } - }; - let r2_secret = match &self.r2_secret { - Some(s) => s, - None => { - return Response::Error { - error: "no round 2 secret package (call dkg_round2 first)".into(), - } - } - }; - - let mut r1_pkgs: BTreeMap = BTreeMap::new(); - for (id_hex, pkg_json) in &round1_packages { - let id = match parse_identifier(id_hex) { - Ok(id) => id, - Err(e) => return Response::Error { error: e }, - }; - let pkg = match Round1Package::from_json_value(pkg_json) { - Ok(p) => p, - Err(e) => { - return Response::Error { - error: format!("failed to parse Round1Package: {}", e), - } - } - }; - r1_pkgs.insert(id, pkg); - } - - let mut r2_pkgs: BTreeMap = BTreeMap::new(); - for (id_hex, pkg_json) in &round2_packages { - let id = match parse_identifier(id_hex) { - Ok(id) => id, - Err(e) => return Response::Error { error: e }, - }; - let pkg = match Round2Package::from_json_value(pkg_json) { - Ok(p) => p, - Err(e) => { - return Response::Error { - error: format!("failed to parse Round2Package: {}", e), - } - } - }; - r2_pkgs.insert(id, pkg); - } - - let mut receiver_ids = Vec::new(); - for hex_str in &receiver_identifier_hexes { - match parse_identifier(hex_str) { - Ok(id) => receiver_ids.push(id), - Err(e) => return Response::Error { error: e }, - } - } - - match dkg::dkg_part3(r1_secret, r2_secret, &r1_pkgs, &r2_pkgs, &receiver_ids) { - Ok((kp, pkp)) => { - let id_hex = hex_encode(&kp.identifier.serialize()); - let pk_hex = hex_encode(&pkp.verifying_key.serialize()); - - self.key_package = Some(kp); - self.public_key_package = Some(pkp); - self.r1_secret = None; - self.r2_secret = None; - - Response::DkgRound3 { - ok: true, - identifier_hex: id_hex, - public_key_hex: pk_hex, - } - } - Err(e) => Response::Error { - error: format!("dkg_round3 failed: {}", e), - }, - } - } - - fn handle_generate_nonce(&mut self) -> Response { - let kp = match &self.key_package { - Some(kp) => kp, - None => { - return Response::Error { - error: "no key package (run DKG first)".into(), - } - } - }; - - let signing_nonce = nonce::new_nonce(&mut self.rng, &kp.secret_share); - - let hiding_hex = - hex_encode(&point::serialize_compressed(&signing_nonce.commitments.hiding)); - let binding_hex = - hex_encode(&point::serialize_compressed(&signing_nonce.commitments.binding)); - - self.pending_nonce = Some(signing_nonce); - - Response::GenerateNonce { - hiding_hex, - binding_hex, - } - } - - fn handle_sign( - &mut self, - message_hex: String, - commitments: BTreeMap, - apply_tweak: bool, - merkle_root_hex: Option, - ) -> Response { - let kp = match &self.key_package { - Some(kp) => kp.clone(), - None => { - return Response::Error { - error: "no key package (run DKG first)".into(), - } - } - }; - let pkp = match &self.public_key_package { - Some(pkp) => pkp.clone(), - None => { - return Response::Error { - error: "no public key package".into(), - } - } - }; - let signing_nonce = match self.pending_nonce.take() { - Some(n) => n, - None => { - return Response::Error { - error: "no pending nonce (call generate_nonce first)".into(), - } - } - }; - - let message = match hex_decode(&message_hex) { - Ok(m) => m, - Err(e) => return Response::Error { error: e }, - }; - - let mut signing_commitments = BTreeMap::new(); - for (id_hex, comm_json) in &commitments { - let id = match parse_identifier(id_hex) { - Ok(id) => id, - Err(e) => return Response::Error { error: e }, - }; - let hiding_hex = match comm_json["hiding"].as_str() { - Some(h) => h, - None => { - return Response::Error { - error: "missing hiding commitment".into(), - } - } - }; - let binding_hex = match comm_json["binding"].as_str() { - Some(b) => b, - None => { - return Response::Error { - error: "missing binding commitment".into(), - } - } - }; - - let hiding = match parse_point(hiding_hex) { - Ok(p) => p, - Err(e) => return Response::Error { error: e }, - }; - let binding = match parse_point(binding_hex) { - Ok(p) => p, - Err(e) => return Response::Error { error: e }, - }; - - signing_commitments.insert( - id, - threshold::nonce::SigningCommitments { binding, hiding }, - ); - } - - let (final_kp, _final_pkp) = if apply_tweak { - let merkle_root = merkle_root_hex - .as_ref() - .and_then(|h| hex_decode(h).ok()); - let mr_ref = merkle_root.as_deref(); - (kp.tweak(mr_ref), pkp.tweak(mr_ref)) - } else { - (kp, pkp) - }; - - let signing_package = - threshold::commitment::SigningPackage::new(signing_commitments, message); - - match threshold::signing::sign(&signing_package, &signing_nonce, &final_kp) { - Ok(share) => { - let share_hex = hex_encode(&scalar_to_bytes(&share.s)); - Response::Sign { share_hex } - } - Err(e) => Response::Error { - error: format!("sign failed: {}", e), - }, - } - } - - fn handle_get_info(&self) -> Response { - let identifier_hex = self - .key_package - .as_ref() - .map(|kp| hex_encode(&kp.identifier.serialize())); - - Response::Info { - has_key_package: self.key_package.is_some(), - has_pending_nonce: self.pending_nonce.is_some(), - identifier_hex, - } - } -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -fn scalar_from_bytes(bytes: &[u8; 32]) -> Result { - use k256::elliptic_curve::scalar::FromUintUnchecked; - use k256::U256; - let uint = U256::from_be_slice(bytes); - let s = Scalar::from_uint_unchecked(uint); - if bool::from(s.is_zero()) { - return Err("stored DKG secret is zero".into()); - } - Ok(s) -} - -fn random_scalar(rng: &mut impl rand_core::RngCore) -> Scalar { - use k256::elliptic_curve::ops::Reduce; - use k256::U256; - loop { - let mut bytes = [0u8; 32]; - rng.fill_bytes(&mut bytes); - let wide = U256::from_be_slice(&bytes); - let s = >::reduce(wide); - if !bool::from(s.is_zero()) { - return s; - } - } -} - -fn hex_encode(bytes: &[u8]) -> String { - bytes.iter().map(|b| format!("{:02x}", b)).collect() -} - -fn hex_decode(s: &str) -> Result, String> { - if s.len() % 2 != 0 { - return Err("odd-length hex string".into()); - } - let mut out = Vec::with_capacity(s.len() / 2); - for i in (0..s.len()).step_by(2) { - let byte = u8::from_str_radix(&s[i..i + 2], 16) - .map_err(|_| format!("invalid hex at position {}", i))?; - out.push(byte); - } - Ok(out) -} - -fn parse_identifier(hex_str: &str) -> Result { - let bytes = hex_decode(hex_str)?; - if bytes.len() != 32 { - return Err(format!("identifier must be 32 bytes, got {}", bytes.len())); - } - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes); - Identifier::deserialize(&arr).map_err(|e| format!("invalid identifier: {}", e)) -} - -fn parse_point(hex_str: &str) -> Result { - let bytes = hex_decode(hex_str)?; - if bytes.len() != 33 { - return Err(format!("point must be 33 bytes, got {}", bytes.len())); - } - let mut arr = [0u8; 33]; - arr.copy_from_slice(&bytes); - point::deserialize_compressed(&arr).map_err(|e| format!("invalid point: {}", e)) -} diff --git a/pico-signer/src/main.rs b/pico-signer/src/main.rs deleted file mode 100644 index e7e18f5..0000000 --- a/pico-signer/src/main.rs +++ /dev/null @@ -1,196 +0,0 @@ -#![no_std] -#![no_main] - -extern crate alloc; - -mod allocator; -mod chunking; -mod handler; -mod protocol; -mod rng; -mod storage; -mod usb_hid; - -use defmt::*; -use defmt_rtt as _; -use panic_probe as _; - -use embassy_executor::Spawner; -use embassy_rp::bind_interrupts; -use embassy_rp::peripherals::USB; -use embassy_rp::usb::InterruptHandler; -use embassy_usb::class::hid::{HidReaderWriter, State as HidState}; -use embassy_usb::Builder; - -use embassy_rp::peripherals::TRNG; - -bind_interrupts!(struct Irqs { - USBCTRL_IRQ => InterruptHandler; - TRNG_IRQ => embassy_rp::trng::InterruptHandler; -}); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - // 1. Initialize heap allocator - allocator::init(); - - // 2. Initialize RP2350 peripherals - let p = embassy_rp::init(Default::default()); - - // 3. Initialize TRNG-based RNG - let pico_rng = rng::PicoRng::new(p.TRNG, Irqs); - - // 4. Initialize flash storage and try loading existing keys - let mut key_storage = storage::KeyStorage::new(p.FLASH, p.DMA_CH0); - let loaded_keys = key_storage.load(); - - // 5. Initialize signer state - let mut signer_state = handler::SignerState::new(pico_rng); - if let Some((kp, pkp, dkg_secret)) = loaded_keys { - info!("Restored key material from flash"); - signer_state.restore_keys(kp, pkp, dkg_secret); - } else { - info!("No key material in flash -- awaiting DKG"); - } - - // 6. Set up USB device - let driver = embassy_rp::usb::Driver::new(p.USB, Irqs); - - let mut config = embassy_usb::Config::new(usb_hid::VID, usb_hid::PID); - config.manufacturer = Some("MPCWallet"); - config.product = Some("Pico Signer"); - config.serial_number = Some("001"); - config.max_power = 100; - config.max_packet_size_0 = 64; - - let mut config_descriptor = [0; 256]; - let mut bos_descriptor = [0; 256]; - let mut control_buf = [0; 64]; - let mut hid_state = HidState::new(); - - let mut builder = Builder::new( - driver, - config, - &mut config_descriptor, - &mut bos_descriptor, - &mut [], // msos descriptors - &mut control_buf, - ); - - // 7. Create HID class with vendor-defined report descriptor - let hid_config = embassy_usb::class::hid::Config { - report_descriptor: usb_hid::REPORT_DESCRIPTOR, - request_handler: None, - poll_ms: 5, - max_packet_size: 64, - }; - let hid = HidReaderWriter::<_, 64, 64>::new(&mut builder, &mut hid_state, hid_config); - - // 8. Build USB device - let mut usb = builder.build(); - - // 9. Split HID into reader/writer and run everything - let (reader, writer) = hid.split(); - - let usb_fut = usb.run(); - let hid_fut = run_hid_loop(reader, writer, &mut signer_state, &mut key_storage); - - // Run USB stack and HID handler concurrently - embassy_futures::join::join(usb_fut, hid_fut).await; -} - -/// Main HID request-response loop. -async fn run_hid_loop<'d, D: embassy_usb::driver::Driver<'d>>( - mut reader: embassy_usb::class::hid::HidReader<'d, D, 64>, - mut writer: embassy_usb::class::hid::HidWriter<'d, D, 64>, - state: &mut handler::SignerState, - storage: &mut storage::KeyStorage, -) { - let mut reassembler = chunking::Reassembler::new(); - let mut report_buf = [0u8; 64]; - - info!("Pico Signer ready -- waiting for HID commands"); - - loop { - match reader.read(&mut report_buf).await { - Ok(_) => { - let report: &[u8; 64] = &report_buf; - - match reassembler.feed(report) { - Ok(Some(message)) => { - // Complete message received - let response_bytes = process_message(&message, state, storage); - - // Chunk and send response - let reports = chunking::chunk_message(&response_bytes); - for report in &reports { - if let Err(e) = writer.write(report).await { - error!("HID write error: {:?}", e); - break; - } - } - } - Ok(None) => { - // More packets needed - } - Err(e) => { - warn!("Reassembly error: {}", e); - reassembler.reset(); - // Send error response - let err_json = br#"{"error":"protocol framing error"}"#; - let reports = chunking::chunk_message(err_json); - for report in &reports { - let _ = writer.write(report).await; - } - } - } - } - Err(e) => { - error!("HID read error: {:?}", e); - // USB disconnected -- wait and retry - embassy_time::Timer::after_millis(100).await; - } - } - } -} - -/// Parse a JSON request, dispatch to handler, return JSON response bytes. -fn process_message( - msg: &[u8], - state: &mut handler::SignerState, - storage: &mut storage::KeyStorage, -) -> alloc::vec::Vec { - let json_str = match core::str::from_utf8(msg) { - Ok(s) => s, - Err(_) => return br#"{"error":"invalid UTF-8"}"#.to_vec(), - }; - - let response = match serde_json::from_str::(json_str) { - Ok(req) => { - let is_dkg_round3 = matches!(&req, protocol::Request::DkgRound3 { .. }); - let resp = state.handle(req); - - // Persist keys after successful DKG round 3 - if is_dkg_round3 { - if let (Some(kp), Some(pkp)) = (state.key_package(), state.public_key_package()) { - if let Err(()) = storage.save(kp, pkp, state.dkg_secret()) { - warn!("Failed to persist key material to flash"); - } - } - } - - resp - } - Err(e) => { - warn!("Invalid request: {}", defmt::Debug2Format(&e)); - protocol::Response::Error { - error: alloc::format!("invalid request: {}", e), - } - } - }; - - match serde_json::to_vec(&response) { - Ok(bytes) => bytes, - Err(_) => br#"{"error":"serialization failed"}"#.to_vec(), - } -} diff --git a/pico-signer/src/rng.rs b/pico-signer/src/rng.rs deleted file mode 100644 index 7efed74..0000000 --- a/pico-signer/src/rng.rs +++ /dev/null @@ -1,47 +0,0 @@ -use rand_core::{CryptoRng, RngCore}; - -/// Hardware TRNG wrapper implementing `rand_core::RngCore + CryptoRng`. -/// -/// Uses the RP2350's true random number generator peripheral. -pub struct PicoRng { - inner: embassy_rp::trng::Trng<'static, embassy_rp::peripherals::TRNG>, -} - -impl PicoRng { - pub fn new( - trng: embassy_rp::Peri<'static, embassy_rp::peripherals::TRNG>, - irq: impl embassy_rp::interrupt::typelevel::Binding< - embassy_rp::interrupt::typelevel::TRNG_IRQ, - embassy_rp::trng::InterruptHandler, - > + 'static, - ) -> Self { - Self { - inner: embassy_rp::trng::Trng::new(trng, irq, embassy_rp::trng::Config::default()), - } - } -} - -impl RngCore for PicoRng { - fn next_u32(&mut self) -> u32 { - let mut buf = [0u8; 4]; - self.fill_bytes(&mut buf); - u32::from_le_bytes(buf) - } - - fn next_u64(&mut self) -> u64 { - let mut buf = [0u8; 8]; - self.fill_bytes(&mut buf); - u64::from_le_bytes(buf) - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - self.inner.blocking_fill_bytes(dest); - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { - self.fill_bytes(dest); - Ok(()) - } -} - -impl CryptoRng for PicoRng {} diff --git a/pico-signer/src/storage.rs b/pico-signer/src/storage.rs deleted file mode 100644 index 1fab8b3..0000000 --- a/pico-signer/src/storage.rs +++ /dev/null @@ -1,254 +0,0 @@ -//! Flash persistence for key material. -//! -//! Stores KeyPackage, PublicKeyPackage, and (optionally) the original DKG -//! secret in the last 4KB flash sector so they survive power cycles. -//! -//! Layout v2: -//! [magic:1=0xA5][version:1=0x02] -//! [secret_len:2 BE][DKG secret bytes (32)] -//! [kp_len:2 BE][KeyPackage JSON bytes] -//! [pkp_len:2 BE][PublicKeyPackage JSON bytes] -//! [crc32:4 BE] (over all preceding bytes) -//! -//! Layout v1 (backward-compatible read): -//! [magic:1=0xA5][version:1=0x01] -//! [kp_len:2 BE][KeyPackage JSON bytes] -//! [pkp_len:2 BE][PublicKeyPackage JSON bytes] -//! [crc32:4 BE] (over all preceding bytes) - -extern crate alloc; - -use defmt::*; -use embassy_rp::flash::{Async, Flash, ERASE_SIZE}; -use threshold::keys::{KeyPackage, PublicKeyPackage}; - -const STORAGE_OFFSET: u32 = 0x3FF000; // Last 4KB sector -const SECTOR_SIZE: usize = ERASE_SIZE; -const MAGIC: u8 = 0xA5; -const VERSION: u8 = 0x02; -const VERSION_V1: u8 = 0x01; - -pub struct KeyStorage { - flash: Flash<'static, embassy_rp::peripherals::FLASH, Async, { 4 * 1024 * 1024 }>, -} - -impl KeyStorage { - pub fn new( - flash: embassy_rp::Peri<'static, embassy_rp::peripherals::FLASH>, - dma: embassy_rp::Peri<'static, impl embassy_rp::dma::Channel>, - ) -> Self { - Self { - flash: Flash::new(flash, dma), - } - } - - /// Load key material from flash. Returns None if no valid data found. - /// The optional `[u8; 32]` is the DKG secret (present in v2, absent in v1). - pub fn load(&mut self) -> Option<(KeyPackage, PublicKeyPackage, Option<[u8; 32]>)> { - let mut buf = [0u8; SECTOR_SIZE]; - if self.flash.blocking_read(STORAGE_OFFSET, &mut buf).is_err() { - warn!("Flash read failed"); - return None; - } - - if buf[0] != MAGIC { - info!("No key material in flash (magic mismatch)"); - return None; - } - - match buf[1] { - VERSION => self.load_v2(&buf), - VERSION_V1 => self.load_v1(&buf), - _ => { - info!("No key material in flash (version mismatch)"); - None - } - } - } - - fn load_v1(&self, buf: &[u8; SECTOR_SIZE]) -> Option<(KeyPackage, PublicKeyPackage, Option<[u8; 32]>)> { - // Parse KeyPackage - let kp_len = u16::from_be_bytes([buf[2], buf[3]]) as usize; - if 4 + kp_len + 2 > SECTOR_SIZE - 4 { - warn!("Invalid kp_len in flash"); - return None; - } - let kp_json = core::str::from_utf8(&buf[4..4 + kp_len]).ok()?; - - // Parse PublicKeyPackage - let pkp_offset = 4 + kp_len; - let pkp_len = u16::from_be_bytes([buf[pkp_offset], buf[pkp_offset + 1]]) as usize; - let pkp_start = pkp_offset + 2; - if pkp_start + pkp_len > SECTOR_SIZE - 4 { - warn!("Invalid pkp_len in flash"); - return None; - } - let pkp_json = core::str::from_utf8(&buf[pkp_start..pkp_start + pkp_len]).ok()?; - - // Verify CRC32 - let crc_offset = pkp_start + pkp_len; - let stored_crc = u32::from_be_bytes([ - buf[crc_offset], - buf[crc_offset + 1], - buf[crc_offset + 2], - buf[crc_offset + 3], - ]); - let computed_crc = crc32(&buf[..crc_offset]); - if stored_crc != computed_crc { - warn!("Flash CRC mismatch"); - return None; - } - - let kp = KeyPackage::from_json(kp_json).ok()?; - let pkp = PublicKeyPackage::from_json(pkp_json).ok()?; - - info!("Loaded key material from flash (v1, no DKG secret)"); - Some((kp, pkp, None)) - } - - fn load_v2(&self, buf: &[u8; SECTOR_SIZE]) -> Option<(KeyPackage, PublicKeyPackage, Option<[u8; 32]>)> { - // Parse DKG secret - let secret_len = u16::from_be_bytes([buf[2], buf[3]]) as usize; - if secret_len != 32 { - warn!("Invalid secret_len in flash: {}", secret_len); - return None; - } - let mut dkg_secret = [0u8; 32]; - dkg_secret.copy_from_slice(&buf[4..36]); - - // Parse KeyPackage - let kp_offset = 36; - let kp_len = u16::from_be_bytes([buf[kp_offset], buf[kp_offset + 1]]) as usize; - let kp_start = kp_offset + 2; - if kp_start + kp_len + 2 > SECTOR_SIZE - 4 { - warn!("Invalid kp_len in flash"); - return None; - } - let kp_json = core::str::from_utf8(&buf[kp_start..kp_start + kp_len]).ok()?; - - // Parse PublicKeyPackage - let pkp_offset = kp_start + kp_len; - let pkp_len = u16::from_be_bytes([buf[pkp_offset], buf[pkp_offset + 1]]) as usize; - let pkp_start = pkp_offset + 2; - if pkp_start + pkp_len > SECTOR_SIZE - 4 { - warn!("Invalid pkp_len in flash"); - return None; - } - let pkp_json = core::str::from_utf8(&buf[pkp_start..pkp_start + pkp_len]).ok()?; - - // Verify CRC32 - let crc_offset = pkp_start + pkp_len; - let stored_crc = u32::from_be_bytes([ - buf[crc_offset], - buf[crc_offset + 1], - buf[crc_offset + 2], - buf[crc_offset + 3], - ]); - let computed_crc = crc32(&buf[..crc_offset]); - if stored_crc != computed_crc { - warn!("Flash CRC mismatch"); - return None; - } - - let kp = KeyPackage::from_json(kp_json).ok()?; - let pkp = PublicKeyPackage::from_json(pkp_json).ok()?; - - info!("Loaded key material from flash (v2, with DKG secret)"); - Some((kp, pkp, Some(dkg_secret))) - } - - /// Save key material to flash. Erases the sector first. - pub fn save( - &mut self, - kp: &KeyPackage, - pkp: &PublicKeyPackage, - dkg_secret: Option<&[u8; 32]>, - ) -> Result<(), ()> { - let kp_json = kp.to_json(); - let pkp_json = pkp.to_json(); - - let kp_bytes = kp_json.as_bytes(); - let pkp_bytes = pkp_json.as_bytes(); - - let secret_bytes: &[u8; 32] = match dkg_secret { - Some(s) => s, - None => { - error!("DKG secret required for v2 save"); - return Err(()); - } - }; - - // Build buffer: header(2) + secret_len(2) + secret(32) + kp_len(2) + kp + pkp_len(2) + pkp + crc(4) - let total = 2 + 2 + 32 + 2 + kp_bytes.len() + 2 + pkp_bytes.len() + 4; - if total > SECTOR_SIZE { - error!("Key material too large for flash sector"); - return Err(()); - } - - let mut buf = [0xFFu8; SECTOR_SIZE]; - buf[0] = MAGIC; - buf[1] = VERSION; - - // DKG secret - buf[2..4].copy_from_slice(&32u16.to_be_bytes()); - buf[4..36].copy_from_slice(secret_bytes); - - // KeyPackage - let kp_offset = 36; - buf[kp_offset..kp_offset + 2].copy_from_slice(&(kp_bytes.len() as u16).to_be_bytes()); - let kp_start = kp_offset + 2; - buf[kp_start..kp_start + kp_bytes.len()].copy_from_slice(kp_bytes); - - // PublicKeyPackage - let pkp_offset = kp_start + kp_bytes.len(); - buf[pkp_offset..pkp_offset + 2].copy_from_slice(&(pkp_bytes.len() as u16).to_be_bytes()); - let pkp_start = pkp_offset + 2; - buf[pkp_start..pkp_start + pkp_bytes.len()].copy_from_slice(pkp_bytes); - - // CRC32 - let crc_offset = pkp_start + pkp_bytes.len(); - let crc = crc32(&buf[..crc_offset]); - buf[crc_offset..crc_offset + 4].copy_from_slice(&crc.to_be_bytes()); - - // Erase and write - self.flash - .blocking_erase(STORAGE_OFFSET, STORAGE_OFFSET + SECTOR_SIZE as u32) - .map_err(|_| { - error!("Flash erase failed"); - })?; - - self.flash - .blocking_write(STORAGE_OFFSET, &buf) - .map_err(|_| { - error!("Flash write failed"); - })?; - - info!("Saved key material to flash"); - Ok(()) - } - - /// Erase key material from flash. - pub fn erase(&mut self) -> Result<(), ()> { - self.flash - .blocking_erase(STORAGE_OFFSET, STORAGE_OFFSET + SECTOR_SIZE as u32) - .map_err(|_| { - error!("Flash erase failed"); - }) - } -} - -/// Simple CRC32 (no lookup table to save flash space). -fn crc32(data: &[u8]) -> u32 { - let mut crc: u32 = 0xFFFF_FFFF; - for &byte in data { - crc ^= byte as u32; - for _ in 0..8 { - if crc & 1 != 0 { - crc = (crc >> 1) ^ 0xEDB8_8320; - } else { - crc >>= 1; - } - } - } - !crc -} diff --git a/scripts/99-pico-signer.rules b/scripts/99-hwsigner.rules similarity index 78% rename from scripts/99-pico-signer.rules rename to scripts/99-hwsigner.rules index 424aa9b..752bd8e 100644 --- a/scripts/99-pico-signer.rules +++ b/scripts/99-hwsigner.rules @@ -1,3 +1,3 @@ -# Pico Signer USB HID - allow non-root access +# HW Signer USB HID - allow non-root access SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="0001", MODE="0666" SUBSYSTEM=="usb", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="0001", MODE="0666" diff --git a/scripts/test_pico.py b/scripts/test_hwsigner.py similarity index 97% rename from scripts/test_pico.py rename to scripts/test_hwsigner.py index a0dcdb1..8dd6698 100755 --- a/scripts/test_pico.py +++ b/scripts/test_hwsigner.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 -"""Smoke test for Pico Signer firmware over USB HID. +"""Smoke test for HW Signer firmware over USB HID. Sends JSON commands using the same chunking protocol as the Flutter app. Requires: pip install hidapi Usage: - python scripts/test_pico.py # just get_info - python scripts/test_pico.py --full-dkg # run full DKG + sign with signer-server as 2nd participant + python scripts/test_hwsigner.py # just get_info + python scripts/test_hwsigner.py --full-dkg # run full DKG + sign with signer-server as 2nd participant """ import argparse @@ -337,12 +337,12 @@ def run_full_dkg(device): # --------------------------------------------------------------------------- def main(): - parser = argparse.ArgumentParser(description="Test Pico Signer firmware") + parser = argparse.ArgumentParser(description="Test HW Signer firmware") parser.add_argument("--full-dkg", action="store_true", help="Run full 2-of-2 DKG + sign with signer-server as 2nd participant") args = parser.parse_args() - print(f"Looking for Pico Signer (VID=0x{VID:04x} PID=0x{PID:04x})...") + print(f"Looking for HW Signer (VID=0x{VID:04x} PID=0x{PID:04x})...") device = hid.device() try: diff --git a/threshold/src/auth.rs b/threshold/src/auth.rs index 34f391c..e4eec65 100644 --- a/threshold/src/auth.rs +++ b/threshold/src/auth.rs @@ -2,7 +2,7 @@ //! //! Provides [`AuthSigner`] for signing authentication messages and //! [`verify_schnorr_signature`] for standalone verification. -//! This module requires the `std` feature (pico-signer does not need it). +//! This module requires the `std` feature (hwsigner does not need it). use crate::error::Error; use crate::hash::{compute_challenge, tagged_hash}; diff --git a/threshold/src/random.rs b/threshold/src/random.rs index 6707636..01a10a2 100644 --- a/threshold/src/random.rs +++ b/threshold/src/random.rs @@ -1,7 +1,7 @@ //! Random scalar generation utilities. //! //! Provides functions for generating random and seeded scalars. -//! Requires the `std` feature (pico-signer uses its own RNG). +//! Requires the `std` feature (hwsigner uses its own RNG). use alloc::vec::Vec; use k256::elliptic_curve::ops::Reduce;