From 788d9bd6ea7781068f758512e0346a5c4f7103b4 Mon Sep 17 00:00:00 2001 From: martijn Date: Thu, 24 Apr 2025 12:07:40 +0200 Subject: [PATCH] init project --- cyw43-firmware/43439A0.bin | Bin 0 -> 231077 bytes cyw43-firmware/43439A0_btfw.bin | Bin 0 -> 6164 bytes cyw43-firmware/43439A0_clm.bin | Bin 0 -> 984 bytes .../LICENSE-permissive-binary-license-1.0.txt | 49 + cyw43-firmware/README.md | 14 + cyw43-pio/CHANGELOG.md | 25 + cyw43-pio/Cargo.toml | 22 + cyw43-pio/README.md | 3 + cyw43-pio/src/lib.rs | 245 +++ cyw43/CHANGELOG.md | 30 + cyw43/Cargo.toml | 48 + cyw43/README.md | 55 + cyw43/src/bluetooth.rs | 508 ++++++ cyw43/src/bus.rs | 388 +++++ cyw43/src/consts.rs | 670 ++++++++ cyw43/src/control.rs | 745 ++++++++ cyw43/src/countries.rs | 481 ++++++ cyw43/src/events.rs | 400 +++++ cyw43/src/fmt.rs | 270 +++ cyw43/src/ioctl.rs | 123 ++ cyw43/src/lib.rs | 299 ++++ cyw43/src/nvram.rs | 48 + cyw43/src/runner.rs | 666 ++++++++ cyw43/src/structs.rs | 555 ++++++ cyw43/src/util.rs | 20 + embassy-embedded-hal/CHANGELOG.md | 26 + embassy-embedded-hal/Cargo.toml | 42 + embassy-embedded-hal/README.md | 12 + .../src/adapter/blocking_async.rs | 149 ++ embassy-embedded-hal/src/adapter/mod.rs | 7 + .../src/adapter/yielding_async.rs | 169 ++ .../src/flash/concat_flash.rs | 225 +++ embassy-embedded-hal/src/flash/mem_flash.rs | 125 ++ embassy-embedded-hal/src/flash/mod.rs | 8 + .../src/flash/partition/asynch.rs | 149 ++ .../src/flash/partition/blocking.rs | 159 ++ .../src/flash/partition/mod.rs | 28 + embassy-embedded-hal/src/lib.rs | 39 + .../src/shared_bus/asynch/i2c.rs | 166 ++ .../src/shared_bus/asynch/mod.rs | 3 + .../src/shared_bus/asynch/spi.rs | 212 +++ .../src/shared_bus/blocking/i2c.rs | 187 ++ .../src/shared_bus/blocking/mod.rs | 3 + .../src/shared_bus/blocking/spi.rs | 167 ++ embassy-embedded-hal/src/shared_bus/mod.rs | 59 + embassy-executor-macros/Cargo.toml | 25 + embassy-executor-macros/README.md | 5 + embassy-executor-macros/src/lib.rs | 175 ++ embassy-executor-macros/src/macros/main.rs | 184 ++ embassy-executor-macros/src/macros/mod.rs | 2 + embassy-executor-macros/src/macros/task.rs | 220 +++ embassy-executor-macros/src/util.rs | 74 + embassy-executor/CHANGELOG.md | 97 ++ embassy-executor/Cargo.toml | 205 +++ embassy-executor/README.md | 37 + embassy-executor/build.rs | 99 ++ embassy-executor/build_common.rs | 126 ++ embassy-executor/gen_config.py | 88 + embassy-executor/src/arch/avr.rs | 72 + embassy-executor/src/arch/cortex_m.rs | 231 +++ embassy-executor/src/arch/riscv32.rs | 80 + embassy-executor/src/arch/spin.rs | 58 + embassy-executor/src/arch/std.rs | 94 + embassy-executor/src/arch/wasm.rs | 83 + embassy-executor/src/fmt.rs | 270 +++ embassy-executor/src/lib.rs | 150 ++ embassy-executor/src/raw/mod.rs | 571 +++++++ embassy-executor/src/raw/run_queue_atomics.rs | 88 + .../src/raw/run_queue_critical_section.rs | 74 + embassy-executor/src/raw/state_atomics.rs | 58 + embassy-executor/src/raw/state_atomics_arm.rs | 83 + .../src/raw/state_critical_section.rs | 73 + embassy-executor/src/raw/timer_queue.rs | 73 + embassy-executor/src/raw/trace.rs | 84 + embassy-executor/src/raw/util.rs | 57 + embassy-executor/src/raw/waker.rs | 42 + embassy-executor/src/raw/waker_turbo.rs | 34 + embassy-executor/src/spawner.rs | 232 +++ embassy-executor/tests/test.rs | 285 ++++ embassy-executor/tests/ui.rs | 23 + embassy-executor/tests/ui/abi.rs | 8 + embassy-executor/tests/ui/abi.stderr | 5 + embassy-executor/tests/ui/bad_return.rs | 10 + embassy-executor/tests/ui/bad_return.stderr | 5 + embassy-executor/tests/ui/generics.rs | 8 + embassy-executor/tests/ui/generics.stderr | 5 + embassy-executor/tests/ui/impl_trait.rs | 6 + embassy-executor/tests/ui/impl_trait.stderr | 5 + .../tests/ui/impl_trait_nested.rs | 8 + .../tests/ui/impl_trait_nested.stderr | 5 + .../tests/ui/impl_trait_static.rs | 6 + .../tests/ui/impl_trait_static.stderr | 5 + .../tests/ui/nonstatic_ref_anon.rs | 6 + .../tests/ui/nonstatic_ref_anon.stderr | 5 + .../tests/ui/nonstatic_ref_anon_nested.rs | 6 + .../tests/ui/nonstatic_ref_anon_nested.stderr | 5 + .../tests/ui/nonstatic_ref_elided.rs | 6 + .../tests/ui/nonstatic_ref_elided.stderr | 5 + .../tests/ui/nonstatic_ref_generic.rs | 6 + .../tests/ui/nonstatic_ref_generic.stderr | 11 + .../tests/ui/nonstatic_struct_anon.rs | 8 + .../tests/ui/nonstatic_struct_anon.stderr | 5 + .../tests/ui/nonstatic_struct_elided.rs | 8 + .../tests/ui/nonstatic_struct_elided.stderr | 10 + .../tests/ui/nonstatic_struct_generic.rs | 8 + .../tests/ui/nonstatic_struct_generic.stderr | 11 + embassy-executor/tests/ui/not_async.rs | 8 + embassy-executor/tests/ui/not_async.stderr | 5 + embassy-executor/tests/ui/self.rs | 8 + embassy-executor/tests/ui/self.stderr | 13 + embassy-executor/tests/ui/self_ref.rs | 8 + embassy-executor/tests/ui/self_ref.stderr | 13 + embassy-executor/tests/ui/where_clause.rs | 12 + embassy-executor/tests/ui/where_clause.stderr | 7 + embassy-futures/Cargo.toml | 28 + embassy-futures/README.md | 13 + embassy-futures/src/block_on.rs | 45 + embassy-futures/src/fmt.rs | 270 +++ embassy-futures/src/join.rs | 322 ++++ embassy-futures/src/lib.rs | 15 + embassy-futures/src/select.rs | 545 ++++++ embassy-futures/src/yield_now.rs | 49 + embassy-hal-internal/Cargo.toml | 37 + embassy-hal-internal/README.md | 6 + embassy-hal-internal/build.rs | 7 + embassy-hal-internal/build_common.rs | 94 + .../src/atomic_ring_buffer.rs | 604 +++++++ embassy-hal-internal/src/drop.rs | 56 + embassy-hal-internal/src/fmt.rs | 270 +++ embassy-hal-internal/src/interrupt.rs | 870 ++++++++++ embassy-hal-internal/src/lib.rs | 17 + embassy-hal-internal/src/macros.rs | 135 ++ embassy-hal-internal/src/peripheral.rs | 177 ++ embassy-hal-internal/src/ratio.rs | 130 ++ embassy-net-driver-channel/CHANGELOG.md | 23 + embassy-net-driver-channel/Cargo.toml | 30 + embassy-net-driver-channel/README.md | 86 + embassy-net-driver-channel/src/fmt.rs | 270 +++ embassy-net-driver-channel/src/lib.rs | 410 +++++ embassy-net-driver/CHANGELOG.md | 17 + embassy-net-driver/Cargo.toml | 25 + embassy-net-driver/README.md | 18 + embassy-net-driver/src/lib.rs | 219 +++ embassy-net-wiznet/Cargo.toml | 27 + embassy-net-wiznet/README.md | 16 + embassy-net-wiznet/src/chip/mod.rs | 48 + embassy-net-wiznet/src/chip/w5100s.rs | 65 + embassy-net-wiznet/src/chip/w5500.rs | 76 + embassy-net-wiznet/src/device.rs | 255 +++ embassy-net-wiznet/src/lib.rs | 133 ++ embassy-net/CHANGELOG.md | 87 + embassy-net/Cargo.toml | 83 + embassy-net/README.md | 54 + embassy-net/src/dns.rs | 120 ++ embassy-net/src/driver_util.rs | 112 ++ embassy-net/src/fmt.rs | 270 +++ embassy-net/src/icmp.rs | 859 ++++++++++ embassy-net/src/lib.rs | 892 ++++++++++ embassy-net/src/raw.rs | 190 +++ embassy-net/src/tcp.rs | 920 ++++++++++ embassy-net/src/time.rs | 20 + embassy-net/src/udp.rs | 396 +++++ embassy-rp/CHANGELOG.md | 63 + embassy-rp/Cargo.toml | 176 ++ embassy-rp/LICENSE-APACHE | 202 +++ embassy-rp/LICENSE-MIT | 26 + embassy-rp/README.md | 27 + embassy-rp/build.rs | 39 + embassy-rp/funcsel.txt | 30 + embassy-rp/link-rp.x.in | 8 + embassy-rp/src/adc.rs | 469 +++++ embassy-rp/src/block.rs | 1079 ++++++++++++ embassy-rp/src/bootsel.rs | 83 + embassy-rp/src/clocks.rs | 1196 +++++++++++++ embassy-rp/src/critical_section_impl.rs | 137 ++ embassy-rp/src/dma.rs | 315 ++++ embassy-rp/src/flash.rs | 989 +++++++++++ embassy-rp/src/float/add_sub.rs | 92 + embassy-rp/src/float/cmp.rs | 201 +++ embassy-rp/src/float/conv.rs | 157 ++ embassy-rp/src/float/div.rs | 139 ++ embassy-rp/src/float/functions.rs | 239 +++ embassy-rp/src/float/mod.rs | 150 ++ embassy-rp/src/float/mul.rs | 70 + embassy-rp/src/fmt.rs | 270 +++ embassy-rp/src/gpio.rs | 1389 +++++++++++++++ embassy-rp/src/i2c.rs | 919 ++++++++++ embassy-rp/src/i2c_slave.rs | 395 +++++ embassy-rp/src/intrinsics.rs | 476 ++++++ embassy-rp/src/lib.rs | 759 +++++++++ embassy-rp/src/multicore.rs | 348 ++++ embassy-rp/src/otp.rs | 175 ++ embassy-rp/src/pio/instr.rs | 103 ++ embassy-rp/src/pio/mod.rs | 1479 ++++++++++++++++ embassy-rp/src/pio_programs/hd44780.rs | 203 +++ embassy-rp/src/pio_programs/i2s.rs | 95 ++ embassy-rp/src/pio_programs/mod.rs | 10 + embassy-rp/src/pio_programs/onewire.rs | 109 ++ embassy-rp/src/pio_programs/pwm.rs | 121 ++ embassy-rp/src/pio_programs/rotary_encoder.rs | 75 + embassy-rp/src/pio_programs/stepper.rs | 147 ++ embassy-rp/src/pio_programs/uart.rs | 185 ++ embassy-rp/src/pio_programs/ws2812.rs | 118 ++ embassy-rp/src/pwm.rs | 611 +++++++ embassy-rp/src/relocate.rs | 66 + embassy-rp/src/reset.rs | 15 + embassy-rp/src/rom_data/mod.rs | 33 + embassy-rp/src/rom_data/rp2040.rs | 756 +++++++++ embassy-rp/src/rom_data/rp235x.rs | 752 ++++++++ embassy-rp/src/rtc/datetime_chrono.rs | 62 + embassy-rp/src/rtc/datetime_no_deps.rs | 128 ++ embassy-rp/src/rtc/filter.rs | 100 ++ embassy-rp/src/rtc/mod.rs | 204 +++ embassy-rp/src/spi.rs | 728 ++++++++ embassy-rp/src/time_driver.rs | 144 ++ embassy-rp/src/trng.rs | 405 +++++ embassy-rp/src/uart/buffered.rs | 839 +++++++++ embassy-rp/src/uart/mod.rs | 1509 +++++++++++++++++ embassy-rp/src/usb.rs | 827 +++++++++ embassy-rp/src/watchdog.rs | 165 ++ embassy-sync/CHANGELOG.md | 66 + embassy-sync/Cargo.toml | 45 + embassy-sync/README.md | 21 + embassy-sync/build.rs | 7 + embassy-sync/build_common.rs | 94 + embassy-sync/src/blocking_mutex/mod.rs | 190 +++ embassy-sync/src/blocking_mutex/raw.rs | 149 ++ embassy-sync/src/channel.rs | 935 ++++++++++ embassy-sync/src/fmt.rs | 270 +++ embassy-sync/src/lazy_lock.rs | 152 ++ embassy-sync/src/lib.rs | 25 + embassy-sync/src/mutex.rs | 391 +++++ embassy-sync/src/once_lock.rs | 235 +++ embassy-sync/src/pipe.rs | 919 ++++++++++ embassy-sync/src/priority_channel.rs | 779 +++++++++ embassy-sync/src/pubsub/mod.rs | 787 +++++++++ embassy-sync/src/pubsub/publisher.rs | 314 ++++ embassy-sync/src/pubsub/subscriber.rs | 192 +++ embassy-sync/src/ring_buffer.rs | 138 ++ embassy-sync/src/semaphore.rs | 772 +++++++++ embassy-sync/src/signal.rs | 140 ++ embassy-sync/src/waitqueue/atomic_waker.rs | 63 + .../src/waitqueue/atomic_waker_turbo.rs | 30 + embassy-sync/src/waitqueue/mod.rs | 11 + embassy-sync/src/waitqueue/multi_waker.rs | 58 + .../src/waitqueue/waker_registration.rs | 52 + embassy-sync/src/watch.rs | 1121 ++++++++++++ embassy-sync/src/zerocopy_channel.rs | 359 ++++ embassy-time-driver/CHANGELOG.md | 15 + embassy-time-driver/Cargo.toml | 374 ++++ embassy-time-driver/README.md | 19 + embassy-time-driver/build.rs | 1 + embassy-time-driver/gen_tick.py | 81 + embassy-time-driver/src/lib.rs | 173 ++ embassy-time-driver/src/tick.rs | 482 ++++++ embassy-time-queue-utils/CHANGELOG.md | 10 + embassy-time-queue-utils/Cargo.toml | 58 + embassy-time-queue-utils/README.md | 8 + embassy-time-queue-utils/build.rs | 1 + embassy-time-queue-utils/src/lib.rs | 13 + embassy-time-queue-utils/src/queue_generic.rs | 146 ++ .../src/queue_integrated.rs | 89 + embassy-time/CHANGELOG.md | 74 + embassy-time/Cargo.toml | 442 +++++ embassy-time/README.md | 47 + embassy-time/src/delay.rs | 81 + embassy-time/src/driver_mock.rs | 145 ++ embassy-time/src/driver_std.rs | 104 ++ embassy-time/src/driver_wasm.rs | 103 ++ embassy-time/src/duration.rs | 295 ++++ embassy-time/src/fmt.rs | 270 +++ embassy-time/src/instant.rs | 205 +++ embassy-time/src/lib.rs | 63 + embassy-time/src/timer.rs | 276 +++ embassy-usb-driver/Cargo.toml | 22 + embassy-usb-driver/README.md | 17 + embassy-usb-driver/src/lib.rs | 397 +++++ embassy-usb-logger/CHANGELOG.md | 25 + embassy-usb-logger/Cargo.toml | 21 + embassy-usb-logger/README.md | 15 + embassy-usb-logger/src/lib.rs | 318 ++++ embassy-usb/CHANGELOG.md | 36 + embassy-usb/Cargo.toml | 60 + embassy-usb/README.md | 50 + embassy-usb/build.rs | 96 ++ embassy-usb/gen_config.py | 74 + embassy-usb/src/builder.rs | 623 +++++++ embassy-usb/src/class/cdc_acm.rs | 546 ++++++ embassy-usb/src/class/cdc_ncm/embassy_net.rs | 104 ++ embassy-usb/src/class/cdc_ncm/mod.rs | 528 ++++++ embassy-usb/src/class/hid.rs | 552 ++++++ embassy-usb/src/class/midi.rs | 227 +++ embassy-usb/src/class/mod.rs | 7 + embassy-usb/src/class/uac1/class_codes.rs | 151 ++ embassy-usb/src/class/uac1/mod.rs | 134 ++ embassy-usb/src/class/uac1/speaker.rs | 777 +++++++++ embassy-usb/src/class/uac1/terminal_type.rs | 50 + embassy-usb/src/class/web_usb.rs | 186 ++ embassy-usb/src/control.rs | 146 ++ embassy-usb/src/descriptor.rs | 434 +++++ embassy-usb/src/descriptor_reader.rs | 111 ++ embassy-usb/src/fmt.rs | 270 +++ embassy-usb/src/lib.rs | 786 +++++++++ embassy-usb/src/msos.rs | 728 ++++++++ embassy-usb/src/types.rs | 43 + 305 files changed, 61443 insertions(+) create mode 100644 cyw43-firmware/43439A0.bin create mode 100644 cyw43-firmware/43439A0_btfw.bin create mode 100644 cyw43-firmware/43439A0_clm.bin create mode 100644 cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt create mode 100644 cyw43-firmware/README.md create mode 100644 cyw43-pio/CHANGELOG.md create mode 100644 cyw43-pio/Cargo.toml create mode 100644 cyw43-pio/README.md create mode 100644 cyw43-pio/src/lib.rs create mode 100644 cyw43/CHANGELOG.md create mode 100644 cyw43/Cargo.toml create mode 100644 cyw43/README.md create mode 100644 cyw43/src/bluetooth.rs create mode 100644 cyw43/src/bus.rs create mode 100644 cyw43/src/consts.rs create mode 100644 cyw43/src/control.rs create mode 100644 cyw43/src/countries.rs create mode 100644 cyw43/src/events.rs create mode 100644 cyw43/src/fmt.rs create mode 100644 cyw43/src/ioctl.rs create mode 100644 cyw43/src/lib.rs create mode 100644 cyw43/src/nvram.rs create mode 100644 cyw43/src/runner.rs create mode 100644 cyw43/src/structs.rs create mode 100644 cyw43/src/util.rs create mode 100644 embassy-embedded-hal/CHANGELOG.md create mode 100644 embassy-embedded-hal/Cargo.toml create mode 100644 embassy-embedded-hal/README.md create mode 100644 embassy-embedded-hal/src/adapter/blocking_async.rs create mode 100644 embassy-embedded-hal/src/adapter/mod.rs create mode 100644 embassy-embedded-hal/src/adapter/yielding_async.rs create mode 100644 embassy-embedded-hal/src/flash/concat_flash.rs create mode 100644 embassy-embedded-hal/src/flash/mem_flash.rs create mode 100644 embassy-embedded-hal/src/flash/mod.rs create mode 100644 embassy-embedded-hal/src/flash/partition/asynch.rs create mode 100644 embassy-embedded-hal/src/flash/partition/blocking.rs create mode 100644 embassy-embedded-hal/src/flash/partition/mod.rs create mode 100644 embassy-embedded-hal/src/lib.rs create mode 100644 embassy-embedded-hal/src/shared_bus/asynch/i2c.rs create mode 100644 embassy-embedded-hal/src/shared_bus/asynch/mod.rs create mode 100644 embassy-embedded-hal/src/shared_bus/asynch/spi.rs create mode 100644 embassy-embedded-hal/src/shared_bus/blocking/i2c.rs create mode 100644 embassy-embedded-hal/src/shared_bus/blocking/mod.rs create mode 100644 embassy-embedded-hal/src/shared_bus/blocking/spi.rs create mode 100644 embassy-embedded-hal/src/shared_bus/mod.rs create mode 100644 embassy-executor-macros/Cargo.toml create mode 100644 embassy-executor-macros/README.md create mode 100644 embassy-executor-macros/src/lib.rs create mode 100644 embassy-executor-macros/src/macros/main.rs create mode 100644 embassy-executor-macros/src/macros/mod.rs create mode 100644 embassy-executor-macros/src/macros/task.rs create mode 100644 embassy-executor-macros/src/util.rs create mode 100644 embassy-executor/CHANGELOG.md create mode 100644 embassy-executor/Cargo.toml create mode 100644 embassy-executor/README.md create mode 100644 embassy-executor/build.rs create mode 100644 embassy-executor/build_common.rs create mode 100644 embassy-executor/gen_config.py create mode 100644 embassy-executor/src/arch/avr.rs create mode 100644 embassy-executor/src/arch/cortex_m.rs create mode 100644 embassy-executor/src/arch/riscv32.rs create mode 100644 embassy-executor/src/arch/spin.rs create mode 100644 embassy-executor/src/arch/std.rs create mode 100644 embassy-executor/src/arch/wasm.rs create mode 100644 embassy-executor/src/fmt.rs create mode 100644 embassy-executor/src/lib.rs create mode 100644 embassy-executor/src/raw/mod.rs create mode 100644 embassy-executor/src/raw/run_queue_atomics.rs create mode 100644 embassy-executor/src/raw/run_queue_critical_section.rs create mode 100644 embassy-executor/src/raw/state_atomics.rs create mode 100644 embassy-executor/src/raw/state_atomics_arm.rs create mode 100644 embassy-executor/src/raw/state_critical_section.rs create mode 100644 embassy-executor/src/raw/timer_queue.rs create mode 100644 embassy-executor/src/raw/trace.rs create mode 100644 embassy-executor/src/raw/util.rs create mode 100644 embassy-executor/src/raw/waker.rs create mode 100644 embassy-executor/src/raw/waker_turbo.rs create mode 100644 embassy-executor/src/spawner.rs create mode 100644 embassy-executor/tests/test.rs create mode 100644 embassy-executor/tests/ui.rs create mode 100644 embassy-executor/tests/ui/abi.rs create mode 100644 embassy-executor/tests/ui/abi.stderr create mode 100644 embassy-executor/tests/ui/bad_return.rs create mode 100644 embassy-executor/tests/ui/bad_return.stderr create mode 100644 embassy-executor/tests/ui/generics.rs create mode 100644 embassy-executor/tests/ui/generics.stderr create mode 100644 embassy-executor/tests/ui/impl_trait.rs create mode 100644 embassy-executor/tests/ui/impl_trait.stderr create mode 100644 embassy-executor/tests/ui/impl_trait_nested.rs create mode 100644 embassy-executor/tests/ui/impl_trait_nested.stderr create mode 100644 embassy-executor/tests/ui/impl_trait_static.rs create mode 100644 embassy-executor/tests/ui/impl_trait_static.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_elided.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_elided.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_ref_generic.rs create mode 100644 embassy-executor/tests/ui/nonstatic_ref_generic.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_struct_anon.rs create mode 100644 embassy-executor/tests/ui/nonstatic_struct_anon.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_struct_elided.rs create mode 100644 embassy-executor/tests/ui/nonstatic_struct_elided.stderr create mode 100644 embassy-executor/tests/ui/nonstatic_struct_generic.rs create mode 100644 embassy-executor/tests/ui/nonstatic_struct_generic.stderr create mode 100644 embassy-executor/tests/ui/not_async.rs create mode 100644 embassy-executor/tests/ui/not_async.stderr create mode 100644 embassy-executor/tests/ui/self.rs create mode 100644 embassy-executor/tests/ui/self.stderr create mode 100644 embassy-executor/tests/ui/self_ref.rs create mode 100644 embassy-executor/tests/ui/self_ref.stderr create mode 100644 embassy-executor/tests/ui/where_clause.rs create mode 100644 embassy-executor/tests/ui/where_clause.stderr create mode 100644 embassy-futures/Cargo.toml create mode 100644 embassy-futures/README.md create mode 100644 embassy-futures/src/block_on.rs create mode 100644 embassy-futures/src/fmt.rs create mode 100644 embassy-futures/src/join.rs create mode 100644 embassy-futures/src/lib.rs create mode 100644 embassy-futures/src/select.rs create mode 100644 embassy-futures/src/yield_now.rs create mode 100644 embassy-hal-internal/Cargo.toml create mode 100644 embassy-hal-internal/README.md create mode 100644 embassy-hal-internal/build.rs create mode 100644 embassy-hal-internal/build_common.rs create mode 100644 embassy-hal-internal/src/atomic_ring_buffer.rs create mode 100644 embassy-hal-internal/src/drop.rs create mode 100644 embassy-hal-internal/src/fmt.rs create mode 100644 embassy-hal-internal/src/interrupt.rs create mode 100644 embassy-hal-internal/src/lib.rs create mode 100644 embassy-hal-internal/src/macros.rs create mode 100644 embassy-hal-internal/src/peripheral.rs create mode 100644 embassy-hal-internal/src/ratio.rs create mode 100644 embassy-net-driver-channel/CHANGELOG.md create mode 100644 embassy-net-driver-channel/Cargo.toml create mode 100644 embassy-net-driver-channel/README.md create mode 100644 embassy-net-driver-channel/src/fmt.rs create mode 100644 embassy-net-driver-channel/src/lib.rs create mode 100644 embassy-net-driver/CHANGELOG.md create mode 100644 embassy-net-driver/Cargo.toml create mode 100644 embassy-net-driver/README.md create mode 100644 embassy-net-driver/src/lib.rs create mode 100644 embassy-net-wiznet/Cargo.toml create mode 100644 embassy-net-wiznet/README.md create mode 100644 embassy-net-wiznet/src/chip/mod.rs create mode 100644 embassy-net-wiznet/src/chip/w5100s.rs create mode 100644 embassy-net-wiznet/src/chip/w5500.rs create mode 100644 embassy-net-wiznet/src/device.rs create mode 100644 embassy-net-wiznet/src/lib.rs create mode 100644 embassy-net/CHANGELOG.md create mode 100644 embassy-net/Cargo.toml create mode 100644 embassy-net/README.md create mode 100644 embassy-net/src/dns.rs create mode 100644 embassy-net/src/driver_util.rs create mode 100644 embassy-net/src/fmt.rs create mode 100644 embassy-net/src/icmp.rs create mode 100644 embassy-net/src/lib.rs create mode 100644 embassy-net/src/raw.rs create mode 100644 embassy-net/src/tcp.rs create mode 100644 embassy-net/src/time.rs create mode 100644 embassy-net/src/udp.rs create mode 100644 embassy-rp/CHANGELOG.md create mode 100644 embassy-rp/Cargo.toml create mode 100644 embassy-rp/LICENSE-APACHE create mode 100644 embassy-rp/LICENSE-MIT create mode 100644 embassy-rp/README.md create mode 100644 embassy-rp/build.rs create mode 100644 embassy-rp/funcsel.txt create mode 100644 embassy-rp/link-rp.x.in create mode 100644 embassy-rp/src/adc.rs create mode 100644 embassy-rp/src/block.rs create mode 100644 embassy-rp/src/bootsel.rs create mode 100644 embassy-rp/src/clocks.rs create mode 100644 embassy-rp/src/critical_section_impl.rs create mode 100644 embassy-rp/src/dma.rs create mode 100644 embassy-rp/src/flash.rs create mode 100644 embassy-rp/src/float/add_sub.rs create mode 100644 embassy-rp/src/float/cmp.rs create mode 100644 embassy-rp/src/float/conv.rs create mode 100644 embassy-rp/src/float/div.rs create mode 100644 embassy-rp/src/float/functions.rs create mode 100644 embassy-rp/src/float/mod.rs create mode 100644 embassy-rp/src/float/mul.rs create mode 100644 embassy-rp/src/fmt.rs create mode 100644 embassy-rp/src/gpio.rs create mode 100644 embassy-rp/src/i2c.rs create mode 100644 embassy-rp/src/i2c_slave.rs create mode 100644 embassy-rp/src/intrinsics.rs create mode 100644 embassy-rp/src/lib.rs create mode 100644 embassy-rp/src/multicore.rs create mode 100644 embassy-rp/src/otp.rs create mode 100644 embassy-rp/src/pio/instr.rs create mode 100644 embassy-rp/src/pio/mod.rs create mode 100644 embassy-rp/src/pio_programs/hd44780.rs create mode 100644 embassy-rp/src/pio_programs/i2s.rs create mode 100644 embassy-rp/src/pio_programs/mod.rs create mode 100644 embassy-rp/src/pio_programs/onewire.rs create mode 100644 embassy-rp/src/pio_programs/pwm.rs create mode 100644 embassy-rp/src/pio_programs/rotary_encoder.rs create mode 100644 embassy-rp/src/pio_programs/stepper.rs create mode 100644 embassy-rp/src/pio_programs/uart.rs create mode 100644 embassy-rp/src/pio_programs/ws2812.rs create mode 100644 embassy-rp/src/pwm.rs create mode 100644 embassy-rp/src/relocate.rs create mode 100644 embassy-rp/src/reset.rs create mode 100644 embassy-rp/src/rom_data/mod.rs create mode 100644 embassy-rp/src/rom_data/rp2040.rs create mode 100644 embassy-rp/src/rom_data/rp235x.rs create mode 100644 embassy-rp/src/rtc/datetime_chrono.rs create mode 100644 embassy-rp/src/rtc/datetime_no_deps.rs create mode 100644 embassy-rp/src/rtc/filter.rs create mode 100644 embassy-rp/src/rtc/mod.rs create mode 100644 embassy-rp/src/spi.rs create mode 100644 embassy-rp/src/time_driver.rs create mode 100644 embassy-rp/src/trng.rs create mode 100644 embassy-rp/src/uart/buffered.rs create mode 100644 embassy-rp/src/uart/mod.rs create mode 100644 embassy-rp/src/usb.rs create mode 100644 embassy-rp/src/watchdog.rs create mode 100644 embassy-sync/CHANGELOG.md create mode 100644 embassy-sync/Cargo.toml create mode 100644 embassy-sync/README.md create mode 100644 embassy-sync/build.rs create mode 100644 embassy-sync/build_common.rs create mode 100644 embassy-sync/src/blocking_mutex/mod.rs create mode 100644 embassy-sync/src/blocking_mutex/raw.rs create mode 100644 embassy-sync/src/channel.rs create mode 100644 embassy-sync/src/fmt.rs create mode 100644 embassy-sync/src/lazy_lock.rs create mode 100644 embassy-sync/src/lib.rs create mode 100644 embassy-sync/src/mutex.rs create mode 100644 embassy-sync/src/once_lock.rs create mode 100644 embassy-sync/src/pipe.rs create mode 100644 embassy-sync/src/priority_channel.rs create mode 100644 embassy-sync/src/pubsub/mod.rs create mode 100644 embassy-sync/src/pubsub/publisher.rs create mode 100644 embassy-sync/src/pubsub/subscriber.rs create mode 100644 embassy-sync/src/ring_buffer.rs create mode 100644 embassy-sync/src/semaphore.rs create mode 100644 embassy-sync/src/signal.rs create mode 100644 embassy-sync/src/waitqueue/atomic_waker.rs create mode 100644 embassy-sync/src/waitqueue/atomic_waker_turbo.rs create mode 100644 embassy-sync/src/waitqueue/mod.rs create mode 100644 embassy-sync/src/waitqueue/multi_waker.rs create mode 100644 embassy-sync/src/waitqueue/waker_registration.rs create mode 100644 embassy-sync/src/watch.rs create mode 100644 embassy-sync/src/zerocopy_channel.rs create mode 100644 embassy-time-driver/CHANGELOG.md create mode 100644 embassy-time-driver/Cargo.toml create mode 100644 embassy-time-driver/README.md create mode 100644 embassy-time-driver/build.rs create mode 100644 embassy-time-driver/gen_tick.py create mode 100644 embassy-time-driver/src/lib.rs create mode 100644 embassy-time-driver/src/tick.rs create mode 100644 embassy-time-queue-utils/CHANGELOG.md create mode 100644 embassy-time-queue-utils/Cargo.toml create mode 100644 embassy-time-queue-utils/README.md create mode 100644 embassy-time-queue-utils/build.rs create mode 100644 embassy-time-queue-utils/src/lib.rs create mode 100644 embassy-time-queue-utils/src/queue_generic.rs create mode 100644 embassy-time-queue-utils/src/queue_integrated.rs create mode 100644 embassy-time/CHANGELOG.md create mode 100644 embassy-time/Cargo.toml create mode 100644 embassy-time/README.md create mode 100644 embassy-time/src/delay.rs create mode 100644 embassy-time/src/driver_mock.rs create mode 100644 embassy-time/src/driver_std.rs create mode 100644 embassy-time/src/driver_wasm.rs create mode 100644 embassy-time/src/duration.rs create mode 100644 embassy-time/src/fmt.rs create mode 100644 embassy-time/src/instant.rs create mode 100644 embassy-time/src/lib.rs create mode 100644 embassy-time/src/timer.rs create mode 100644 embassy-usb-driver/Cargo.toml create mode 100644 embassy-usb-driver/README.md create mode 100644 embassy-usb-driver/src/lib.rs create mode 100644 embassy-usb-logger/CHANGELOG.md create mode 100644 embassy-usb-logger/Cargo.toml create mode 100644 embassy-usb-logger/README.md create mode 100644 embassy-usb-logger/src/lib.rs create mode 100644 embassy-usb/CHANGELOG.md create mode 100644 embassy-usb/Cargo.toml create mode 100644 embassy-usb/README.md create mode 100644 embassy-usb/build.rs create mode 100644 embassy-usb/gen_config.py create mode 100644 embassy-usb/src/builder.rs create mode 100644 embassy-usb/src/class/cdc_acm.rs create mode 100644 embassy-usb/src/class/cdc_ncm/embassy_net.rs create mode 100644 embassy-usb/src/class/cdc_ncm/mod.rs create mode 100644 embassy-usb/src/class/hid.rs create mode 100644 embassy-usb/src/class/midi.rs create mode 100644 embassy-usb/src/class/mod.rs create mode 100644 embassy-usb/src/class/uac1/class_codes.rs create mode 100644 embassy-usb/src/class/uac1/mod.rs create mode 100644 embassy-usb/src/class/uac1/speaker.rs create mode 100644 embassy-usb/src/class/uac1/terminal_type.rs create mode 100644 embassy-usb/src/class/web_usb.rs create mode 100644 embassy-usb/src/control.rs create mode 100644 embassy-usb/src/descriptor.rs create mode 100644 embassy-usb/src/descriptor_reader.rs create mode 100644 embassy-usb/src/fmt.rs create mode 100644 embassy-usb/src/lib.rs create mode 100644 embassy-usb/src/msos.rs create mode 100644 embassy-usb/src/types.rs diff --git a/cyw43-firmware/43439A0.bin b/cyw43-firmware/43439A0.bin new file mode 100644 index 0000000000000000000000000000000000000000..a05482fe957fbbf27d334e9b5c4fa6c53022f0bf GIT binary patch literal 231077 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

w>?fhn1I6lG&!cVPT)KYe*G<|KS;7b>wI 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 Mw%74+XFL}sCCRg@9XeigZBR{>d7oT<#-;) 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 X^C0l)rvY5Xo3-pBxXe^|a|7~sadC2< literal 0 HcmV?d00001 diff --git a/cyw43-firmware/43439A0_clm.bin b/cyw43-firmware/43439A0_clm.bin new file mode 100644 index 0000000000000000000000000000000000000000..dc4ee02523209e1cc843272ead9d95fd285f2bbf GIT binary patch literal 984 zcmbVKJ#Q015S_KdmtzAVp^YrjAfa5&E87WHn)BJQ51*Zr?@UZOKuEEa#Fi5wMT&&z zC@Cpvsfg0j@B=9M0SJknn&x)*qvH!!Fv%H12%4k>XHMhE^%SE?Zt5j<4Cb$$s zNvc+{v_%=XGKX^xZ=zNeI8c{{y?Stjdr#Bf;w2DJ=~wXv7Sx*reG7cgsHNY;M~zvq z9}@Hkb_n(?Xh0`b$ZB+~r%B~zY(3j7(W+Zdi=H;K+H5Rf(SSt*>I6K+lgR3HSgS+9 zFtAj};E>AUQ%iM8b%*pkv!K@^U>iLixyK_%EE@5mA_hlPju{hEFlJ*hkDM?)Ve`qZ ztv-m&JL0{DL&>HYT<@M|DOJGZw87fR{2S~B_@lXe;r5-oOZS#n?&tBDvJ2O)-?(`z Nw`jEfwa=eK{sQ=&t>FLw literal 0 HcmV?d00001 diff --git a/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt b/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt new file mode 100644 index 0000000..cbb51f9 --- /dev/null +++ b/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt @@ -0,0 +1,49 @@ +Permissive Binary License + +Version 1.0, July 2019 + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +1) Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. + +2) Unless to the extent explicitly permitted by law, no reverse + engineering, decompilation, or disassembly of this software is + permitted. + +3) Redistribution as part of a software development kit must include the + accompanying file named �DEPENDENCIES� and any dependencies listed in + that file. + +4) Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +Limited patent license. The copyright holders (and contributors) grant a +worldwide, non-exclusive, no-charge, royalty-free patent license to +make, have made, use, offer to sell, sell, import, and otherwise +transfer this software, where such license applies only to those patent +claims licensable by the copyright holders (and contributors) that are +necessarily infringed by this software. This patent license shall not +apply to any combinations that include this software. No hardware is +licensed hereunder. + +If you institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the software +itself infringes your patent(s), then your rights granted under this +license shall terminate as of the date such litigation is filed. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/cyw43-firmware/README.md b/cyw43-firmware/README.md new file mode 100644 index 0000000..10a6b5d --- /dev/null +++ b/cyw43-firmware/README.md @@ -0,0 +1,14 @@ +# WiFi + Bluetooth firmware blobs + +Firmware obtained from https://github.com/georgerobotics/cyw43-driver/tree/main/firmware + +Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) + +## Changelog + +* 2023-08-21: synced with `a1dc885` - Update 43439 fw + clm to come from `wb43439A0_7_95_49_00_combined.h` + add Bluetooth firmware +* 2023-07-28: synced with `ad3bad0` - Update 43439 fw from 7.95.55 to 7.95.62 + +## Notes + +If you update these files, please update the lengths in the `tests/rp/src/bin/cyw43_perf.rs` test (which relies on these files running from RAM). diff --git a/cyw43-pio/CHANGELOG.md b/cyw43-pio/CHANGELOG.md new file mode 100644 index 0000000..4d56973 --- /dev/null +++ b/cyw43-pio/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +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 + +- Update embassy-rp to 0.4.0 + +## 0.3.0 - 2025-01-05 + +- Update embassy-time to 0.4.0 +- Update cyw43 to 0.3.0 +- Update embassy-rp to 0.3.0 + +## 0.2.0 - 2024-08-05 + +- Update to cyw43 0.2.0 +- Update to embassy-rp 0.2.0 + +## 0.1.0 - 2024-01-11 + +- First release diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml new file mode 100644 index 0000000..600caf5 --- /dev/null +++ b/cyw43-pio/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "cyw43-pio" +version = "0.4.0" +edition = "2021" +description = "RP2040 PIO SPI implementation for cyw43" +keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/cyw43-pio" + +[dependencies] +cyw43 = { version = "0.3.0", path = "../cyw43" } +embassy-rp = { version = "0.4.0", path = "../embassy-rp" } +fixed = "1.23.1" +defmt = { version = "0.3", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/" +target = "thumbv6m-none-eabi" +features = ["embassy-rp/rp2040"] diff --git a/cyw43-pio/README.md b/cyw43-pio/README.md new file mode 100644 index 0000000..4a2b2aa --- /dev/null +++ b/cyw43-pio/README.md @@ -0,0 +1,3 @@ +# cyw43-pio + +RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. The PIO driver offloads SPI communication with the WiFi chip and improves throughput. diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs new file mode 100644 index 0000000..c1b3015 --- /dev/null +++ b/cyw43-pio/src/lib.rs @@ -0,0 +1,245 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use core::slice; + +use cyw43::SpiBusCyw43; +use embassy_rp::dma::Channel; +use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; +use embassy_rp::pio::program::pio_asm; +use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::{Peripheral, PeripheralRef}; +use fixed::types::extra::U8; +use fixed::FixedU32; + +/// SPI comms driven by PIO. +pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA> { + cs: Output<'d>, + sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + dma: PeripheralRef<'d, DMA>, + wrap_target: u8, +} + +/// The default clock divider that works for Pico 1 and 2 W. As well as the RM2 on rp2040 devices. +/// same speed as pico-sdk, 62.5Mhz +/// This is actually the fastest we can go without overclocking. +/// According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. +/// However, the PIO uses a fractional divider, which works by introducing jitter when +/// the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz +/// so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles +/// violate the maximum from the data sheet. +pub const DEFAULT_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0200); + +/// The overclock clock divider for the Pico 1 W. Does not work on any known RM2 devices. +/// 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to +/// data sheet, but seems to work fine. +pub const OVERCLOCK_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0100); + +/// The clock divider for the RM2 module. Found to be needed for the Pimoroni Pico Plus 2 W, +/// Pico Plus 2 Non w with the RM2 breakout module, and the Pico 2 with the RM2 breakout module. +pub const RM2_CLOCK_DIVIDER: FixedU32 = FixedU32::from_bits(0x0300); + +impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> +where + DMA: Channel, + PIO: Instance, +{ + /// Create a new instance of PioSpi. + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + clock_divider: FixedU32, + irq: Irq<'d, PIO, 0>, + cs: Output<'d>, + dio: DIO, + clk: CLK, + dma: impl Peripheral

+ 'd, + ) -> Self + where + DIO: PioPin, + CLK: PioPin, + { + let loaded_program = if clock_divider < DEFAULT_CLOCK_DIVIDER { + let overclock_program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 1" // necessary for clkdiv=1. + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + common.load_program(&overclock_program.program) + } else { + let default_program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + common.load_program(&default_program.program) + }; + + let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio); + pin_io.set_pull(Pull::None); + pin_io.set_schmitt(true); + pin_io.set_input_sync_bypass(true); + pin_io.set_drive_strength(Drive::_12mA); + pin_io.set_slew_rate(SlewRate::Fast); + + let mut pin_clk = common.make_pio_pin(clk); + pin_clk.set_drive_strength(Drive::_12mA); + pin_clk.set_slew_rate(SlewRate::Fast); + + let mut cfg = Config::default(); + cfg.use_program(&loaded_program, &[&pin_clk]); + cfg.set_out_pins(&[&pin_io]); + cfg.set_in_pins(&[&pin_io]); + cfg.set_set_pins(&[&pin_io]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.auto_fill = true; + //cfg.shift_out.threshold = 32; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.auto_fill = true; + //cfg.shift_in.threshold = 32; + cfg.clock_divider = clock_divider; + + sm.set_config(&cfg); + + sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]); + sm.set_pins(Level::Low, &[&pin_clk, &pin_io]); + + Self { + cs, + sm, + irq, + dma: dma.into_ref(), + wrap_target: loaded_program.wrap.target, + } + } + + /// Write data to peripheral and return status. + pub async fn write(&mut self, write: &[u32]) -> u32 { + self.sm.set_enable(false); + let write_bits = write.len() * 32 - 1; + let read_bits = 31; + + #[cfg(feature = "defmt")] + defmt::trace!("write={} read={}", write_bits, read_bits); + + unsafe { + self.sm.set_x(write_bits as u32); + self.sm.set_y(read_bits as u32); + self.sm.set_pindir(0b1); + self.sm.exec_jmp(self.wrap_target); + } + + self.sm.set_enable(true); + + self.sm.tx().dma_push(self.dma.reborrow(), write, false).await; + + let mut status = 0; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false) + .await; + status + } + + /// Send command and read response into buffer. + pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 { + self.sm.set_enable(false); + let write_bits = 31; + let read_bits = read.len() * 32 + 32 - 1; + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read write={} read={}", write_bits, read_bits); + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read cmd = {:02x} len = {}", cmd, read.len()); + + unsafe { + self.sm.set_y(read_bits as u32); + self.sm.set_x(write_bits as u32); + self.sm.set_pindir(0b1); + self.sm.exec_jmp(self.wrap_target); + } + + // self.cs.set_low(); + self.sm.set_enable(true); + + self.sm + .tx() + .dma_push(self.dma.reborrow(), slice::from_ref(&cmd), false) + .await; + self.sm.rx().dma_pull(self.dma.reborrow(), read, false).await; + + let mut status = 0; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status), false) + .await; + + #[cfg(feature = "defmt")] + defmt::trace!("cmd_read cmd = {:02x} len = {} read = {:08x}", cmd, read.len(), read); + + status + } +} + +impl<'d, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, PIO, SM, DMA> +where + PIO: Instance, + DMA: Channel, +{ + async fn cmd_write(&mut self, write: &[u32]) -> u32 { + self.cs.set_low(); + let status = self.write(write).await; + self.cs.set_high(); + status + } + + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { + self.cs.set_low(); + let status = self.cmd_read(write, read).await; + self.cs.set_high(); + status + } + + async fn wait_for_event(&mut self) { + self.irq.wait().await; + } +} diff --git a/cyw43/CHANGELOG.md b/cyw43/CHANGELOG.md new file mode 100644 index 0000000..40a6383 --- /dev/null +++ b/cyw43/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +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 + +## 0.3.0 - 2025-01-05 + +- Update `embassy-time` to 0.4.0 +- Add Bluetooth support. +- Add WPA3 support. +- Expand wifi security configuration options. + +## 0.2.0 - 2024-08-05 + +- Update to new versions of embassy-{time,sync} +- Add more fields to the BssInfo packet struct #2461 +- Extend the Scan API #2282 +- Reuse buf to reduce stack usage #2580 +- Add MAC address getter to cyw43 controller #2818 +- Add function to join WPA2 network with precomputed PSK. #2885 +- Add function to close soft AP. #3042 +- Fixing missing re-export #3211 + +## 0.1.0 - 2024-01-11 + +- First release diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml new file mode 100644 index 0000000..34a5133 --- /dev/null +++ b/cyw43/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "cyw43" +version = "0.3.0" +edition = "2021" +description = "Rust driver for the CYW43439 WiFi chip, used in the Raspberry Pi Pico W." +keywords = ["embedded", "cyw43", "embassy-net", "embedded-hal-async", "wifi"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/cyw43" + +[features] +defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt", "bt-hci?/defmt", "embedded-io-async?/defmt-03"] +log = ["dep:log"] +bluetooth = ["dep:bt-hci", "dep:embedded-io-async"] + +# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. +firmware-logs = [] + +[dependencies] +embassy-time = { version = "0.4.0", path = "../embassy-time"} +embassy-sync = { version = "0.6.2", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel"} + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.17", optional = true } + +cortex-m = "0.7.6" +cortex-m-rt = "0.7.0" +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } + +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +num_enum = { version = "0.5.7", default-features = false } +heapless = "0.8.0" + +# Bluetooth deps +embedded-io-async = { version = "0.6.0", optional = true } +bt-hci = { version = "0.2.0", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" +target = "thumbv6m-none-eabi" +features = ["defmt", "firmware-logs"] + +[package.metadata.docs.rs] +features = ["defmt", "firmware-logs"] diff --git a/cyw43/README.md b/cyw43/README.md new file mode 100644 index 0000000..9e9f7a3 --- /dev/null +++ b/cyw43/README.md @@ -0,0 +1,55 @@ +# cyw43 + +Rust driver for the CYW43439 wifi+bluetooth chip. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). + +Works on the following boards: + +- Raspberry Pi Pico W (RP2040) +- Raspberry Pi Pico 2 W (RP2350A) +- Pimoroni Pico Plus 2 W (RP2350B) +- Any board with Raspberry Pi RM2 radio module. +- Any board with the CYW43439 chip, and possibly others if the protocol is similar enough. + +## Features + +Working: + +- WiFi support + - Station mode (joining an AP). + - AP mode (creating an AP) + - Scanning + - Sending and receiving Ethernet frames. + - Using the default MAC address. + - [`embassy-net`](https://embassy.dev) integration. + - RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. + - Using IRQ for device events, no busy polling. + - GPIO support (for LED on the Pico W). +- Bluetooth support + - Bluetooth Classic + LE HCI commands. + - Concurrent operation with WiFi. + - Implements the [bt-hci](https://crates.io/crates/bt-hci) controller traits. + - Works with the [TrouBLE](https://github.com/embassy-rs/trouble) bluetooth LE stack. Check its repo for examples using `cyw43`. + +## Running the WiFi examples + +- Install `probe-rs` following the instructions at . +- `cd examples/rp` +### Example 1: Scan the wifi stations +- `cargo run --release --bin wifi_scan` +### Example 2: Create an access point (IP and credentials in the code) +- `cargo run --release --bin wifi_ap_tcp_server` +### Example 3: Connect to an existing network and create a server +- `cargo run --release --bin wifi_tcp_server` + +After a few seconds, you should see that DHCP picks up an IP address like this +``` +11.944489 DEBUG Acquired IP configuration: +11.944517 DEBUG IP address: 192.168.0.250/24 +11.944620 DEBUG Default gateway: 192.168.0.33 +11.944722 DEBUG DNS server 0: 192.168.0.33 +``` +This example implements a TCP echo server on port 1234. You can try connecting to it with: +``` +nc 192.168.0.250 1234 +``` +Send it some data, you should see it echoed back and printed in the firmware's logs. diff --git a/cyw43/src/bluetooth.rs b/cyw43/src/bluetooth.rs new file mode 100644 index 0000000..f617a8c --- /dev/null +++ b/cyw43/src/bluetooth.rs @@ -0,0 +1,508 @@ +use core::cell::RefCell; +use core::future::Future; +use core::mem::MaybeUninit; + +use bt_hci::transport::WithIndicator; +use bt_hci::{ControllerToHostPacket, FromHciBytes, HostToControllerPacket, PacketKind, WriteHci}; +use embassy_futures::yield_now; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::zerocopy_channel; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::util::round_up; +use crate::{util, CHIP}; + +pub(crate) struct BtState { + rx: [BtPacketBuf; 4], + tx: [BtPacketBuf; 4], + inner: MaybeUninit>, +} + +impl BtState { + pub const fn new() -> Self { + Self { + rx: [const { BtPacketBuf::new() }; 4], + tx: [const { BtPacketBuf::new() }; 4], + inner: MaybeUninit::uninit(), + } + } +} + +struct BtStateInnre<'d> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, BtPacketBuf>, +} + +/// Bluetooth driver. +pub struct BtDriver<'d> { + rx: RefCell>, + tx: RefCell>, +} + +pub(crate) struct BtRunner<'d> { + pub(crate) tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, BtPacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, BtPacketBuf>, + + // Bluetooth circular buffers + addr: u32, + h2b_write_pointer: u32, + b2h_read_pointer: u32, +} + +const BT_HCI_MTU: usize = 1024; + +/// Represents a packet of size MTU. +pub(crate) struct BtPacketBuf { + pub(crate) len: usize, + pub(crate) buf: [u8; BT_HCI_MTU], +} + +impl BtPacketBuf { + /// Create a new packet buffer. + pub const fn new() -> Self { + Self { + len: 0, + buf: [0; BT_HCI_MTU], + } + } +} + +pub(crate) fn new<'d>(state: &'d mut BtState) -> (BtRunner<'d>, BtDriver<'d>) { + // safety: this is a self-referential struct, however: + // - it can't move while the `'d` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let state_uninit: *mut MaybeUninit> = + (&mut state.inner as *mut MaybeUninit>).cast(); + let state = unsafe { &mut *state_uninit }.write(BtStateInnre { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + BtRunner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + + addr: 0, + h2b_write_pointer: 0, + b2h_read_pointer: 0, + }, + BtDriver { + rx: RefCell::new(rx_receiver), + tx: RefCell::new(tx_sender), + }, + ) +} + +pub(crate) struct CybtFwCb<'a> { + pub p_next_line_start: &'a [u8], +} + +pub(crate) struct HexFileData<'a> { + pub addr_mode: i32, + pub hi_addr: u16, + pub dest_addr: u32, + pub p_ds: &'a mut [u8], +} + +pub(crate) fn read_firmware_patch_line(p_btfw_cb: &mut CybtFwCb, hfd: &mut HexFileData) -> u32 { + let mut abs_base_addr32 = 0; + + loop { + let num_bytes = p_btfw_cb.p_next_line_start[0]; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..]; + + let addr = (p_btfw_cb.p_next_line_start[0] as u16) << 8 | p_btfw_cb.p_next_line_start[1] as u16; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[2..]; + + let line_type = p_btfw_cb.p_next_line_start[0]; + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[1..]; + + if num_bytes == 0 { + break; + } + + hfd.p_ds[..num_bytes as usize].copy_from_slice(&p_btfw_cb.p_next_line_start[..num_bytes as usize]); + p_btfw_cb.p_next_line_start = &p_btfw_cb.p_next_line_start[num_bytes as usize..]; + + match line_type { + BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS => { + hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16; + hfd.addr_mode = BTFW_ADDR_MODE_EXTENDED; + } + BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS => { + hfd.hi_addr = (hfd.p_ds[0] as u16) << 8 | hfd.p_ds[1] as u16; + hfd.addr_mode = BTFW_ADDR_MODE_SEGMENT; + } + BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS => { + abs_base_addr32 = (hfd.p_ds[0] as u32) << 24 + | (hfd.p_ds[1] as u32) << 16 + | (hfd.p_ds[2] as u32) << 8 + | hfd.p_ds[3] as u32; + hfd.addr_mode = BTFW_ADDR_MODE_LINEAR32; + } + BTFW_HEX_LINE_TYPE_DATA => { + hfd.dest_addr = addr as u32; + match hfd.addr_mode { + BTFW_ADDR_MODE_EXTENDED => hfd.dest_addr += (hfd.hi_addr as u32) << 16, + BTFW_ADDR_MODE_SEGMENT => hfd.dest_addr += (hfd.hi_addr as u32) << 4, + BTFW_ADDR_MODE_LINEAR32 => hfd.dest_addr += abs_base_addr32, + _ => {} + } + return num_bytes as u32; + } + _ => {} + } + } + 0 +} + +impl<'a> BtRunner<'a> { + pub(crate) async fn init_bluetooth(&mut self, bus: &mut Bus, firmware: &[u8]) { + trace!("init_bluetooth"); + bus.bp_write32(CHIP.bluetooth_base_address + BT2WLAN_PWRUP_ADDR, BT2WLAN_PWRUP_WAKE) + .await; + Timer::after(Duration::from_millis(2)).await; + self.upload_bluetooth_firmware(bus, firmware).await; + self.wait_bt_ready(bus).await; + self.init_bt_buffers(bus).await; + self.wait_bt_awake(bus).await; + self.bt_set_host_ready(bus).await; + self.bt_toggle_intr(bus).await; + } + + pub(crate) async fn upload_bluetooth_firmware( + &mut self, + bus: &mut Bus, + firmware: &[u8], + ) { + // read version + let version_length = firmware[0]; + let _version = &firmware[1..=version_length as usize]; + // skip version + 1 extra byte as per cybt_shared_bus_driver.c + let firmware = &firmware[version_length as usize + 2..]; + // buffers + let mut data_buffer: [u8; 0x100] = [0; 0x100]; + let mut aligned_data_buffer: [u8; 0x100] = [0; 0x100]; + // structs + let mut btfw_cb = CybtFwCb { + p_next_line_start: firmware, + }; + let mut hfd = HexFileData { + addr_mode: BTFW_ADDR_MODE_EXTENDED, + hi_addr: 0, + dest_addr: 0, + p_ds: &mut data_buffer, + }; + loop { + let num_fw_bytes = read_firmware_patch_line(&mut btfw_cb, &mut hfd); + if num_fw_bytes == 0 { + break; + } + let fw_bytes = &hfd.p_ds[0..num_fw_bytes as usize]; + let mut dest_start_addr = hfd.dest_addr + CHIP.bluetooth_base_address; + let mut aligned_data_buffer_index: usize = 0; + // pad start + if !util::is_aligned(dest_start_addr, 4) { + let num_pad_bytes = dest_start_addr % 4; + let padded_dest_start_addr = util::round_down(dest_start_addr, 4); + let memory_value = bus.bp_read32(padded_dest_start_addr).await; + let memory_value_bytes = memory_value.to_le_bytes(); + // Copy the previous memory value's bytes to the start + for i in 0..num_pad_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i]; + aligned_data_buffer_index += 1; + } + // Copy the firmware bytes after the padding bytes + for i in 0..num_fw_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i]; + aligned_data_buffer_index += 1; + } + dest_start_addr = padded_dest_start_addr; + } else { + // Directly copy fw_bytes into aligned_data_buffer if no start padding is required + for i in 0..num_fw_bytes as usize { + aligned_data_buffer[aligned_data_buffer_index] = fw_bytes[i]; + aligned_data_buffer_index += 1; + } + } + // pad end + let mut dest_end_addr = dest_start_addr + aligned_data_buffer_index as u32; + if !util::is_aligned(dest_end_addr, 4) { + let offset = dest_end_addr % 4; + let num_pad_bytes_end = 4 - offset; + let padded_dest_end_addr = util::round_down(dest_end_addr, 4); + let memory_value = bus.bp_read32(padded_dest_end_addr).await; + let memory_value_bytes = memory_value.to_le_bytes(); + // Append the necessary memory bytes to pad the end of aligned_data_buffer + for i in offset..4 { + aligned_data_buffer[aligned_data_buffer_index] = memory_value_bytes[i as usize]; + aligned_data_buffer_index += 1; + } + dest_end_addr += num_pad_bytes_end; + } else { + // pad end alignment not needed + } + let buffer_to_write = &aligned_data_buffer[0..aligned_data_buffer_index as usize]; + assert!(dest_start_addr % 4 == 0); + assert!(dest_end_addr % 4 == 0); + assert!(aligned_data_buffer_index % 4 == 0); + bus.bp_write(dest_start_addr, buffer_to_write).await; + } + } + + pub(crate) async fn wait_bt_ready(&mut self, bus: &mut Bus) { + trace!("wait_bt_ready"); + let mut success = false; + for _ in 0..300 { + let val = bus.bp_read32(BT_CTRL_REG_ADDR).await; + trace!("BT_CTRL_REG_ADDR = {:08x}", val); + if val & BTSDIO_REG_FW_RDY_BITMASK != 0 { + success = true; + break; + } + Timer::after(Duration::from_millis(1)).await; + } + assert!(success == true); + } + + pub(crate) async fn wait_bt_awake(&mut self, bus: &mut Bus) { + trace!("wait_bt_awake"); + let mut success = false; + for _ in 0..300 { + let val = bus.bp_read32(BT_CTRL_REG_ADDR).await; + trace!("BT_CTRL_REG_ADDR = {:08x}", val); + if val & BTSDIO_REG_BT_AWAKE_BITMASK != 0 { + success = true; + break; + } + Timer::after(Duration::from_millis(1)).await; + } + assert!(success == true); + } + + pub(crate) async fn bt_set_host_ready(&mut self, bus: &mut Bus) { + trace!("bt_set_host_ready"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = old_val | BTSDIO_REG_SW_RDY_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + // TODO: use this + #[allow(dead_code)] + pub(crate) async fn bt_set_awake(&mut self, bus: &mut Bus, awake: bool) { + trace!("bt_set_awake"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = if awake { + old_val | BTSDIO_REG_WAKE_BT_BITMASK + } else { + old_val & !BTSDIO_REG_WAKE_BT_BITMASK + }; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + pub(crate) async fn bt_toggle_intr(&mut self, bus: &mut Bus) { + trace!("bt_toggle_intr"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + // TODO: do we need to swap endianness on this read? + let new_val = old_val ^ BTSDIO_REG_DATA_VALID_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + // TODO: use this + #[allow(dead_code)] + pub(crate) async fn bt_set_intr(&mut self, bus: &mut Bus) { + trace!("bt_set_intr"); + let old_val = bus.bp_read32(HOST_CTRL_REG_ADDR).await; + let new_val = old_val | BTSDIO_REG_DATA_VALID_BITMASK; + bus.bp_write32(HOST_CTRL_REG_ADDR, new_val).await; + } + + pub(crate) async fn init_bt_buffers(&mut self, bus: &mut Bus) { + trace!("init_bt_buffers"); + self.addr = bus.bp_read32(WLAN_RAM_BASE_REG_ADDR).await; + assert!(self.addr != 0); + trace!("wlan_ram_base_addr = {:08x}", self.addr); + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_IN, 0).await; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, 0).await; + } + + async fn bt_bus_request(&mut self, bus: &mut Bus) { + // TODO: CYW43_THREAD_ENTER mutex? + self.bt_set_awake(bus, true).await; + self.wait_bt_awake(bus).await; + } + + pub(crate) async fn hci_write(&mut self, bus: &mut Bus) { + self.bt_bus_request(bus).await; + + // NOTE(unwrap): we only call this when we do have a packet in the queue. + let buf = self.tx_chan.try_receive().unwrap(); + debug!("HCI tx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len])); + + let len = buf.len as u32 - 1; // len doesn't include hci type byte + let rounded_len = round_up(len, 4); + let total_len = 4 + rounded_len; + + let read_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_HOST2BT_OUT).await; + let available = read_pointer.wrapping_sub(self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE; + if available < total_len { + warn!( + "bluetooth tx queue full, retrying. len {} available {}", + total_len, available + ); + yield_now().await; + return; + } + + // Build header + let mut header = [0u8; 4]; + header[0] = len as u8; + header[1] = (len >> 8) as u8; + header[2] = (len >> 16) as u8; + header[3] = buf.buf[0]; // HCI type byte + + // Write header + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, &header).await; + self.h2b_write_pointer = (self.h2b_write_pointer + 4) % BTSDIO_FWBUF_SIZE; + + // Write payload. + let payload = &buf.buf[1..][..rounded_len as usize]; + if self.h2b_write_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize { + // wraparound + let n = BTSDIO_FWBUF_SIZE - self.h2b_write_pointer; + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, &payload[..n as usize]).await; + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF; + bus.bp_write(addr, &payload[n as usize..]).await; + } else { + // no wraparound + let addr = self.addr + BTSDIO_OFFSET_HOST_WRITE_BUF + self.h2b_write_pointer; + bus.bp_write(addr, payload).await; + } + self.h2b_write_pointer = (self.h2b_write_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE; + + // Update pointer. + bus.bp_write32(self.addr + BTSDIO_OFFSET_HOST2BT_IN, self.h2b_write_pointer) + .await; + + self.bt_toggle_intr(bus).await; + + self.tx_chan.receive_done(); + } + + async fn bt_has_work(&mut self, bus: &mut Bus) -> bool { + let int_status = bus.bp_read32(CHIP.sdiod_core_base_address + SDIO_INT_STATUS).await; + if int_status & I_HMB_FC_CHANGE != 0 { + bus.bp_write32( + CHIP.sdiod_core_base_address + SDIO_INT_STATUS, + int_status & I_HMB_FC_CHANGE, + ) + .await; + return true; + } + return false; + } + + pub(crate) async fn handle_irq(&mut self, bus: &mut Bus) { + if self.bt_has_work(bus).await { + loop { + // Check if we have data. + let write_pointer = bus.bp_read32(self.addr + BTSDIO_OFFSET_BT2HOST_IN).await; + let available = write_pointer.wrapping_sub(self.b2h_read_pointer) % BTSDIO_FWBUF_SIZE; + if available == 0 { + break; + } + + // read header + let mut header = [0u8; 4]; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, &mut header).await; + + // calc length + let len = header[0] as u32 | ((header[1]) as u32) << 8 | ((header[2]) as u32) << 16; + let rounded_len = round_up(len, 4); + if available < 4 + rounded_len { + warn!("ringbuf data not enough for a full packet?"); + break; + } + self.b2h_read_pointer = (self.b2h_read_pointer + 4) % BTSDIO_FWBUF_SIZE; + + // Obtain a buf from the channel. + let buf = self.rx_chan.send().await; + + buf.buf[0] = header[3]; // hci packet type + let payload = &mut buf.buf[1..][..rounded_len as usize]; + if self.b2h_read_pointer as usize + payload.len() > BTSDIO_FWBUF_SIZE as usize { + // wraparound + let n = BTSDIO_FWBUF_SIZE - self.b2h_read_pointer; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, &mut payload[..n as usize]).await; + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF; + bus.bp_read(addr, &mut payload[n as usize..]).await; + } else { + // no wraparound + let addr = self.addr + BTSDIO_OFFSET_HOST_READ_BUF + self.b2h_read_pointer; + bus.bp_read(addr, payload).await; + } + self.b2h_read_pointer = (self.b2h_read_pointer + payload.len() as u32) % BTSDIO_FWBUF_SIZE; + bus.bp_write32(self.addr + BTSDIO_OFFSET_BT2HOST_OUT, self.b2h_read_pointer) + .await; + + buf.len = 1 + len as usize; + debug!("HCI rx: {:02x}", crate::fmt::Bytes(&buf.buf[..buf.len])); + + self.rx_chan.send_done(); + + self.bt_toggle_intr(bus).await; + } + } + } +} + +impl<'d> embedded_io_async::ErrorType for BtDriver<'d> { + type Error = core::convert::Infallible; +} + +impl<'d> bt_hci::transport::Transport for BtDriver<'d> { + fn read<'a>(&self, rx: &'a mut [u8]) -> impl Future, Self::Error>> { + async { + let ch = &mut *self.rx.borrow_mut(); + let buf = ch.receive().await; + let n = buf.len; + assert!(n < rx.len()); + rx[..n].copy_from_slice(&buf.buf[..n]); + ch.receive_done(); + + let kind = PacketKind::from_hci_bytes_complete(&rx[..1]).unwrap(); + let (res, _) = ControllerToHostPacket::from_hci_bytes_with_kind(kind, &rx[1..n]).unwrap(); + Ok(res) + } + } + + /// Write a complete HCI packet from the tx buffer + fn write(&self, val: &T) -> impl Future> { + async { + let ch = &mut *self.tx.borrow_mut(); + let buf = ch.send().await; + let buf_len = buf.buf.len(); + let mut slice = &mut buf.buf[..]; + WithIndicator::new(val).write_hci(&mut slice).unwrap(); + buf.len = buf_len - slice.len(); + ch.send_done(); + Ok(()) + } + } +} diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs new file mode 100644 index 0000000..8a53484 --- /dev/null +++ b/cyw43/src/bus.rs @@ -0,0 +1,388 @@ +use embassy_futures::yield_now; +use embassy_time::Timer; +use embedded_hal_1::digital::OutputPin; +use futures::FutureExt; + +use crate::consts::*; +use crate::util::slice8_mut; + +/// Custom Spi Trait that _only_ supports the bus operation of the cyw43 +/// Implementors are expected to hold the CS pin low during an operation. +pub trait SpiBusCyw43 { + /// Issues a write command on the bus + /// First 32 bits of `word` are expected to be a cmd word + async fn cmd_write(&mut self, write: &[u32]) -> u32; + + /// Issues a read command on the bus + /// `write` is expected to be a 32 bit cmd word + /// `read` will contain the response of the device + /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. + /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32; + + /// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high. + /// The default implementation always reports ready, resulting in active polling of the device. + async fn wait_for_event(&mut self) { + yield_now().await; + } +} + +pub(crate) struct Bus { + backplane_window: u32, + pwr: PWR, + spi: SPI, + status: u32, +} + +impl Bus +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { + Self { + backplane_window: 0xAAAA_AAAA, + pwr, + spi, + status: 0, + } + } + + pub async fn init(&mut self, bluetooth_enabled: bool) { + // Reset + trace!("WL_REG off/on"); + self.pwr.set_low().unwrap(); + Timer::after_millis(20).await; + self.pwr.set_high().unwrap(); + Timer::after_millis(250).await; + + trace!("read REG_BUS_TEST_RO"); + while self + .read32_swapped(FUNC_BUS, REG_BUS_TEST_RO) + .inspect(|v| trace!("{:#x}", v)) + .await + != FEEDBEAD + {} + + trace!("write REG_BUS_TEST_RW"); + self.write32_swapped(FUNC_BUS, REG_BUS_TEST_RW, TEST_PATTERN).await; + let val = self.read32_swapped(FUNC_BUS, REG_BUS_TEST_RW).await; + trace!("{:#x}", val); + assert_eq!(val, TEST_PATTERN); + + trace!("read REG_BUS_CTRL"); + let val = self.read32_swapped(FUNC_BUS, REG_BUS_CTRL).await; + trace!("{:#010b}", (val & 0xff)); + + // 32-bit word length, little endian (which is the default endianess). + // TODO: C library is uint32_t val = WORD_LENGTH_32 | HIGH_SPEED_MODE| ENDIAN_BIG | INTERRUPT_POLARITY_HIGH | WAKE_UP | 0x4 << (8 * SPI_RESPONSE_DELAY) | INTR_WITH_STATUS << (8 * SPI_STATUS_ENABLE); + trace!("write REG_BUS_CTRL"); + self.write32_swapped( + FUNC_BUS, + REG_BUS_CTRL, + WORD_LENGTH_32 + | HIGH_SPEED + | INTERRUPT_POLARITY_HIGH + | WAKE_UP + | 0x4 << (8 * REG_BUS_RESPONSE_DELAY) + | STATUS_ENABLE << (8 * REG_BUS_STATUS_ENABLE) + | INTR_WITH_STATUS << (8 * REG_BUS_STATUS_ENABLE), + ) + .await; + + trace!("read REG_BUS_CTRL"); + let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; + trace!("{:#b}", val); + + // TODO: C doesn't do this? i doubt it messes anything up + trace!("read REG_BUS_TEST_RO"); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + trace!("{:#x}", val); + assert_eq!(val, FEEDBEAD); + + // TODO: C doesn't do this? i doubt it messes anything up + trace!("read REG_BUS_TEST_RW"); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + trace!("{:#x}", val); + assert_eq!(val, TEST_PATTERN); + + trace!("write SPI_RESP_DELAY_F1 CYW43_BACKPLANE_READ_PAD_LEN_BYTES"); + self.write8(FUNC_BUS, SPI_RESP_DELAY_F1, WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE) + .await; + + // TODO: Make sure error interrupt bits are clear? + // cyw43_write_reg_u8(self, BUS_FUNCTION, SPI_INTERRUPT_REGISTER, DATA_UNAVAILABLE | COMMAND_ERROR | DATA_ERROR | F1_OVERFLOW) != 0) + trace!("Make sure error interrupt bits are clear"); + self.write8( + FUNC_BUS, + REG_BUS_INTERRUPT, + (IRQ_DATA_UNAVAILABLE | IRQ_COMMAND_ERROR | IRQ_DATA_ERROR | IRQ_F1_OVERFLOW) as u8, + ) + .await; + + // Enable a selection of interrupts + // TODO: why not all of these F2_F3_FIFO_RD_UNDERFLOW | F2_F3_FIFO_WR_OVERFLOW | COMMAND_ERROR | DATA_ERROR | F2_PACKET_AVAILABLE | F1_OVERFLOW | F1_INTR + trace!("enable a selection of interrupts"); + let mut val = IRQ_F2_F3_FIFO_RD_UNDERFLOW + | IRQ_F2_F3_FIFO_WR_OVERFLOW + | IRQ_COMMAND_ERROR + | IRQ_DATA_ERROR + | IRQ_F2_PACKET_AVAILABLE + | IRQ_F1_OVERFLOW; + if bluetooth_enabled { + val = val | IRQ_F1_INTR; + } + self.write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, val).await; + } + + pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { + let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); + let len_in_u32 = (len_in_u8 as usize + 3) / 4; + + self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; + } + + pub async fn wlan_write(&mut self, buf: &[u32]) { + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + //TODO try to remove copy? + let mut cmd_buf = [0_u32; 513]; + cmd_buf[0] = cmd; + cmd_buf[1..][..buf.len()].copy_from_slice(buf); + + self.status = self.spi.cmd_write(&cmd_buf[..buf.len() + 1]).await; + } + + #[allow(unused)] + pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + trace!("bp_read addr = {:08x}", addr); + + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + // Backplane read buffer has one extra word for the response delay. + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + + // round `buf` to word boundary, add one extra word for the response delay + self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; + + // when writing out the data, we skip the response-delay byte + data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); + + // Advance ptr. + addr += len as u32; + data = &mut data[len..]; + } + } + + pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + trace!("bp_write addr = {:08x}", addr); + + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + buf[0] = cmd; + + self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; + + // Advance ptr. + addr += len as u32; + data = &data[len..]; + } + } + + pub async fn bp_read8(&mut self, addr: u32) -> u8 { + self.backplane_readn(addr, 1).await as u8 + } + + pub async fn bp_write8(&mut self, addr: u32, val: u8) { + self.backplane_writen(addr, val as u32, 1).await + } + + pub async fn bp_read16(&mut self, addr: u32) -> u16 { + self.backplane_readn(addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn bp_write16(&mut self, addr: u32, val: u16) { + self.backplane_writen(addr, val as u32, 2).await + } + + #[allow(unused)] + pub async fn bp_read32(&mut self, addr: u32) -> u32 { + self.backplane_readn(addr, 4).await + } + + pub async fn bp_write32(&mut self, addr: u32, val: u32) { + self.backplane_writen(addr, val, 4).await + } + + async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + trace!("backplane_readn addr = {:08x} len = {}", addr, len); + + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; + } + + let val = self.readn(FUNC_BACKPLANE, bus_addr, len).await; + + trace!("backplane_readn addr = {:08x} len = {} val = {:08x}", addr, len, val); + + return val; + } + + async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + trace!("backplane_writen addr = {:08x} len = {} val = {:08x}", addr, len, val); + + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG; + } + self.writen(FUNC_BACKPLANE, bus_addr, val, len).await; + } + + async fn backplane_set_window(&mut self, addr: u32) { + let new_window = addr & !BACKPLANE_ADDRESS_MASK; + + if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, + (new_window >> 24) as u8, + ) + .await; + } + if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_MID, + (new_window >> 16) as u8, + ) + .await; + } + if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, + (new_window >> 8) as u8, + ) + .await; + } + self.backplane_window = new_window; + } + + pub async fn read8(&mut self, func: u32, addr: u32) -> u8 { + self.readn(func, addr, 1).await as u8 + } + + pub async fn write8(&mut self, func: u32, addr: u32, val: u8) { + self.writen(func, addr, val as u32, 1).await + } + + pub async fn read16(&mut self, func: u32, addr: u32) -> u16 { + self.readn(func, addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn write16(&mut self, func: u32, addr: u32, val: u16) { + self.writen(func, addr, val as u32, 2).await + } + + pub async fn read32(&mut self, func: u32, addr: u32) -> u32 { + self.readn(func, addr, 4).await + } + + #[allow(unused)] + pub async fn write32(&mut self, func: u32, addr: u32, val: u32) { + self.writen(func, addr, val, 4).await + } + + async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, len); + let mut buf = [0; 2]; + // if we are reading from the backplane, we need an extra word for the response delay + let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; + + self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await; + + // if we read from the backplane, the result is in the second word, after the response delay + if func == FUNC_BACKPLANE { + buf[1] + } else { + buf[0] + } + } + + async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); + + self.status = self.spi.cmd_write(&[cmd, val]).await; + } + + async fn read32_swapped(&mut self, func: u32, addr: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, 4); + let cmd = swap16(cmd); + let mut buf = [0; 1]; + + self.status = self.spi.cmd_read(cmd, &mut buf).await; + + swap16(buf[0]) + } + + async fn write32_swapped(&mut self, func: u32, addr: u32, val: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, 4); + let buf = [swap16(cmd), swap16(val)]; + + self.status = self.spi.cmd_write(&buf).await; + } + + pub async fn wait_for_event(&mut self) { + self.spi.wait_for_event().await; + } + + pub fn status(&self) -> u32 { + self.status + } +} + +fn swap16(x: u32) -> u32 { + x.rotate_left(16) +} + +fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { + (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) +} diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs new file mode 100644 index 0000000..c3f0dbf --- /dev/null +++ b/cyw43/src/consts.rs @@ -0,0 +1,670 @@ +#![allow(unused)] + +pub(crate) const FUNC_BUS: u32 = 0; +pub(crate) const FUNC_BACKPLANE: u32 = 1; +pub(crate) const FUNC_WLAN: u32 = 2; +pub(crate) const FUNC_BT: u32 = 3; + +// Register addresses +pub(crate) const REG_BUS_CTRL: u32 = 0x0; +pub(crate) const REG_BUS_RESPONSE_DELAY: u32 = 0x1; +pub(crate) const REG_BUS_STATUS_ENABLE: u32 = 0x2; +pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status +pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask +pub(crate) const REG_BUS_STATUS: u32 = 0x8; +pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; +pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; +pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; + +// SPI_BUS_CONTROL Bits +pub(crate) const WORD_LENGTH_32: u32 = 0x1; +pub(crate) const ENDIAN_BIG: u32 = 0x2; +pub(crate) const CLOCK_PHASE: u32 = 0x4; +pub(crate) const CLOCK_POLARITY: u32 = 0x8; +pub(crate) const HIGH_SPEED: u32 = 0x10; +pub(crate) const INTERRUPT_POLARITY_HIGH: u32 = 0x20; +pub(crate) const WAKE_UP: u32 = 0x80; + +// SPI_STATUS_ENABLE bits +pub(crate) const STATUS_ENABLE: u32 = 0x01; +pub(crate) const INTR_WITH_STATUS: u32 = 0x02; +pub(crate) const RESP_DELAY_ALL: u32 = 0x04; +pub(crate) const DWORD_PKT_LEN_EN: u32 = 0x08; +pub(crate) const CMD_ERR_CHK_EN: u32 = 0x20; +pub(crate) const DATA_ERR_CHK_EN: u32 = 0x40; + +// SPI_STATUS_REGISTER bits +pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; +pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; +pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; +pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; +pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; +pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; +pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; +pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; +pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; +pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; +pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; +pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; +pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; +pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + +pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; +pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; +pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; +pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; +pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; +pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; +pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; +pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; +pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; +pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + +pub(crate) const I_HMB_SW_MASK: u32 = 0x000000f0; +pub(crate) const I_HMB_FC_CHANGE: u32 = 1 << 5; +pub(crate) const SDIO_INT_STATUS: u32 = 0x20; +pub(crate) const SDIO_INT_HOST_MASK: u32 = 0x24; + +pub(crate) const SPI_F2_WATERMARK: u8 = 0x20; + +pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; +pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; +pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; +pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Active Low Power (ALP) clock constants +pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; +pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; + +// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect +// (AI) pub (crate) constants +pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; +pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; +pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; +pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + +pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; +pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; + +pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; + +pub(crate) const TEST_PATTERN: u32 = 0x12345678; +pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; + +// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits +pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" +pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; +pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; +pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 +pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 +pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; +pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; +pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests +pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; +pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; +pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; +pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; +pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; +pub(crate) const IRQ_F1_INTR: u16 = 0x2000; +pub(crate) const IRQ_F2_INTR: u16 = 0x4000; +pub(crate) const IRQ_F3_INTR: u16 = 0x8000; + +pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; +pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; +pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; + +// CYW_SPID command structure constants. +pub(crate) const WRITE: bool = true; +pub(crate) const READ: bool = false; +pub(crate) const INC_ADDR: bool = true; +pub(crate) const FIXED_ADDR: bool = false; + +pub(crate) const AES_ENABLED: u32 = 0x0004; +pub(crate) const WPA2_SECURITY: u32 = 0x00400000; + +pub(crate) const MIN_PSK_LEN: usize = 8; +pub(crate) const MAX_PSK_LEN: usize = 64; + +// Bluetooth firmware extraction constants. +pub(crate) const BTFW_ADDR_MODE_UNKNOWN: i32 = 0; +pub(crate) const BTFW_ADDR_MODE_EXTENDED: i32 = 1; +pub(crate) const BTFW_ADDR_MODE_SEGMENT: i32 = 2; +pub(crate) const BTFW_ADDR_MODE_LINEAR32: i32 = 3; + +pub(crate) const BTFW_HEX_LINE_TYPE_DATA: u8 = 0; +pub(crate) const BTFW_HEX_LINE_TYPE_END_OF_DATA: u8 = 1; +pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_SEGMENT_ADDRESS: u8 = 2; +pub(crate) const BTFW_HEX_LINE_TYPE_EXTENDED_ADDRESS: u8 = 4; +pub(crate) const BTFW_HEX_LINE_TYPE_ABSOLUTE_32BIT_ADDRESS: u8 = 5; + +// Bluetooth constants. +pub(crate) const SPI_RESP_DELAY_F1: u32 = 0x001d; +pub(crate) const WHD_BUS_SPI_BACKPLANE_READ_PADD_SIZE: u8 = 4; + +pub(crate) const BT2WLAN_PWRUP_WAKE: u32 = 3; +pub(crate) const BT2WLAN_PWRUP_ADDR: u32 = 0x640894; + +pub(crate) const BT_CTRL_REG_ADDR: u32 = 0x18000c7c; +pub(crate) const HOST_CTRL_REG_ADDR: u32 = 0x18000d6c; +pub(crate) const WLAN_RAM_BASE_REG_ADDR: u32 = 0x18000d68; + +pub(crate) const BTSDIO_REG_DATA_VALID_BITMASK: u32 = 1 << 1; +pub(crate) const BTSDIO_REG_BT_AWAKE_BITMASK: u32 = 1 << 8; +pub(crate) const BTSDIO_REG_WAKE_BT_BITMASK: u32 = 1 << 17; +pub(crate) const BTSDIO_REG_SW_RDY_BITMASK: u32 = 1 << 24; +pub(crate) const BTSDIO_REG_FW_RDY_BITMASK: u32 = 1 << 24; + +pub(crate) const BTSDIO_FWBUF_SIZE: u32 = 0x1000; +pub(crate) const BTSDIO_OFFSET_HOST_WRITE_BUF: u32 = 0; +pub(crate) const BTSDIO_OFFSET_HOST_READ_BUF: u32 = BTSDIO_FWBUF_SIZE; + +pub(crate) const BTSDIO_OFFSET_HOST2BT_IN: u32 = 0x00002000; +pub(crate) const BTSDIO_OFFSET_HOST2BT_OUT: u32 = 0x00002004; +pub(crate) const BTSDIO_OFFSET_BT2HOST_IN: u32 = 0x00002008; +pub(crate) const BTSDIO_OFFSET_BT2HOST_OUT: u32 = 0x0000200C; + +// Security type (authentication and encryption types are combined using bit mask) +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, PartialEq)] +#[repr(u32)] +pub(crate) enum Security { + OPEN = 0, + WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED, +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum EStatus { + /// operation was successful + SUCCESS = 0, + /// operation failed + FAIL = 1, + /// operation timed out + TIMEOUT = 2, + /// failed due to no matching network found + NO_NETWORKS = 3, + /// operation was aborted + ABORT = 4, + /// protocol failure: packet not ack'd + NO_ACK = 5, + /// AUTH or ASSOC packet was unsolicited + UNSOLICITED = 6, + /// attempt to assoc to an auto auth configuration + ATTEMPT = 7, + /// scan results are incomplete + PARTIAL = 8, + /// scan aborted by another scan + NEWSCAN = 9, + /// scan aborted due to assoc in progress + NEWASSOC = 10, + /// 802.11h quiet period started + _11HQUIET = 11, + /// user disabled scanning (WLC_SET_SCANSUPPRESS) + SUPPRESS = 12, + /// no allowable channels to scan + NOCHANS = 13, + /// scan aborted due to CCX fast roam + CCXFASTRM = 14, + /// abort channel select + CS_ABORT = 15, +} + +impl PartialEq for u32 { + fn eq(&self, other: &EStatus) -> bool { + *self == *other as Self + } +} + +#[allow(dead_code)] +pub(crate) struct FormatStatus(pub u32); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatStatus { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[7..]); + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatStatus { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} + +#[allow(dead_code)] +pub(crate) struct FormatInterrupt(pub u16); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatInterrupt { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[4..]); + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatInterrupt { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatInterrupt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} + +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u32)] +pub(crate) enum Ioctl { + GetMagic = 0, + GetVersion = 1, + Up = 2, + Down = 3, + GetLoop = 4, + SetLoop = 5, + Dump = 6, + GetMsglevel = 7, + SetMsglevel = 8, + GetPromisc = 9, + SetPromisc = 10, + GetRate = 12, + GetInstance = 14, + GetInfra = 19, + SetInfra = 20, + GetAuth = 21, + SetAuth = 22, + GetBssid = 23, + SetBssid = 24, + GetSsid = 25, + SetSsid = 26, + Restart = 27, + GetChannel = 29, + SetChannel = 30, + GetSrl = 31, + SetSrl = 32, + GetLrl = 33, + SetLrl = 34, + GetPlcphdr = 35, + SetPlcphdr = 36, + GetRadio = 37, + SetRadio = 38, + GetPhytype = 39, + DumpRate = 40, + SetRateParams = 41, + GetKey = 44, + SetKey = 45, + GetRegulatory = 46, + SetRegulatory = 47, + GetPassiveScan = 48, + SetPassiveScan = 49, + Scan = 50, + ScanResults = 51, + Disassoc = 52, + Reassoc = 53, + GetRoamTrigger = 54, + SetRoamTrigger = 55, + GetRoamDelta = 56, + SetRoamDelta = 57, + GetRoamScanPeriod = 58, + SetRoamScanPeriod = 59, + Evm = 60, + GetTxant = 61, + SetTxant = 62, + GetAntdiv = 63, + SetAntdiv = 64, + GetClosed = 67, + SetClosed = 68, + GetMaclist = 69, + SetMaclist = 70, + GetRateset = 71, + SetRateset = 72, + Longtrain = 74, + GetBcnprd = 75, + SetBcnprd = 76, + GetDtimprd = 77, + SetDtimprd = 78, + GetSrom = 79, + SetSrom = 80, + GetWepRestrict = 81, + SetWepRestrict = 82, + GetCountry = 83, + SetCountry = 84, + GetPm = 85, + SetPm = 86, + GetWake = 87, + SetWake = 88, + GetForcelink = 90, + SetForcelink = 91, + FreqAccuracy = 92, + CarrierSuppress = 93, + GetPhyreg = 94, + SetPhyreg = 95, + GetRadioreg = 96, + SetRadioreg = 97, + GetRevinfo = 98, + GetUcantdiv = 99, + SetUcantdiv = 100, + RReg = 101, + WReg = 102, + GetMacmode = 105, + SetMacmode = 106, + GetMonitor = 107, + SetMonitor = 108, + GetGmode = 109, + SetGmode = 110, + GetLegacyErp = 111, + SetLegacyErp = 112, + GetRxAnt = 113, + GetCurrRateset = 114, + GetScansuppress = 115, + SetScansuppress = 116, + GetAp = 117, + SetAp = 118, + GetEapRestrict = 119, + SetEapRestrict = 120, + ScbAuthorize = 121, + ScbDeauthorize = 122, + GetWdslist = 123, + SetWdslist = 124, + GetAtim = 125, + SetAtim = 126, + GetRssi = 127, + GetPhyantdiv = 128, + SetPhyantdiv = 129, + ApRxOnly = 130, + GetTxPathPwr = 131, + SetTxPathPwr = 132, + GetWsec = 133, + SetWsec = 134, + GetPhyNoise = 135, + GetBssInfo = 136, + GetPktcnts = 137, + GetLazywds = 138, + SetLazywds = 139, + GetBandlist = 140, + GetBand = 141, + SetBand = 142, + ScbDeauthenticate = 143, + GetShortslot = 144, + GetShortslotOverride = 145, + SetShortslotOverride = 146, + GetShortslotRestrict = 147, + SetShortslotRestrict = 148, + GetGmodeProtection = 149, + GetGmodeProtectionOverride = 150, + SetGmodeProtectionOverride = 151, + Upgrade = 152, + GetIgnoreBcns = 155, + SetIgnoreBcns = 156, + GetScbTimeout = 157, + SetScbTimeout = 158, + GetAssoclist = 159, + GetClk = 160, + SetClk = 161, + GetUp = 162, + Out = 163, + GetWpaAuth = 164, + SetWpaAuth = 165, + GetUcflags = 166, + SetUcflags = 167, + GetPwridx = 168, + SetPwridx = 169, + GetTssi = 170, + GetSupRatesetOverride = 171, + SetSupRatesetOverride = 172, + GetProtectionControl = 178, + SetProtectionControl = 179, + GetPhylist = 180, + EncryptStrength = 181, + DecryptStatus = 182, + GetKeySeq = 183, + GetScanChannelTime = 184, + SetScanChannelTime = 185, + GetScanUnassocTime = 186, + SetScanUnassocTime = 187, + GetScanHomeTime = 188, + SetScanHomeTime = 189, + GetScanNprobes = 190, + SetScanNprobes = 191, + GetPrbRespTimeout = 192, + SetPrbRespTimeout = 193, + GetAtten = 194, + SetAtten = 195, + GetShmem = 196, + SetShmem = 197, + SetWsecTest = 200, + ScbDeauthenticateForReason = 201, + TkipCountermeasures = 202, + GetPiomode = 203, + SetPiomode = 204, + SetAssocPrefer = 205, + GetAssocPrefer = 206, + SetRoamPrefer = 207, + GetRoamPrefer = 208, + SetLed = 209, + GetLed = 210, + GetInterferenceMode = 211, + SetInterferenceMode = 212, + GetChannelQa = 213, + StartChannelQa = 214, + GetChannelSel = 215, + StartChannelSel = 216, + GetValidChannels = 217, + GetFakefrag = 218, + SetFakefrag = 219, + GetPwroutPercentage = 220, + SetPwroutPercentage = 221, + SetBadFramePreempt = 222, + GetBadFramePreempt = 223, + SetLeapList = 224, + GetLeapList = 225, + GetCwmin = 226, + SetCwmin = 227, + GetCwmax = 228, + SetCwmax = 229, + GetWet = 230, + SetWet = 231, + GetPub = 232, + GetKeyPrimary = 235, + SetKeyPrimary = 236, + GetAciArgs = 238, + SetAciArgs = 239, + UnsetCallback = 240, + SetCallback = 241, + GetRadar = 242, + SetRadar = 243, + SetSpectManagment = 244, + GetSpectManagment = 245, + WdsGetRemoteHwaddr = 246, + WdsGetWpaSup = 247, + SetCsScanTimer = 248, + GetCsScanTimer = 249, + MeasureRequest = 250, + Init = 251, + SendQuiet = 252, + Keepalive = 253, + SendPwrConstraint = 254, + UpgradeStatus = 255, + CurrentPwr = 256, + GetScanPassiveTime = 257, + SetScanPassiveTime = 258, + LegacyLinkBehavior = 259, + GetChannelsInCountry = 260, + GetCountryList = 261, + GetVar = 262, + SetVar = 263, + NvramGet = 264, + NvramSet = 265, + NvramDump = 266, + Reboot = 267, + SetWsecPmk = 268, + GetAuthMode = 269, + SetAuthMode = 270, + GetWakeentry = 271, + SetWakeentry = 272, + NdconfigItem = 273, + Nvotpw = 274, + Otpw = 275, + IovBlockGet = 276, + IovModulesGet = 277, + SoftReset = 278, + GetAllowMode = 279, + SetAllowMode = 280, + GetDesiredBssid = 281, + SetDesiredBssid = 282, + DisassocMyap = 283, + GetNbands = 284, + GetBandstates = 285, + GetWlcBssInfo = 286, + GetAssocInfo = 287, + GetOidPhy = 288, + SetOidPhy = 289, + SetAssocTime = 290, + GetDesiredSsid = 291, + GetChanspec = 292, + GetAssocState = 293, + SetPhyState = 294, + GetScanPending = 295, + GetScanreqPending = 296, + GetPrevRoamReason = 297, + SetPrevRoamReason = 298, + GetBandstatesPi = 299, + GetPhyState = 300, + GetBssWpaRsn = 301, + GetBssWpa2Rsn = 302, + GetBssBcnTs = 303, + GetIntDisassoc = 304, + SetNumPeers = 305, + GetNumBss = 306, + GetWsecPmk = 318, + GetRandomBytes = 319, +} + +pub(crate) const WSEC_TKIP: u32 = 0x02; +pub(crate) const WSEC_AES: u32 = 0x04; + +pub(crate) const AUTH_OPEN: u32 = 0x00; +pub(crate) const AUTH_SAE: u32 = 0x03; + +pub(crate) const MFP_NONE: u32 = 0; +pub(crate) const MFP_CAPABLE: u32 = 1; +pub(crate) const MFP_REQUIRED: u32 = 2; + +pub(crate) const WPA_AUTH_DISABLED: u32 = 0x0000; +pub(crate) const WPA_AUTH_WPA_PSK: u32 = 0x0004; +pub(crate) const WPA_AUTH_WPA2_PSK: u32 = 0x0080; +pub(crate) const WPA_AUTH_WPA3_SAE_PSK: u32 = 0x40000; diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs new file mode 100644 index 0000000..f77b487 --- /dev/null +++ b/cyw43/src/control.rs @@ -0,0 +1,745 @@ +use core::cmp::{max, min}; +use core::iter::zip; + +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; +use embassy_time::{Duration, Timer}; + +use crate::consts::*; +use crate::events::{Event, EventSubscriber, Events}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType}; +use crate::structs::*; +use crate::{countries, events, PowerManagementMode}; + +/// Control errors. +#[derive(Debug)] +pub struct Error { + /// Status code. + pub status: u32, +} + +/// Multicast errors. +#[derive(Debug)] +pub enum AddMulticastAddressError { + /// Not a multicast address. + NotMulticast, + /// No free address slots. + NoFreeSlots, +} + +/// Control driver. +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + events: &'a Events, + ioctl_state: &'a IoctlState, +} + +/// WiFi scan type. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ScanType { + /// Active scan: the station actively transmits probes that make APs respond. + /// Faster, but uses more power. + Active, + /// Passive scan: the station doesn't transmit any probes, just listens for beacons. + /// Slower, but uses less power. + Passive, +} + +/// Scan options. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ScanOptions { + /// SSID to scan for. + pub ssid: Option>, + /// If set to `None`, all APs will be returned. If set to `Some`, only APs + /// with the specified BSSID will be returned. + pub bssid: Option<[u8; 6]>, + /// Number of probes to send on each channel. + pub nprobes: Option, + /// Time to spend waiting on the home channel. + pub home_time: Option, + /// Scan type: active or passive. + pub scan_type: ScanType, + /// Period of time to wait on each channel when passive scanning. + pub dwell_time: Option, +} + +impl Default for ScanOptions { + fn default() -> Self { + Self { + ssid: None, + bssid: None, + nprobes: None, + home_time: None, + scan_type: ScanType::Passive, + dwell_time: None, + } + } +} + +/// Authentication type, used in [`JoinOptions::auth`]. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum JoinAuth { + /// Open network + Open, + /// WPA only + Wpa, + /// WPA2 only + Wpa2, + /// WPA3 only + Wpa3, + /// WPA2 + WPA3 + Wpa2Wpa3, +} + +/// Options for [`Control::join`]. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct JoinOptions<'a> { + /// Authentication type. Default `Wpa2Wpa3`. + pub auth: JoinAuth, + /// Enable TKIP encryption. Default false. + pub cipher_tkip: bool, + /// Enable AES encryption. Default true. + pub cipher_aes: bool, + /// Passphrase. Default empty. + pub passphrase: &'a [u8], + /// If false, `passphrase` is the human-readable passphrase string. + /// If true, `passphrase` is the result of applying the PBKDF2 hash to the + /// passphrase string. This makes it possible to avoid storing unhashed passwords. + /// + /// This is not compatible with WPA3. + /// Default false. + pub passphrase_is_prehashed: bool, +} + +impl<'a> JoinOptions<'a> { + /// Create a new `JoinOptions` for joining open networks. + pub fn new_open() -> Self { + Self { + auth: JoinAuth::Open, + cipher_tkip: false, + cipher_aes: false, + passphrase: &[], + passphrase_is_prehashed: false, + } + } + + /// Create a new `JoinOptions` for joining encrypted networks. + /// + /// Defaults to supporting WPA2+WPA3 with AES only, you may edit + /// the returned options to change this. + pub fn new(passphrase: &'a [u8]) -> Self { + let mut this = Self::default(); + this.passphrase = passphrase; + this + } +} + +impl<'a> Default for JoinOptions<'a> { + fn default() -> Self { + Self { + auth: JoinAuth::Wpa2Wpa3, + cipher_tkip: false, + cipher_aes: true, + passphrase: &[], + passphrase_is_prehashed: false, + } + } +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { + Self { + state_ch, + events: event_sub, + ioctl_state, + } + } + + async fn load_clm(&mut self, clm: &[u8]) { + const CHUNK_SIZE: usize = 1024; + + debug!("Downloading CLM..."); + + let mut offs = 0; + for chunk in clm.chunks(CHUNK_SIZE) { + let mut flag = DOWNLOAD_FLAG_HANDLER_VER; + if offs == 0 { + flag |= DOWNLOAD_FLAG_BEGIN; + } + offs += chunk.len(); + if offs == clm.len() { + flag |= DOWNLOAD_FLAG_END; + } + + let header = DownloadHeader { + flag, + dload_type: DOWNLOAD_TYPE_CLM, + len: chunk.len() as _, + crc: 0, + }; + let mut buf = [0; 8 + 12 + CHUNK_SIZE]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(&header.to_bytes()); + buf[20..][..chunk.len()].copy_from_slice(&chunk); + self.ioctl(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..8 + 12 + chunk.len()]) + .await; + } + + // check clmload ok + assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + } + + /// Initialize WiFi controller. + pub async fn init(&mut self, clm: &[u8]) { + self.load_clm(&clm).await; + + debug!("Configuring misc stuff..."); + + // Disable tx gloming which transfers multiple packets in one request. + // 'glom' is short for "conglomerate" which means "gather together into + // a compact mass". + self.set_iovar_u32("bus:txglom", 0).await; + self.set_iovar_u32("apsta", 1).await; + + // read MAC addr. + let mac_addr = self.address().await; + debug!("mac addr: {:02x}", Bytes(&mac_addr)); + + let country = countries::WORLD_WIDE_XX; + let country_info = CountryInfo { + country_abbrev: [country.code[0], country.code[1], 0, 0], + country_code: [country.code[0], country.code[1], 0, 0], + rev: if country.rev == 0 { -1 } else { country.rev as _ }, + }; + self.set_iovar("country", &country_info.to_bytes()).await; + + // set country takes some time, next ioctls fail if we don't wait. + Timer::after_millis(100).await; + + // Set antenna to chip antenna + self.ioctl_set_u32(Ioctl::SetAntdiv, 0, 0).await; + + self.set_iovar_u32("bus:txglom", 0).await; + Timer::after_millis(100).await; + //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + //Timer::after_millis(100).await; + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + Timer::after_millis(100).await; + self.set_iovar_u32("ampdu_mpdu", 4).await; + Timer::after_millis(100).await; + //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + //Timer::after_millis(100).await; + + // evts + let mut evts = EventMask { + iface: 0, + events: [0xFF; 24], + }; + + // Disable spammy uninteresting events. + evts.unset(Event::RADIO); + evts.unset(Event::IF); + evts.unset(Event::PROBREQ_MSG); + evts.unset(Event::PROBREQ_MSG_RX); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::ROAM); + + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + + Timer::after_millis(100).await; + + // set wifi up + self.up().await; + + Timer::after_millis(100).await; + + self.ioctl_set_u32(Ioctl::SetGmode, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(Ioctl::SetBand, 0, 0).await; // SET_BAND = any + + Timer::after_millis(100).await; + + self.state_ch.set_hardware_address(HardwareAddress::Ethernet(mac_addr)); + + debug!("cyw43 control init done"); + } + + /// Set the WiFi interface up. + async fn up(&mut self) { + self.ioctl(IoctlType::Set, Ioctl::Up, 0, &mut []).await; + } + + /// Set the interface down. + async fn down(&mut self) { + self.ioctl(IoctlType::Set, Ioctl::Down, 0, &mut []).await; + } + + /// Set power management mode. + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { + // power save mode + let mode_num = mode.mode(); + if mode_num == 2 { + self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; + self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; + self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; + self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; + } + self.ioctl_set_u32(Ioctl::SetPm, 0, mode_num).await; + } + + /// Join an unprotected network with the provided ssid. + pub async fn join(&mut self, ssid: &str, options: JoinOptions<'_>) -> Result<(), Error> { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + if options.auth == JoinAuth::Open { + self.ioctl_set_u32(Ioctl::SetWsec, 0, 0).await; + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, 0).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, WPA_AUTH_DISABLED).await; + } else { + let mut wsec = 0; + if options.cipher_aes { + wsec |= WSEC_AES; + } + if options.cipher_tkip { + wsec |= WSEC_TKIP; + } + self.ioctl_set_u32(Ioctl::SetWsec, 0, wsec).await; + + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after_millis(100).await; + + let (wpa12, wpa3, auth, mfp, wpa_auth) = match options.auth { + JoinAuth::Open => unreachable!(), + JoinAuth::Wpa => (true, false, AUTH_OPEN, MFP_NONE, WPA_AUTH_WPA_PSK), + JoinAuth::Wpa2 => (true, false, AUTH_OPEN, MFP_CAPABLE, WPA_AUTH_WPA2_PSK), + JoinAuth::Wpa3 => (false, true, AUTH_SAE, MFP_REQUIRED, WPA_AUTH_WPA3_SAE_PSK), + JoinAuth::Wpa2Wpa3 => (true, true, AUTH_SAE, MFP_CAPABLE, WPA_AUTH_WPA3_SAE_PSK), + }; + + if wpa12 { + let mut flags = 0; + if !options.passphrase_is_prehashed { + flags |= 1; + } + let mut pfi = PassphraseInfo { + len: options.passphrase.len() as _, + flags, + passphrase: [0; 64], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + if wpa3 { + let mut pfi = SaePassphraseInfo { + len: options.passphrase.len() as _, + passphrase: [0; 128], + }; + pfi.passphrase[..options.passphrase.len()].copy_from_slice(options.passphrase); + Timer::after_millis(3).await; + self.set_iovar("sae_password", &pfi.to_bytes()).await; + } + + self.ioctl_set_u32(Ioctl::SetInfra, 0, 1).await; + self.ioctl_set_u32(Ioctl::SetAuth, 0, auth).await; + self.set_iovar_u32("mfp", mfp).await; + self.ioctl_set_u32(Ioctl::SetWpaAuth, 0, wpa_auth).await; + } + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + self.wait_for_join(i).await + } + + async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { + self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); + let mut subscriber = self.events.queue.subscriber().unwrap(); + // the actual join operation starts here + // we make sure to enable events before so we don't miss any + + self.ioctl(IoctlType::Set, Ioctl::SetSsid, 0, &mut i.to_bytes()).await; + + // to complete the join, we wait for a SET_SSID event + // we also save the AUTH status for the user, it may be interesting + let mut auth_status = 0; + let status = loop { + let msg = subscriber.next_message_pure().await; + if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { + auth_status = msg.header.status; + } else if msg.header.event_type == Event::SET_SSID { + // join operation ends with SET_SSID event + break msg.header.status; + } + }; + + self.events.mask.disable_all(); + if status == EStatus::SUCCESS { + // successful join + self.state_ch.set_link_state(LinkState::Up); + debug!("JOINED"); + Ok(()) + } else { + warn!("JOIN failed with status={} auth={}", status, auth_status); + Err(Error { status }) + } + } + + /// Set GPIO pin on WiFi chip. + pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { + assert!(gpio_n < 3); + self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) + .await + } + + /// Start open access point. + pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + self.start_ap(ssid, "", Security::OPEN, channel).await; + } + + /// Start WPA2 protected access point. + pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { + self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; + } + + async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { + if security != Security::OPEN + && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) + { + panic!("Passphrase is too short or too long"); + } + + // Temporarily set wifi down + self.down().await; + + // Turn off APSTA mode + self.set_iovar_u32("apsta", 0).await; + + // Set wifi up again + self.up().await; + + // Turn on AP mode + self.ioctl_set_u32(Ioctl::SetAp, 0, 1).await; + + // Set SSID + let mut i = SsidInfoWithIndex { + index: 0, + ssid_info: SsidInfo { + len: ssid.as_bytes().len() as _, + ssid: [0; 32], + }, + }; + i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); + self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; + + // Set channel number + self.ioctl_set_u32(Ioctl::SetChannel, 0, channel as u32).await; + + // Set security + self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; + + if security != Security::OPEN { + self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK + + Timer::after_millis(100).await; + + // Set passphrase + let mut pfi = PassphraseInfo { + len: passphrase.as_bytes().len() as _, + flags: 1, // WSEC_PASSPHRASE + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, Ioctl::SetWsecPmk, 0, &mut pfi.to_bytes()) + .await; + } + + // Change mutlicast rate from 1 Mbps to 11 Mbps + self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; + + // Start AP + self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP + } + + /// Closes access point. + pub async fn close_ap(&mut self) { + // Stop AP + self.set_iovar_u32x2("bss", 0, 0).await; // bss = BSS_DOWN + + // Turn off AP mode + self.ioctl_set_u32(Ioctl::SetAp, 0, 0).await; + + // Temporarily set wifi down + self.down().await; + + // Turn on APSTA mode + self.set_iovar_u32("apsta", 1).await; + + // Set wifi up again + self.up().await; + } + + /// Add specified address to the list of hardware addresses the device + /// listens on. The address must be a Group address (I/G bit set). Up + /// to 10 addresses are supported by the firmware. Returns the number of + /// address slots filled after adding, or an error. + pub async fn add_multicast_address(&mut self, address: [u8; 6]) -> Result { + // The firmware seems to ignore non-multicast addresses, so let's + // prevent the user from adding them and wasting space. + if address[0] & 0x01 != 1 { + return Err(AddMulticastAddressError::NotMulticast); + } + + let mut buf = [0; 64]; + self.get_iovar("mcast_list", &mut buf).await; + + let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; + let (used, free) = buf[4..].split_at_mut(n * 6); + + if used.chunks(6).any(|a| a == address) { + return Ok(n); + } + + if free.len() < 6 { + return Err(AddMulticastAddressError::NoFreeSlots); + } + + free[..6].copy_from_slice(&address); + let n = n + 1; + buf[..4].copy_from_slice(&(n as u32).to_le_bytes()); + + self.set_iovar_v::<80>("mcast_list", &buf).await; + Ok(n) + } + + /// Retrieve the list of configured multicast hardware addresses. + pub async fn list_multicast_addresses(&mut self, result: &mut [[u8; 6]; 10]) -> usize { + let mut buf = [0; 64]; + self.get_iovar("mcast_list", &mut buf).await; + + let n = u32::from_le_bytes(buf[..4].try_into().unwrap()) as usize; + let used = &buf[4..][..n * 6]; + + for (addr, output) in zip(used.chunks(6), result.iter_mut()) { + output.copy_from_slice(addr) + } + + n + } + + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { + let mut buf = [0; 8]; + buf[0..4].copy_from_slice(&val1.to_le_bytes()); + buf[4..8].copy_from_slice(&val2.to_le_bytes()); + self.set_iovar(name, &buf).await + } + + async fn set_iovar_u32(&mut self, name: &str, val: u32) { + self.set_iovar(name, &val.to_le_bytes()).await + } + + async fn get_iovar_u32(&mut self, name: &str) -> u32 { + let mut buf = [0; 4]; + let len = self.get_iovar(name, &mut buf).await; + assert_eq!(len, 4); + u32::from_le_bytes(buf) + } + + async fn set_iovar(&mut self, name: &str, val: &[u8]) { + self.set_iovar_v::<196>(name, val).await + } + + async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { + debug!("iovar set {} = {:02x}", name, Bytes(val)); + + let mut buf = [0; BUFSIZE]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + buf[name.len() + 1..][..val.len()].copy_from_slice(val); + + let total_len = name.len() + 1 + val.len(); + self.ioctl_inner(IoctlType::Set, Ioctl::SetVar, 0, &mut buf[..total_len]) + .await; + } + + // TODO this is not really working, it always returns all zeros. + async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { + debug!("iovar get {}", name); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + + let total_len = max(name.len() + 1, res.len()); + let res_len = self + .ioctl_inner(IoctlType::Get, Ioctl::GetVar, 0, &mut buf[..total_len]) + .await; + + let out_len = min(res.len(), res_len); + res[..out_len].copy_from_slice(&buf[..out_len]); + out_len + } + + async fn ioctl_set_u32(&mut self, cmd: Ioctl, iface: u32, val: u32) { + let mut buf = val.to_le_bytes(); + self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; + } + + async fn ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + if kind == IoctlType::Set { + debug!("ioctl set {:?} iface {} = {:02x}", cmd, iface, Bytes(buf)); + } + let n = self.ioctl_inner(kind, cmd, iface, buf).await; + n + } + + async fn ioctl_inner(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + struct CancelOnDrop<'a>(&'a IoctlState); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.cancel_ioctl(); + } + } + + let ioctl = CancelOnDrop(self.ioctl_state); + let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await; + ioctl.defuse(); + + resp_len + } + + /// Start a wifi scan + /// + /// Returns a `Stream` of networks found by the device + /// + /// # Note + /// Device events are currently implemented using a bounded queue. + /// To not miss any events, you should make sure to always await the stream. + pub async fn scan(&mut self, scan_opts: ScanOptions) -> Scanner<'_> { + const SCANTYPE_ACTIVE: u8 = 0; + const SCANTYPE_PASSIVE: u8 = 1; + + let dwell_time = match scan_opts.dwell_time { + None => !0, + Some(t) => { + let mut t = t.as_millis() as u32; + if t == !0 { + t = !0 - 1; + } + t + } + }; + + let mut active_time = !0; + let mut passive_time = !0; + let scan_type = match scan_opts.scan_type { + ScanType::Active => { + active_time = dwell_time; + SCANTYPE_ACTIVE + } + ScanType::Passive => { + passive_time = dwell_time; + SCANTYPE_PASSIVE + } + }; + + let scan_params = ScanParams { + version: 1, + action: 1, + sync_id: 1, + ssid_len: scan_opts.ssid.as_ref().map(|e| e.as_bytes().len() as u32).unwrap_or(0), + ssid: scan_opts + .ssid + .map(|e| { + let mut ssid = [0; 32]; + ssid[..e.as_bytes().len()].copy_from_slice(e.as_bytes()); + ssid + }) + .unwrap_or([0; 32]), + bssid: scan_opts.bssid.unwrap_or([0xff; 6]), + bss_type: 2, + scan_type, + nprobes: scan_opts.nprobes.unwrap_or(!0).into(), + active_time, + passive_time, + home_time: scan_opts.home_time.map(|e| e.as_millis() as u32).unwrap_or(!0), + channel_num: 0, + channel_list: [0; 1], + }; + + self.events.mask.enable(&[Event::ESCAN_RESULT]); + let subscriber = self.events.queue.subscriber().unwrap(); + self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; + + Scanner { + subscriber, + events: &self.events, + } + } + /// Leave the wifi, with which we are currently associated. + pub async fn leave(&mut self) { + self.ioctl(IoctlType::Set, Ioctl::Disassoc, 0, &mut []).await; + info!("Disassociated") + } + + /// Gets the MAC address of the device + pub async fn address(&mut self) -> [u8; 6] { + let mut mac_addr = [0; 6]; + assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); + mac_addr + } +} + +/// WiFi network scanner. +pub struct Scanner<'a> { + subscriber: EventSubscriber<'a>, + events: &'a Events, +} + +impl Scanner<'_> { + /// Wait for the next found network. + pub async fn next(&mut self) -> Option { + let event = self.subscriber.next_message_pure().await; + if event.header.status != EStatus::PARTIAL { + self.events.mask.disable_all(); + return None; + } + + if let events::Payload::BssInfo(bss) = event.payload { + Some(bss) + } else { + None + } + } +} + +impl Drop for Scanner<'_> { + fn drop(&mut self) { + self.events.mask.disable_all(); + } +} diff --git a/cyw43/src/countries.rs b/cyw43/src/countries.rs new file mode 100644 index 0000000..fa1e8ca --- /dev/null +++ b/cyw43/src/countries.rs @@ -0,0 +1,481 @@ +#![allow(unused)] + +pub struct Country { + pub code: [u8; 2], + pub rev: u16, +} + +/// AF Afghanistan +pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 }; +/// AL Albania +pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 }; +/// DZ Algeria +pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 }; +/// AS American_Samoa +pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 }; +/// AO Angola +pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 }; +/// AI Anguilla +pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 }; +/// AG Antigua_and_Barbuda +pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 }; +/// AR Argentina +pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 }; +/// AM Armenia +pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 }; +/// AW Aruba +pub const ARUBA: Country = Country { code: *b"AW", rev: 0 }; +/// AU Australia +pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 }; +/// AT Austria +pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 }; +/// AZ Azerbaijan +pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 }; +/// BS Bahamas +pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 }; +/// BH Bahrain +pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 }; +/// 0B Baker_Island +pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 }; +/// BD Bangladesh +pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 }; +/// BB Barbados +pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 }; +/// BY Belarus +pub const BELARUS: Country = Country { code: *b"BY", rev: 0 }; +/// BE Belgium +pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 }; +/// BZ Belize +pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 }; +/// BJ Benin +pub const BENIN: Country = Country { code: *b"BJ", rev: 0 }; +/// BM Bermuda +pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 }; +/// BT Bhutan +pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 }; +/// BO Bolivia +pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 }; +/// BA Bosnia_and_Herzegovina +pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 }; +/// BW Botswana +pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 }; +/// BR Brazil +pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 }; +/// IO British_Indian_Ocean_Territory +pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 }; +/// BN Brunei_Darussalam +pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 }; +/// BG Bulgaria +pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 }; +/// BF Burkina_Faso +pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 }; +/// BI Burundi +pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 }; +/// KH Cambodia +pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 }; +/// CM Cameroon +pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 }; +/// CA Canada +pub const CANADA: Country = Country { code: *b"CA", rev: 0 }; +/// CA Canada Revision 950 +pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 }; +/// CV Cape_Verde +pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 }; +/// KY Cayman_Islands +pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 }; +/// CF Central_African_Republic +pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 }; +/// TD Chad +pub const CHAD: Country = Country { code: *b"TD", rev: 0 }; +/// CL Chile +pub const CHILE: Country = Country { code: *b"CL", rev: 0 }; +/// CN China +pub const CHINA: Country = Country { code: *b"CN", rev: 0 }; +/// CX Christmas_Island +pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 }; +/// CO Colombia +pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 }; +/// KM Comoros +pub const COMOROS: Country = Country { code: *b"KM", rev: 0 }; +/// CG Congo +pub const CONGO: Country = Country { code: *b"CG", rev: 0 }; +/// CD Congo,_The_Democratic_Republic_Of_The +pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 }; +/// CR Costa_Rica +pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 }; +/// CI Cote_D'ivoire +pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 }; +/// HR Croatia +pub const CROATIA: Country = Country { code: *b"HR", rev: 0 }; +/// CU Cuba +pub const CUBA: Country = Country { code: *b"CU", rev: 0 }; +/// CY Cyprus +pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 }; +/// CZ Czech_Republic +pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 }; +/// DK Denmark +pub const DENMARK: Country = Country { code: *b"DK", rev: 0 }; +/// DJ Djibouti +pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 }; +/// DM Dominica +pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 }; +/// DO Dominican_Republic +pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 }; +/// AU G'Day mate! +pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 }; +/// EC Ecuador +pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 }; +/// EG Egypt +pub const EGYPT: Country = Country { code: *b"EG", rev: 0 }; +/// SV El_Salvador +pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 }; +/// GQ Equatorial_Guinea +pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 }; +/// ER Eritrea +pub const ERITREA: Country = Country { code: *b"ER", rev: 0 }; +/// EE Estonia +pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 }; +/// ET Ethiopia +pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 }; +/// FK Falkland_Islands_(Malvinas) +pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 }; +/// FO Faroe_Islands +pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 }; +/// FJ Fiji +pub const FIJI: Country = Country { code: *b"FJ", rev: 0 }; +/// FI Finland +pub const FINLAND: Country = Country { code: *b"FI", rev: 0 }; +/// FR France +pub const FRANCE: Country = Country { code: *b"FR", rev: 0 }; +/// GF French_Guina +pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 }; +/// PF French_Polynesia +pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 }; +/// TF French_Southern_Territories +pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 }; +/// GA Gabon +pub const GABON: Country = Country { code: *b"GA", rev: 0 }; +/// GM Gambia +pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 }; +/// GE Georgia +pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 }; +/// DE Germany +pub const GERMANY: Country = Country { code: *b"DE", rev: 0 }; +/// E0 European_Wide Revision 895 +pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 }; +/// GH Ghana +pub const GHANA: Country = Country { code: *b"GH", rev: 0 }; +/// GI Gibraltar +pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 }; +/// GR Greece +pub const GREECE: Country = Country { code: *b"GR", rev: 0 }; +/// GD Grenada +pub const GRENADA: Country = Country { code: *b"GD", rev: 0 }; +/// GP Guadeloupe +pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 }; +/// GU Guam +pub const GUAM: Country = Country { code: *b"GU", rev: 0 }; +/// GT Guatemala +pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 }; +/// GG Guernsey +pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 }; +/// GN Guinea +pub const GUINEA: Country = Country { code: *b"GN", rev: 0 }; +/// GW Guinea-bissau +pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 }; +/// GY Guyana +pub const GUYANA: Country = Country { code: *b"GY", rev: 0 }; +/// HT Haiti +pub const HAITI: Country = Country { code: *b"HT", rev: 0 }; +/// VA Holy_See_(Vatican_City_State) +pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 }; +/// HN Honduras +pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 }; +/// HK Hong_Kong +pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 }; +/// HU Hungary +pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 }; +/// IS Iceland +pub const ICELAND: Country = Country { code: *b"IS", rev: 0 }; +/// IN India +pub const INDIA: Country = Country { code: *b"IN", rev: 0 }; +/// ID Indonesia +pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 }; +/// IR Iran,_Islamic_Republic_Of +pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 }; +/// IQ Iraq +pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 }; +/// IE Ireland +pub const IRELAND: Country = Country { code: *b"IE", rev: 0 }; +/// IL Israel +pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 }; +/// IT Italy +pub const ITALY: Country = Country { code: *b"IT", rev: 0 }; +/// JM Jamaica +pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 }; +/// JP Japan +pub const JAPAN: Country = Country { code: *b"JP", rev: 0 }; +/// JE Jersey +pub const JERSEY: Country = Country { code: *b"JE", rev: 0 }; +/// JO Jordan +pub const JORDAN: Country = Country { code: *b"JO", rev: 0 }; +/// KZ Kazakhstan +pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 }; +/// KE Kenya +pub const KENYA: Country = Country { code: *b"KE", rev: 0 }; +/// KI Kiribati +pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 }; +/// KR Korea,_Republic_Of +pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 }; +/// 0A Kosovo +pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 }; +/// KW Kuwait +pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 }; +/// KG Kyrgyzstan +pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 }; +/// LA Lao_People's_Democratic_Repubic +pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 }; +/// LV Latvia +pub const LATVIA: Country = Country { code: *b"LV", rev: 0 }; +/// LB Lebanon +pub const LEBANON: Country = Country { code: *b"LB", rev: 0 }; +/// LS Lesotho +pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 }; +/// LR Liberia +pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 }; +/// LY Libyan_Arab_Jamahiriya +pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 }; +/// LI Liechtenstein +pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 }; +/// LT Lithuania +pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 }; +/// LU Luxembourg +pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 }; +/// MO Macao +pub const MACAO: Country = Country { code: *b"MO", rev: 0 }; +/// MK Macedonia,_Former_Yugoslav_Republic_Of +pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 }; +/// MG Madagascar +pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 }; +/// MW Malawi +pub const MALAWI: Country = Country { code: *b"MW", rev: 0 }; +/// MY Malaysia +pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 }; +/// MV Maldives +pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 }; +/// ML Mali +pub const MALI: Country = Country { code: *b"ML", rev: 0 }; +/// MT Malta +pub const MALTA: Country = Country { code: *b"MT", rev: 0 }; +/// IM Man,_Isle_Of +pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 }; +/// MQ Martinique +pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 }; +/// MR Mauritania +pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 }; +/// MU Mauritius +pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 }; +/// YT Mayotte +pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 }; +/// MX Mexico +pub const MEXICO: Country = Country { code: *b"MX", rev: 0 }; +/// FM Micronesia,_Federated_States_Of +pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 }; +/// MD Moldova,_Republic_Of +pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 }; +/// MC Monaco +pub const MONACO: Country = Country { code: *b"MC", rev: 0 }; +/// MN Mongolia +pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 }; +/// ME Montenegro +pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 }; +/// MS Montserrat +pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 }; +/// MA Morocco +pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 }; +/// MZ Mozambique +pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 }; +/// MM Myanmar +pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 }; +/// NA Namibia +pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 }; +/// NR Nauru +pub const NAURU: Country = Country { code: *b"NR", rev: 0 }; +/// NP Nepal +pub const NEPAL: Country = Country { code: *b"NP", rev: 0 }; +/// NL Netherlands +pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 }; +/// AN Netherlands_Antilles +pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 }; +/// NC New_Caledonia +pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 }; +/// NZ New_Zealand +pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 }; +/// NI Nicaragua +pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 }; +/// NE Niger +pub const NIGER: Country = Country { code: *b"NE", rev: 0 }; +/// NG Nigeria +pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 }; +/// NF Norfolk_Island +pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 }; +/// MP Northern_Mariana_Islands +pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 }; +/// NO Norway +pub const NORWAY: Country = Country { code: *b"NO", rev: 0 }; +/// OM Oman +pub const OMAN: Country = Country { code: *b"OM", rev: 0 }; +/// PK Pakistan +pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 }; +/// PW Palau +pub const PALAU: Country = Country { code: *b"PW", rev: 0 }; +/// PA Panama +pub const PANAMA: Country = Country { code: *b"PA", rev: 0 }; +/// PG Papua_New_Guinea +pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 }; +/// PY Paraguay +pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 }; +/// PE Peru +pub const PERU: Country = Country { code: *b"PE", rev: 0 }; +/// PH Philippines +pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 }; +/// PL Poland +pub const POLAND: Country = Country { code: *b"PL", rev: 0 }; +/// PT Portugal +pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 }; +/// PR Pueto_Rico +pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 }; +/// QA Qatar +pub const QATAR: Country = Country { code: *b"QA", rev: 0 }; +/// RE Reunion +pub const REUNION: Country = Country { code: *b"RE", rev: 0 }; +/// RO Romania +pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 }; +/// RU Russian_Federation +pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 }; +/// RW Rwanda +pub const RWANDA: Country = Country { code: *b"RW", rev: 0 }; +/// KN Saint_Kitts_and_Nevis +pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 }; +/// LC Saint_Lucia +pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 }; +/// PM Saint_Pierre_and_Miquelon +pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 }; +/// VC Saint_Vincent_and_The_Grenadines +pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 }; +/// WS Samoa +pub const SAMOA: Country = Country { code: *b"WS", rev: 0 }; +/// MF Sanit_Martin_/_Sint_Marteen +pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 }; +/// ST Sao_Tome_and_Principe +pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 }; +/// SA Saudi_Arabia +pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 }; +/// SN Senegal +pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 }; +/// RS Serbia +pub const SERBIA: Country = Country { code: *b"RS", rev: 0 }; +/// SC Seychelles +pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 }; +/// SL Sierra_Leone +pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 }; +/// SG Singapore +pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 }; +/// SK Slovakia +pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 }; +/// SI Slovenia +pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 }; +/// SB Solomon_Islands +pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 }; +/// SO Somalia +pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 }; +/// ZA South_Africa +pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 }; +/// ES Spain +pub const SPAIN: Country = Country { code: *b"ES", rev: 0 }; +/// LK Sri_Lanka +pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 }; +/// SR Suriname +pub const SURINAME: Country = Country { code: *b"SR", rev: 0 }; +/// SZ Swaziland +pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 }; +/// SE Sweden +pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 }; +/// CH Switzerland +pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 }; +/// SY Syrian_Arab_Republic +pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 }; +/// TW Taiwan,_Province_Of_China +pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 }; +/// TJ Tajikistan +pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 }; +/// TZ Tanzania,_United_Republic_Of +pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 }; +/// TH Thailand +pub const THAILAND: Country = Country { code: *b"TH", rev: 0 }; +/// TG Togo +pub const TOGO: Country = Country { code: *b"TG", rev: 0 }; +/// TO Tonga +pub const TONGA: Country = Country { code: *b"TO", rev: 0 }; +/// TT Trinidad_and_Tobago +pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 }; +/// TN Tunisia +pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 }; +/// TR Turkey +pub const TURKEY: Country = Country { code: *b"TR", rev: 0 }; +/// TM Turkmenistan +pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 }; +/// TC Turks_and_Caicos_Islands +pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 }; +/// TV Tuvalu +pub const TUVALU: Country = Country { code: *b"TV", rev: 0 }; +/// UG Uganda +pub const UGANDA: Country = Country { code: *b"UG", rev: 0 }; +/// UA Ukraine +pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 }; +/// AE United_Arab_Emirates +pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 }; +/// GB United_Kingdom +pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 }; +/// US United_States +pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 }; +/// US United_States Revision 4 +pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 }; +/// Q1 United_States Revision 931 +pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 }; +/// Q2 United_States_(No_DFS) +pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 }; +/// UM United_States_Minor_Outlying_Islands +pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 }; +/// UY Uruguay +pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 }; +/// UZ Uzbekistan +pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 }; +/// VU Vanuatu +pub const VANUATU: Country = Country { code: *b"VU", rev: 0 }; +/// VE Venezuela +pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 }; +/// VN Viet_Nam +pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 }; +/// VG Virgin_Islands,_British +pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 }; +/// VI Virgin_Islands,_U.S. +pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 }; +/// WF Wallis_and_Futuna +pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 }; +/// 0C West_Bank +pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 }; +/// EH Western_Sahara +pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 }; +/// Worldwide Locale Revision 983 +pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 }; +/// Worldwide Locale (passive Ch12-14) +pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 }; +/// Worldwide Locale (passive Ch12-14) Revision 17 +pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 }; +/// YE Yemen +pub const YEMEN: Country = Country { code: *b"YE", rev: 0 }; +/// ZM Zambia +pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 }; +/// ZW Zimbabwe +pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 }; diff --git a/cyw43/src/events.rs b/cyw43/src/events.rs new file mode 100644 index 0000000..44bfa98 --- /dev/null +++ b/cyw43/src/events.rs @@ -0,0 +1,400 @@ +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pubsub::{PubSubChannel, Subscriber}; + +use crate::structs::BssInfo; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Event { + #[num_enum(default)] + Unknown = 0xFF, + /// indicates status of set SSID + SET_SSID = 0, + /// differentiates join IBSS from found (START) IBSS + JOIN = 1, + /// STA founded an IBSS or AP started a BSS + START = 2, + /// 802.11 AUTH request + AUTH = 3, + /// 802.11 AUTH indication + AUTH_IND = 4, + /// 802.11 DEAUTH request + DEAUTH = 5, + /// 802.11 DEAUTH indication + DEAUTH_IND = 6, + /// 802.11 ASSOC request + ASSOC = 7, + /// 802.11 ASSOC indication + ASSOC_IND = 8, + /// 802.11 REASSOC request + REASSOC = 9, + /// 802.11 REASSOC indication + REASSOC_IND = 10, + /// 802.11 DISASSOC request + DISASSOC = 11, + /// 802.11 DISASSOC indication + DISASSOC_IND = 12, + /// 802.11h Quiet period started + QUIET_START = 13, + /// 802.11h Quiet period ended + QUIET_END = 14, + /// BEACONS received/lost indication + BEACON_RX = 15, + /// generic link indication + LINK = 16, + /// TKIP MIC error occurred + MIC_ERROR = 17, + /// NDIS style link indication + NDIS_LINK = 18, + /// roam attempt occurred: indicate status & reason + ROAM = 19, + /// change in dot11FailedCount (txfail) + TXFAIL = 20, + /// WPA2 pmkid cache indication + PMKID_CACHE = 21, + /// current AP's TSF value went backward + RETROGRADE_TSF = 22, + /// AP was pruned from join list for reason + PRUNE = 23, + /// report AutoAuth table entry match for join attempt + AUTOAUTH = 24, + /// Event encapsulating an EAPOL message + EAPOL_MSG = 25, + /// Scan results are ready or scan was aborted + SCAN_COMPLETE = 26, + /// indicate to host addts fail/success + ADDTS_IND = 27, + /// indicate to host delts fail/success + DELTS_IND = 28, + /// indicate to host of beacon transmit + BCNSENT_IND = 29, + /// Send the received beacon up to the host + BCNRX_MSG = 30, + /// indicate to host loss of beacon + BCNLOST_MSG = 31, + /// before attempting to roam + ROAM_PREP = 32, + /// PFN network found event + PFN_NET_FOUND = 33, + /// PFN network lost event + PFN_NET_LOST = 34, + RESET_COMPLETE = 35, + JOIN_START = 36, + ROAM_START = 37, + ASSOC_START = 38, + IBSS_ASSOC = 39, + RADIO = 40, + /// PSM microcode watchdog fired + PSM_WATCHDOG = 41, + /// CCX association start + CCX_ASSOC_START = 42, + /// CCX association abort + CCX_ASSOC_ABORT = 43, + /// probe request received + PROBREQ_MSG = 44, + SCAN_CONFIRM_IND = 45, + /// WPA Handshake + PSK_SUP = 46, + COUNTRY_CODE_CHANGED = 47, + /// WMMAC excedded medium time + EXCEEDED_MEDIUM_TIME = 48, + /// WEP ICV error occurred + ICV_ERROR = 49, + /// Unsupported unicast encrypted frame + UNICAST_DECODE_ERROR = 50, + /// Unsupported multicast encrypted frame + MULTICAST_DECODE_ERROR = 51, + TRACE = 52, + /// BT-AMP HCI event + BTA_HCI_EVENT = 53, + /// I/F change (for wlan host notification) + IF = 54, + /// P2P Discovery listen state expires + P2P_DISC_LISTEN_COMPLETE = 55, + /// indicate RSSI change based on configured levels + RSSI = 56, + /// PFN best network batching event + PFN_BEST_BATCHING = 57, + EXTLOG_MSG = 58, + /// Action frame reception + ACTION_FRAME = 59, + /// Action frame Tx complete + ACTION_FRAME_COMPLETE = 60, + /// assoc request received + PRE_ASSOC_IND = 61, + /// re-assoc request received + PRE_REASSOC_IND = 62, + /// channel adopted (xxx: obsoleted) + CHANNEL_ADOPTED = 63, + /// AP started + AP_STARTED = 64, + /// AP stopped due to DFS + DFS_AP_STOP = 65, + /// AP resumed due to DFS + DFS_AP_RESUME = 66, + /// WAI stations event + WAI_STA_EVENT = 67, + /// event encapsulating an WAI message + WAI_MSG = 68, + /// escan result event + ESCAN_RESULT = 69, + /// action frame off channel complete + ACTION_FRAME_OFF_CHAN_COMPLETE = 70, + /// probe response received + PROBRESP_MSG = 71, + /// P2P Probe request received + P2P_PROBREQ_MSG = 72, + DCS_REQUEST = 73, + /// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM] + FIFO_CREDIT_MAP = 74, + /// Received action frame event WITH wl_event_rx_frame_data_t header + ACTION_FRAME_RX = 75, + /// Wake Event timer fired, used for wake WLAN test mode + WAKE_EVENT = 76, + /// Radio measurement complete + RM_COMPLETE = 77, + /// Synchronize TSF with the host + HTSFSYNC = 78, + /// request an overlay IOCTL/iovar from the host + OVERLAY_REQ = 79, + CSA_COMPLETE_IND = 80, + /// excess PM Wake Event to inform host + EXCESS_PM_WAKE_EVENT = 81, + /// no PFN networks around + PFN_SCAN_NONE = 82, + /// last found PFN network gets lost + PFN_SCAN_ALLGONE = 83, + GTK_PLUMBED = 84, + /// 802.11 ASSOC indication for NDIS only + ASSOC_IND_NDIS = 85, + /// 802.11 REASSOC indication for NDIS only + REASSOC_IND_NDIS = 86, + ASSOC_REQ_IE = 87, + ASSOC_RESP_IE = 88, + /// association recreated on resume + ASSOC_RECREATED = 89, + /// rx action frame event for NDIS only + ACTION_FRAME_RX_NDIS = 90, + /// authentication request received + AUTH_REQ = 91, + /// fast assoc recreation failed + SPEEDY_RECREATE_FAIL = 93, + /// port-specific event and payload (e.g. NDIS) + NATIVE = 94, + /// event for tx pkt delay suddently jump + PKTDELAY_IND = 95, + /// AWDL AW period starts + AWDL_AW = 96, + /// AWDL Master/Slave/NE master role event + AWDL_ROLE = 97, + /// Generic AWDL event + AWDL_EVENT = 98, + /// NIC AF txstatus + NIC_AF_TXS = 99, + /// NAN event + NAN = 100, + BEACON_FRAME_RX = 101, + /// desired service found + SERVICE_FOUND = 102, + /// GAS fragment received + GAS_FRAGMENT_RX = 103, + /// GAS sessions all complete + GAS_COMPLETE = 104, + /// New device found by p2p offload + P2PO_ADD_DEVICE = 105, + /// device has been removed by p2p offload + P2PO_DEL_DEVICE = 106, + /// WNM event to notify STA enter sleep mode + WNM_STA_SLEEP = 107, + /// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s) + TXFAIL_THRESH = 108, + /// Proximity Detection event + PROXD = 109, + /// AWDL RX Probe response + AWDL_RX_PRB_RESP = 111, + /// AWDL RX Action Frames + AWDL_RX_ACT_FRAME = 112, + /// AWDL Wowl nulls + AWDL_WOWL_NULLPKT = 113, + /// AWDL Phycal status + AWDL_PHYCAL_STATUS = 114, + /// AWDL OOB AF status + AWDL_OOB_AF_STATUS = 115, + /// Interleaved Scan status + AWDL_SCAN_STATUS = 116, + /// AWDL AW Start + AWDL_AW_START = 117, + /// AWDL AW End + AWDL_AW_END = 118, + /// AWDL AW Extensions + AWDL_AW_EXT = 119, + AWDL_PEER_CACHE_CONTROL = 120, + CSA_START_IND = 121, + CSA_DONE_IND = 122, + CSA_FAILURE_IND = 123, + /// CCA based channel quality report + CCA_CHAN_QUAL = 124, + /// to report change in BSSID while roaming + BSSID = 125, + /// tx error indication + TX_STAT_ERROR = 126, + /// credit check for BCMC supported + BCMC_CREDIT_SUPPORT = 127, + /// psta primary interface indication + PSTA_PRIMARY_INTF_IND = 128, + /// Handover Request Initiated + BT_WIFI_HANDOVER_REQ = 130, + /// Southpaw TxInhibit notification + SPW_TXINHIBIT = 131, + /// FBT Authentication Request Indication + FBT_AUTH_REQ_IND = 132, + /// Enhancement addition for RSSI + RSSI_LQM = 133, + /// Full probe/beacon (IEs etc) results + PFN_GSCAN_FULL_RESULT = 134, + /// Significant change in rssi of bssids being tracked + PFN_SWC = 135, + /// a STA been authroized for traffic + AUTHORIZED = 136, + /// probe req with wl_event_rx_frame_data_t header + PROBREQ_MSG_RX = 137, + /// PFN completed scan of network list + PFN_SCAN_COMPLETE = 138, + /// RMC Event + RMC_EVENT = 139, + /// DPSTA interface indication + DPSTA_INTF_IND = 140, + /// RRM Event + RRM = 141, + /// ULP entry event + ULP = 146, + /// TCP Keep Alive Offload Event + TKO = 151, + /// authentication request received + EXT_AUTH_REQ = 187, + /// authentication request received + EXT_AUTH_FRAME_RX = 188, + /// mgmt frame Tx complete + MGMT_FRAME_TXSTATUS = 189, + /// highest val + 1 for range checking + LAST = 190, +} + +// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. +pub type EventQueue = PubSubChannel; +pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; + +pub struct Events { + pub queue: EventQueue, + pub mask: SharedEventMask, +} + +impl Events { + pub fn new() -> Self { + Self { + queue: EventQueue::new(), + mask: SharedEventMask::default(), + } + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Status { + pub event_type: Event, + pub status: u32, +} + +#[derive(Copy, Clone)] +pub enum Payload { + None, + BssInfo(BssInfo), +} + +#[derive(Copy, Clone)] + +pub struct Message { + pub header: Status, + pub payload: Payload, +} + +impl Message { + pub fn new(status: Status, payload: Payload) -> Self { + Self { + header: status, + payload, + } + } +} + +#[derive(Default)] +struct EventMask { + mask: [u32; Self::WORD_COUNT], +} + +impl EventMask { + const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; + + fn enable(&mut self, event: Event) { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] |= 1 << bit; + } + + fn disable(&mut self, event: Event) { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] &= !(1 << bit); + } + + fn is_enabled(&self, event: Event) -> bool { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] & (1 << bit) > 0 + } +} + +#[derive(Default)] + +pub struct SharedEventMask { + mask: RefCell, +} + +impl SharedEventMask { + pub fn enable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.enable(*event); + } + } + + #[allow(dead_code)] + pub fn disable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.disable(*event); + } + } + + pub fn disable_all(&self) { + let mut mask = self.mask.borrow_mut(); + mask.mask = Default::default(); + } + + pub fn is_enabled(&self, event: Event) -> bool { + let mask = self.mask.borrow(); + mask.is_enabled(event) + } +} diff --git a/cyw43/src/fmt.rs b/cyw43/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/cyw43/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/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs new file mode 100644 index 0000000..af8bb69 --- /dev/null +++ b/cyw43/src/ioctl.rs @@ -0,0 +1,123 @@ +use core::cell::{Cell, RefCell}; +use core::future::{poll_fn, Future}; +use core::task::{Poll, Waker}; + +use embassy_sync::waitqueue::WakerRegistration; + +use crate::consts::Ioctl; +use crate::fmt::Bytes; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum IoctlType { + Get = 0, + Set = 2, +} + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub kind: IoctlType, + pub cmd: Ioctl, + pub iface: u32, +} + +#[derive(Clone, Copy)] +enum IoctlStateInner { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +struct Wakers { + control: WakerRegistration, + runner: WakerRegistration, +} + +impl Default for Wakers { + fn default() -> Self { + Self { + control: WakerRegistration::new(), + runner: WakerRegistration::new(), + } + } +} + +pub struct IoctlState { + state: Cell, + wakers: RefCell, +} + +impl IoctlState { + pub fn new() -> Self { + Self { + state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), + wakers: Default::default(), + } + } + + fn wake_control(&self) { + self.wakers.borrow_mut().control.wake(); + } + + fn register_control(&self, waker: &Waker) { + self.wakers.borrow_mut().control.register(waker); + } + + fn wake_runner(&self) { + self.wakers.borrow_mut().runner.wake(); + } + + fn register_runner(&self, waker: &Waker) { + self.wakers.borrow_mut().runner.register(waker); + } + + pub fn wait_complete(&self) -> impl Future + '_ { + poll_fn(|cx| { + if let IoctlStateInner::Done { resp_len } = self.state.get() { + Poll::Ready(resp_len) + } else { + self.register_control(cx.waker()); + Poll::Pending + } + }) + } + + pub fn wait_pending(&self) -> impl Future + '_ { + poll_fn(|cx| { + if let IoctlStateInner::Pending(pending) = self.state.get() { + self.state.set(IoctlStateInner::Sent { buf: pending.buf }); + Poll::Ready(pending) + } else { + self.register_runner(cx.waker()); + Poll::Pending + } + }) + } + + pub fn cancel_ioctl(&self) { + self.state.set(IoctlStateInner::Done { resp_len: 0 }); + } + + pub async fn do_ioctl(&self, kind: IoctlType, cmd: Ioctl, iface: u32, buf: &mut [u8]) -> usize { + self.state + .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); + self.wake_runner(); + self.wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + if let IoctlStateInner::Sent { buf } = self.state.get() { + trace!("IOCTL Response: {:02x}", Bytes(response)); + + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + self.state.set(IoctlStateInner::Done { + resp_len: response.len(), + }); + self.wake_control(); + } else { + warn!("IOCTL Response but no pending Ioctl"); + } + } +} diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs new file mode 100644 index 0000000..aab13d8 --- /dev/null +++ b/cyw43/src/lib.rs @@ -0,0 +1,299 @@ +#![no_std] +#![no_main] +#![allow(async_fn_in_trait)] +#![deny(unused_must_use)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "bluetooth")] +/// Bluetooth module. +pub mod bluetooth; +mod bus; +mod consts; +mod control; +mod countries; +mod events; +mod ioctl; +mod nvram; +mod runner; +mod structs; +mod util; + +use embassy_net_driver_channel as ch; +use embedded_hal_1::digital::OutputPin; +use events::Events; +use ioctl::IoctlState; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +pub use crate::control::{ + AddMulticastAddressError, Control, Error as ControlError, JoinAuth, JoinOptions, ScanOptions, ScanType, Scanner, +}; +pub use crate::runner::Runner; +pub use crate::structs::BssInfo; + +const MTU: usize = 1514; + +#[allow(unused)] +#[derive(Clone, Copy, PartialEq, Eq)] +enum Core { + WLAN = 0, + SOCSRAM = 1, + SDIOD = 2, +} + +impl Core { + fn base_addr(&self) -> u32 { + match self { + Self::WLAN => CHIP.arm_core_base_address, + Self::SOCSRAM => CHIP.socsram_wrapper_base_address, + Self::SDIOD => CHIP.sdiod_core_base_address, + } + } +} + +#[allow(unused)] +struct Chip { + arm_core_base_address: u32, + socsram_base_address: u32, + bluetooth_base_address: u32, + socsram_wrapper_base_address: u32, + sdiod_core_base_address: u32, + pmu_base_address: u32, + chip_ram_size: u32, + atcm_ram_base_address: u32, + socram_srmem_size: u32, + chanspec_band_mask: u32, + chanspec_band_2g: u32, + chanspec_band_5g: u32, + chanspec_band_shift: u32, + chanspec_bw_10: u32, + chanspec_bw_20: u32, + chanspec_bw_40: u32, + chanspec_bw_mask: u32, + chanspec_bw_shift: u32, + chanspec_ctl_sb_lower: u32, + chanspec_ctl_sb_upper: u32, + chanspec_ctl_sb_none: u32, + chanspec_ctl_sb_mask: u32, +} + +const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; + +// Data for CYW43439 +const CHIP: Chip = Chip { + arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET, + socsram_base_address: 0x18004000, + bluetooth_base_address: 0x19000000, + socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET, + sdiod_core_base_address: 0x18002000, + pmu_base_address: 0x18000000, + chip_ram_size: 512 * 1024, + atcm_ram_base_address: 0, + socram_srmem_size: 64 * 1024, + chanspec_band_mask: 0xc000, + chanspec_band_2g: 0x0000, + chanspec_band_5g: 0xc000, + chanspec_band_shift: 14, + chanspec_bw_10: 0x0800, + chanspec_bw_20: 0x1000, + chanspec_bw_40: 0x1800, + chanspec_bw_mask: 0x3800, + chanspec_bw_shift: 11, + chanspec_ctl_sb_lower: 0x0000, + chanspec_ctl_sb_upper: 0x0100, + chanspec_ctl_sb_none: 0x0000, + chanspec_ctl_sb_mask: 0x0700, +}; + +/// Driver state. +pub struct State { + ioctl_state: IoctlState, + net: NetState, + #[cfg(feature = "bluetooth")] + bt: bluetooth::BtState, +} + +struct NetState { + ch: ch::State, + events: Events, +} + +impl State { + /// Create new driver state holder. + pub fn new() -> Self { + Self { + ioctl_state: IoctlState::new(), + net: NetState { + ch: ch::State::new(), + events: Events::new(), + }, + #[cfg(feature = "bluetooth")] + bt: bluetooth::BtState::new(), + } + } +} + +/// Power management modes. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerManagementMode { + /// Custom, officially unsupported mode. Use at your own risk. + /// All power-saving features set to their max at only a marginal decrease in power consumption + /// as oppposed to `Aggressive`. + SuperSave, + + /// Aggressive power saving mode. + Aggressive, + + /// The default mode. + PowerSave, + + /// Performance is prefered over power consumption but still some power is conserved as opposed to + /// `None`. + Performance, + + /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of + /// a much lower throughput. + ThroughputThrottling, + + /// No power management is configured. This consumes the most power. + None, +} + +impl Default for PowerManagementMode { + fn default() -> Self { + Self::PowerSave + } +} + +impl PowerManagementMode { + fn sleep_ret_ms(&self) -> u16 { + match self { + PowerManagementMode::SuperSave => 2000, + PowerManagementMode::Aggressive => 2000, + PowerManagementMode::PowerSave => 200, + PowerManagementMode::Performance => 20, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn beacon_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn dtim_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn assoc(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 10, + PowerManagementMode::PowerSave => 10, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn mode(&self) -> u32 { + match self { + PowerManagementMode::ThroughputThrottling => 1, + PowerManagementMode::None => 0, + _ => 2, + } + } +} + +/// Embassy-net driver. +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +/// Create a new instance of the CYW43 driver. +/// +/// Returns a handle to the network device, control handle and a runner for driving the low level +/// stack. +pub async fn new<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + firmware: &[u8], +) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner::new( + ch_runner, + Bus::new(pwr, spi), + &state.ioctl_state, + &state.net.events, + #[cfg(feature = "bluetooth")] + None, + ); + + runner.init(firmware, None).await; + let control = Control::new(state_ch, &state.net.events, &state.ioctl_state); + + (device, control, runner) +} + +/// Create a new instance of the CYW43 driver. +/// +/// Returns a handle to the network device, control handle and a runner for driving the low level +/// stack. +#[cfg(feature = "bluetooth")] +pub async fn new_with_bluetooth<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + wifi_firmware: &[u8], + bluetooth_firmware: &[u8], +) -> ( + NetDriver<'a>, + bluetooth::BtDriver<'a>, + Control<'a>, + Runner<'a, PWR, SPI>, +) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.net.ch, ch::driver::HardwareAddress::Ethernet([0; 6])); + let state_ch = ch_runner.state_runner(); + + let (bt_runner, bt_driver) = bluetooth::new(&mut state.bt); + let mut runner = Runner::new( + ch_runner, + Bus::new(pwr, spi), + &state.ioctl_state, + &state.net.events, + #[cfg(feature = "bluetooth")] + Some(bt_runner), + ); + + runner.init(wifi_firmware, Some(bluetooth_firmware)).await; + let control = Control::new(state_ch, &state.net.events, &state.ioctl_state); + + (device, bt_driver, control, runner) +} diff --git a/cyw43/src/nvram.rs b/cyw43/src/nvram.rs new file mode 100644 index 0000000..3d1b535 --- /dev/null +++ b/cyw43/src/nvram.rs @@ -0,0 +1,48 @@ +pub static NVRAM: &'static [u8] = b" + NVRAMRev=$Rev$\x00\ + manfid=0x2d0\x00\ + prodid=0x0727\x00\ + vendid=0x14e4\x00\ + devid=0x43e2\x00\ + boardtype=0x0887\x00\ + boardrev=0x1100\x00\ + boardnum=22\x00\ + macaddr=00:A0:50:b5:59:5e\x00\ + sromrev=11\x00\ + boardflags=0x00404001\x00\ + boardflags3=0x04000000\x00\ + xtalfreq=37400\x00\ + nocrc=1\x00\ + ag0=255\x00\ + aa2g=1\x00\ + ccode=ALL\x00\ + pa0itssit=0x20\x00\ + extpagain2g=0\x00\ + pa2ga0=-168,6649,-778\x00\ + AvVmid_c0=0x0,0xc8\x00\ + cckpwroffset0=5\x00\ + maxp2ga0=84\x00\ + txpwrbckof=6\x00\ + cckbw202gpo=0\x00\ + legofdmbw202gpo=0x66111111\x00\ + mcsbw202gpo=0x77711111\x00\ + propbw202gpo=0xdd\x00\ + ofdmdigfilttype=18\x00\ + ofdmdigfilttypebe=18\x00\ + papdmode=1\x00\ + papdvalidtest=1\x00\ + pacalidx2g=45\x00\ + papdepsoffset=-30\x00\ + papdendidx=58\x00\ + ltecxmux=0\x00\ + ltecxpadnum=0x0102\x00\ + ltecxfnsel=0x44\x00\ + ltecxgcigpio=0x01\x00\ + il0macaddr=00:90:4c:c5:12:38\x00\ + wl0id=0x431b\x00\ + deadman_to=0xffffffff\x00\ + muxenab=0x100\x00\ + spurconfig=0x3\x00\ + glitch_based_crsmin=1\x00\ + btc_mode=1\x00\ + \x00"; diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs new file mode 100644 index 0000000..77910b2 --- /dev/null +++ b/cyw43/src/runner.rs @@ -0,0 +1,666 @@ +use embassy_futures::select::{select4, Either4}; +use embassy_net_driver_channel as ch; +use embassy_time::{block_for, Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{Event, Events, Status}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; +use crate::nvram::NVRAM; +use crate::structs::*; +use crate::util::slice8_mut; +use crate::{events, Core, CHIP, MTU}; + +#[cfg(feature = "firmware-logs")] +struct LogState { + addr: u32, + last_idx: usize, + buf: [u8; 256], + buf_count: usize, +} + +#[cfg(feature = "firmware-logs")] +impl Default for LogState { + fn default() -> Self { + Self { + addr: Default::default(), + last_idx: Default::default(), + buf: [0; 256], + buf_count: Default::default(), + } + } +} + +/// Driver communicating with the WiFi chip. +pub struct Runner<'a, PWR, SPI> { + ch: ch::Runner<'a, MTU>, + pub(crate) bus: Bus, + + ioctl_state: &'a IoctlState, + ioctl_id: u16, + sdpcm_seq: u8, + sdpcm_seq_max: u8, + + events: &'a Events, + + #[cfg(feature = "firmware-logs")] + log: LogState, + + #[cfg(feature = "bluetooth")] + pub(crate) bt: Option>, +} + +impl<'a, PWR, SPI> Runner<'a, PWR, SPI> +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new( + ch: ch::Runner<'a, MTU>, + bus: Bus, + ioctl_state: &'a IoctlState, + events: &'a Events, + #[cfg(feature = "bluetooth")] bt: Option>, + ) -> Self { + Self { + ch, + bus, + ioctl_state, + ioctl_id: 0, + sdpcm_seq: 0, + sdpcm_seq_max: 1, + events, + #[cfg(feature = "firmware-logs")] + log: LogState::default(), + #[cfg(feature = "bluetooth")] + bt, + } + } + + pub(crate) async fn init(&mut self, wifi_fw: &[u8], bt_fw: Option<&[u8]>) { + self.bus.init(bt_fw.is_some()).await; + + // Init ALP (Active Low Power) clock + debug!("init alp"); + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) + .await; + + debug!("set f2 watermark"); + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 0x10) + .await; + let watermark = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK).await; + debug!("watermark = {:02x}", watermark); + assert!(watermark == 0x10); + + debug!("waiting for clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} + debug!("clock ok"); + + // clear request for ALP + debug!("clear request for ALP"); + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0).await; + + let chip_id = self.bus.bp_read16(0x1800_0000).await; + debug!("chip ID: {}", chip_id); + + // Upload firmware. + self.core_disable(Core::WLAN).await; + self.core_disable(Core::SOCSRAM).await; // TODO: is this needed if we reset right after? + self.core_reset(Core::SOCSRAM).await; + + // this is 4343x specific stuff: Disable remap for SRAM_3 + self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; + + let ram_addr = CHIP.atcm_ram_base_address; + + debug!("loading fw"); + self.bus.bp_write(ram_addr, wifi_fw).await; + + debug!("loading nvram"); + // Round up to 4 bytes. + let nvram_len = (NVRAM.len() + 3) / 4 * 4; + self.bus + .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) + .await; + + let nvram_len_words = nvram_len as u32 / 4; + let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; + self.bus + .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) + .await; + + // Start core! + debug!("starting up core..."); + self.core_reset(Core::WLAN).await; + assert!(self.core_is_up(Core::WLAN).await); + + // wait until HT clock is available; takes about 29ms + debug!("wait for HT clock"); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + + // "Set up the interrupt mask and enable interrupts" + debug!("setup interrupt mask"); + self.bus + .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_SW_MASK) + .await; + + // Set up the interrupt mask and enable interrupts + if bt_fw.is_some() { + debug!("bluetooth setup interrupt mask"); + self.bus + .bp_write32(CHIP.sdiod_core_base_address + SDIO_INT_HOST_MASK, I_HMB_FC_CHANGE) + .await; + } + + self.bus + .write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE) + .await; + + // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." + // Sounds scary... + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, SPI_F2_WATERMARK) + .await; + + // wait for F2 to be ready + debug!("waiting for F2 to be ready..."); + while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} + + // Some random configs related to sleep. + // These aren't needed if we don't want to sleep the bus. + // TODO do we need to sleep the bus to read the irq line, due to + // being on the same pin as MOSI/MISO? + + /* + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; + val |= 0x02; // WAKE_TILL_HT_AVAIL + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; + self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT + + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; + val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; + */ + + // clear pulls + debug!("clear pad pulls"); + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; + let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; + + // start HT clock + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10) + .await; // SBSDIO_HT_AVAIL_REQ + debug!("waiting for HT clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + debug!("clock ok"); + + #[cfg(feature = "firmware-logs")] + self.log_init().await; + + #[cfg(feature = "bluetooth")] + if let Some(bt_fw) = bt_fw { + self.bt.as_mut().unwrap().init_bluetooth(&mut self.bus, bt_fw).await; + } + + debug!("cyw43 runner init done"); + } + + #[cfg(feature = "firmware-logs")] + async fn log_init(&mut self) { + // Initialize shared memory for logging. + + let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; + let shared_addr = self.bus.bp_read32(addr).await; + debug!("shared_addr {:08x}", shared_addr); + + let mut shared = [0; SharedMemData::SIZE]; + self.bus.bp_read(shared_addr, &mut shared).await; + let shared = SharedMemData::from_bytes(&shared); + + self.log.addr = shared.console_addr + 8; + } + + #[cfg(feature = "firmware-logs")] + async fn log_read(&mut self) { + // Read log struct + let mut log = [0; SharedMemLog::SIZE]; + self.bus.bp_read(self.log.addr, &mut log).await; + let log = SharedMemLog::from_bytes(&log); + + let idx = log.idx as usize; + + // If pointer hasn't moved, no need to do anything. + if idx == self.log.last_idx { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + let mut buf = [0; 0x400]; + self.bus.bp_read(log.buf, &mut buf).await; + + while self.log.last_idx != idx as usize { + let b = buf[self.log.last_idx]; + if b == b'\r' || b == b'\n' { + if self.log.buf_count != 0 { + let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; + debug!("LOGS: {}", s); + self.log.buf_count = 0; + } + } else if self.log.buf_count < self.log.buf.len() { + self.log.buf[self.log.buf_count] = b; + self.log.buf_count += 1; + } + + self.log.last_idx += 1; + if self.log.last_idx == 0x400 { + self.log.last_idx = 0; + } + } + } + + /// Run the CYW43 event handling loop. + pub async fn run(mut self) -> ! { + let mut buf = [0; 512]; + loop { + #[cfg(feature = "firmware-logs")] + self.log_read().await; + + if self.has_credit() { + let ioctl = self.ioctl_state.wait_pending(); + let wifi_tx = self.ch.tx_buf(); + #[cfg(feature = "bluetooth")] + let bt_tx = async { + match &mut self.bt { + Some(bt) => bt.tx_chan.receive().await, + None => core::future::pending().await, + } + }; + #[cfg(not(feature = "bluetooth"))] + let bt_tx = core::future::pending::<()>(); + + // interrupts aren't working yet for bluetooth. Do busy-polling instead. + // Note for this to work `ev` has to go last in the `select()`. It prefers + // first futures if they're ready, so other select branches don't get starved.` + #[cfg(feature = "bluetooth")] + let ev = core::future::ready(()); + #[cfg(not(feature = "bluetooth"))] + let ev = self.bus.wait_for_event(); + + match select4(ioctl, wifi_tx, bt_tx, ev).await { + Either4::First(PendingIoctl { + buf: iobuf, + kind, + cmd, + iface, + }) => { + self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }, &mut buf).await; + self.check_status(&mut buf).await; + } + Either4::Second(packet) => { + trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + let buf8 = slice8_mut(&mut buf); + + // There MUST be 2 bytes of padding between the SDPCM and BDC headers. + // And ONLY for data packets! + // No idea why, but the firmware will append two zero bytes to the tx'd packets + // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it + // be oversized and get dropped. + // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 + // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 + // ¯\_(ツ)_/¯ + const PADDING_SIZE: usize = 2; + let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); + + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: CHANNEL_TYPE_DATA, + next_length: 0, + header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bdc_header = BdcHeader { + flags: BDC_VERSION << BDC_VERSION_SHIFT, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] + .copy_from_slice(&bdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] + .copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..(total_len / 4)]).await; + self.ch.tx_done(); + self.check_status(&mut buf).await; + } + Either4::Third(_) => { + #[cfg(feature = "bluetooth")] + self.bt.as_mut().unwrap().hci_write(&mut self.bus).await; + } + Either4::Fourth(()) => { + self.handle_irq(&mut buf).await; + + // If we do busy-polling, make sure to yield. + // `handle_irq` will only do a 32bit read if there's no work to do, which is really fast. + // Depending on optimization level, it is possible that the 32-bit read finishes on + // first poll, so it never yields and we starve all other tasks. + #[cfg(feature = "bluetooth")] + embassy_futures::yield_now().await; + } + } + } else { + warn!("TX stalled"); + self.bus.wait_for_event().await; + self.handle_irq(&mut buf).await; + } + } + } + + /// Wait for IRQ on F2 packet available + async fn handle_irq(&mut self, buf: &mut [u32; 512]) { + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + if irq != 0 { + trace!("irq{}", FormatInterrupt(irq)); + } + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + self.check_status(buf).await; + } + + if irq & IRQ_DATA_UNAVAILABLE != 0 { + // this seems to be ignorable with no ill effects. + trace!("IRQ DATA_UNAVAILABLE, clearing..."); + self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; + } + + #[cfg(feature = "bluetooth")] + if let Some(bt) = &mut self.bt { + bt.handle_irq(&mut self.bus).await; + } + } + + /// Handle F2 events while status register is set + async fn check_status(&mut self, buf: &mut [u32; 512]) { + loop { + let status = self.bus.status(); + trace!("check status{}", FormatStatus(status)); + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); + self.rx(&mut slice8_mut(buf)[..len as usize]); + } else { + break; + } + } + } + + fn rx(&mut self, packet: &mut [u8]) { + let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { + return; + }; + + self.update_credit(&sdpcm_header); + + let channel = sdpcm_header.channel_and_flags & 0x0f; + + match channel { + CHANNEL_TYPE_CONTROL => { + let Some((cdc_header, response)) = CdcHeader::parse(payload) else { + return; + }; + trace!(" {:?}", cdc_header); + + if cdc_header.id == self.ioctl_id { + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {}", cdc_header.status as i32); + } + + self.ioctl_state.ioctl_done(response); + } + } + CHANNEL_TYPE_EVENT => { + let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { + warn!("BDC event, incomplete header"); + return; + }; + + let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { + warn!("BDC event, incomplete data"); + return; + }; + + const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h + if event_packet.eth.ether_type != ETH_P_LINK_CTL { + warn!( + "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", + event_packet.eth.ether_type, ETH_P_LINK_CTL + ); + return; + } + const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; + if event_packet.hdr.oui != BROADCOM_OUI { + warn!( + "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", + Bytes(&event_packet.hdr.oui), + Bytes(BROADCOM_OUI) + ); + return; + } + const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; + if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { + warn!("unexpected subtype {}", event_packet.hdr.subtype); + return; + } + + const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; + if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { + warn!("unexpected user_subtype {}", event_packet.hdr.subtype); + return; + } + + let evt_type = events::Event::from(event_packet.msg.event_type as u8); + debug!( + "=== EVENT {:?}: {:?} {:02x}", + evt_type, + event_packet.msg, + Bytes(evt_data) + ); + + if self.events.mask.is_enabled(evt_type) { + let status = event_packet.msg.status; + let event_payload = match evt_type { + Event::ESCAN_RESULT if status == EStatus::PARTIAL => { + let Some((_, bss_info)) = ScanResults::parse(evt_data) else { + return; + }; + let Some(bss_info) = BssInfo::parse(bss_info) else { + return; + }; + events::Payload::BssInfo(*bss_info) + } + Event::ESCAN_RESULT => events::Payload::None, + _ => events::Payload::None, + }; + + // this intentionally uses the non-blocking publish immediate + // publish() is a deadlock risk in the current design as awaiting here prevents ioctls + // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event + // (if they are actively awaiting the queue) + self.events + .queue + .immediate_publisher() + .publish_immediate(events::Message::new( + Status { + event_type: evt_type, + status, + }, + event_payload, + )); + } + } + CHANNEL_TYPE_DATA => { + let Some((_, packet)) = BdcHeader::parse(payload) else { + return; + }; + trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + match self.ch.try_rx_buf() { + Some(buf) => { + buf[..packet.len()].copy_from_slice(packet); + self.ch.rx_done(packet.len()) + } + None => warn!("failed to push rxd packet to the channel."), + } + } + _ => {} + } + } + + fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { + if sdpcm_header.channel_and_flags & 0xf < 3 { + let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; + if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { + sdpcm_seq_max = self.sdpcm_seq + 2; + } + self.sdpcm_seq_max = sdpcm_seq_max; + } + } + + fn has_credit(&self) -> bool { + self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 + } + + async fn send_ioctl(&mut self, kind: IoctlType, cmd: Ioctl, iface: u32, data: &[u8], buf: &mut [u32; 512]) { + let buf8 = slice8_mut(buf); + + let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + + let sdpcm_seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + self.ioctl_id = self.ioctl_id.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: sdpcm_seq, + channel_and_flags: CHANNEL_TYPE_CONTROL, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let cdc_header = CdcHeader { + cmd: cmd as u32, + len: data.len() as _, + flags: kind as u16 | (iface as u16) << 12, + id: self.ioctl_id, + status: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", cdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..total_len / 4]).await; + } + + async fn core_disable(&mut self, core: Core) { + let base = core.base_addr(); + + // Dummy read? + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + + // Check it isn't already reset + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & AI_RESETCTRL_BIT_RESET != 0 { + return; + } + + self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + block_for(Duration::from_millis(1)); + + self.bus + .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) + .await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + } + + async fn core_reset(&mut self, core: Core) { + self.core_disable(core).await; + + let base = core.base_addr(); + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; + + Timer::after_millis(1).await; + + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + Timer::after_millis(1).await; + } + + async fn core_is_up(&mut self, core: Core) -> bool { + let base = core.base_addr(); + + let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { + debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); + return false; + } + + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & (AI_RESETCTRL_BIT_RESET) != 0 { + debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); + return false; + } + + true + } +} diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs new file mode 100644 index 0000000..81ae6a9 --- /dev/null +++ b/cyw43/src/structs.rs @@ -0,0 +1,555 @@ +use crate::events::Event; +use crate::fmt::Bytes; + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + /// Bytes consumed by this type. + pub const SIZE: usize = core::mem::size_of::(); + + /// Convert to byte array. + #[allow(unused)] + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + /// Create from byte array. + #[allow(unused)] + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + unsafe { core::mem::transmute(bytes) } + } + + /// Create from mutable byte array. + #[allow(unused)] + pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + + unsafe { core::mem::transmute(bytes) } + } + } + }; +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemData { + pub flags: u32, + pub trap_addr: u32, + pub assert_exp_addr: u32, + pub assert_file_addr: u32, + pub assert_line: u32, + pub console_addr: u32, + pub msgtrace_addr: u32, + pub fwid: u32, +} +impl_bytes!(SharedMemData); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemLog { + pub buf: u32, + pub buf_size: u32, + pub idx: u32, + pub out_idx: u32, +} +impl_bytes!(SharedMemLog); + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SdpcmHeader { + pub len: u16, + pub len_inv: u16, + /// Rx/Tx sequence number + pub sequence: u8, + /// 4 MSB Channel number, 4 LSB arbitrary flag + pub channel_and_flags: u8, + /// Length of next data frame, reserved for Tx + pub next_length: u8, + /// Data offset + pub header_length: u8, + /// Flow control bits, reserved for Tx + pub wireless_flow_control: u8, + /// Maximum Sequence number allowed by firmware for Tx + pub bus_data_credit: u8, + /// Reserved + pub reserved: [u8; 2], +} +impl_bytes!(SdpcmHeader); + +impl SdpcmHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + let packet_len = packet.len(); + if packet_len < Self::SIZE { + warn!("packet too short, len={}", packet.len()); + return None; + } + let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE); + let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap()); + trace!("rx {:?}", sdpcm_header); + + if sdpcm_header.len != !sdpcm_header.len_inv { + warn!("len inv mismatch"); + return None; + } + + if sdpcm_header.len as usize != packet_len { + warn!("len from header doesn't match len from spi"); + return None; + } + + let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..]; + Some((sdpcm_header, sdpcm_packet)) + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed(2))] +pub struct CdcHeader { + pub cmd: u32, + pub len: u32, + pub flags: u16, + pub id: u16, + pub status: u32, +} +impl_bytes!(CdcHeader); + +#[cfg(feature = "defmt")] +impl defmt::Format for CdcHeader { + fn format(&self, fmt: defmt::Formatter) { + fn copy(t: T) -> T { + t + } + + defmt::write!( + fmt, + "CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}", + copy(self.cmd), + copy(self.len), + copy(self.flags), + copy(self.id), + copy(self.status), + ) + } +} + +impl CdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + warn!("payload too short, len={}", packet.len()); + return None; + } + + let (cdc_header, payload) = packet.split_at_mut(Self::SIZE); + let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap()); + + let payload = &mut payload[..cdc_header.len as usize]; + Some((cdc_header, payload)) + } +} + +pub const BDC_VERSION: u8 = 2; +pub const BDC_VERSION_SHIFT: u8 = 4; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct BdcHeader { + pub flags: u8, + /// 802.1d Priority (low 3 bits) + pub priority: u8, + pub flags2: u8, + /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. + pub data_offset: u8, +} +impl_bytes!(BdcHeader); + +impl BdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE); + let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap()); + trace!(" {:?}", bdc_header); + + let packet_start = 4 * bdc_header.data_offset as usize; + + let bdc_packet = bdc_packet.get_mut(packet_start..)?; + trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)])); + + Some((bdc_header, bdc_packet)) + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EthernetHeader { + pub destination_mac: [u8; 6], + pub source_mac: [u8; 6], + pub ether_type: u16, +} + +impl EthernetHeader { + /// Swap endianness. + pub fn byteswap(&mut self) { + self.ether_type = self.ether_type.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventHeader { + pub subtype: u16, + pub length: u16, + pub version: u8, + pub oui: [u8; 3], + pub user_subtype: u16, +} + +impl EventHeader { + pub fn byteswap(&mut self) { + self.subtype = self.subtype.to_be(); + self.length = self.length.to_be(); + self.user_subtype = self.user_subtype.to_be(); + } +} + +#[derive(Debug, Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct EventMessage { + /// version + pub version: u16, + /// see flags below + pub flags: u16, + /// Message (see below) + pub event_type: u32, + /// Status code (see below) + pub status: u32, + /// Reason code (if applicable) + pub reason: u32, + /// WLC_E_AUTH + pub auth_type: u32, + /// data buf + pub datalen: u32, + /// Station address (if applicable) + pub addr: [u8; 6], + /// name of the incoming packet interface + pub ifname: [u8; 16], + /// destination OS i/f index + pub ifidx: u8, + /// source bsscfg index + pub bsscfgidx: u8, +} +impl_bytes!(EventMessage); + +#[cfg(feature = "defmt")] +impl defmt::Format for EventMessage { + fn format(&self, fmt: defmt::Formatter) { + let event_type = self.event_type; + let status = self.status; + let reason = self.reason; + let auth_type = self.auth_type; + let datalen = self.datalen; + + defmt::write!( + fmt, + "EventMessage {{ \ + version: {=u16}, \ + flags: {=u16}, \ + event_type: {=u32}, \ + status: {=u32}, \ + reason: {=u32}, \ + auth_type: {=u32}, \ + datalen: {=u32}, \ + addr: {=[u8; 6]:x}, \ + ifname: {=[u8; 16]:x}, \ + ifidx: {=u8}, \ + bsscfgidx: {=u8}, \ + }} ", + self.version, + self.flags, + event_type, + status, + reason, + auth_type, + datalen, + self.addr, + self.ifname, + self.ifidx, + self.bsscfgidx + ); + } +} + +impl EventMessage { + pub fn byteswap(&mut self) { + self.version = self.version.to_be(); + self.flags = self.flags.to_be(); + self.event_type = self.event_type.to_be(); + self.status = self.status.to_be(); + self.reason = self.reason.to_be(); + self.auth_type = self.auth_type.to_be(); + self.datalen = self.datalen.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct EventPacket { + pub eth: EthernetHeader, + pub hdr: EventHeader, + pub msg: EventMessage, +} +impl_bytes!(EventPacket); + +impl EventPacket { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (event_header, event_packet) = packet.split_at_mut(Self::SIZE); + let event_header = Self::from_bytes_mut(event_header.try_into().unwrap()); + // warn!("event_header {:x}", event_header as *const _); + event_header.byteswap(); + + let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?; + + Some((event_header, event_packet)) + } + + pub fn byteswap(&mut self) { + self.eth.byteswap(); + self.hdr.byteswap(); + self.msg.byteswap(); + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct DownloadHeader { + pub flag: u16, // + pub dload_type: u16, + pub len: u32, + pub crc: u32, +} +impl_bytes!(DownloadHeader); + +#[allow(unused)] +pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001; +pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; +pub const DOWNLOAD_FLAG_END: u16 = 0x0004; +pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; + +// Country Locale Matrix (CLM) +pub const DOWNLOAD_TYPE_CLM: u16 = 2; + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct CountryInfo { + pub country_abbrev: [u8; 4], + pub rev: i32, + pub country_code: [u8; 4], +} +impl_bytes!(CountryInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfo { + pub len: u32, + pub ssid: [u8; 32], +} +impl_bytes!(SsidInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct PassphraseInfo { + pub len: u16, + pub flags: u16, + pub passphrase: [u8; 64], +} +impl_bytes!(PassphraseInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SaePassphraseInfo { + pub len: u16, + pub passphrase: [u8; 128], +} +impl_bytes!(SaePassphraseInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfoWithIndex { + pub index: u32, + pub ssid_info: SsidInfo, +} +impl_bytes!(SsidInfoWithIndex); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventMask { + pub iface: u32, + pub events: [u8; 24], +} +impl_bytes!(EventMask); + +impl EventMask { + pub fn unset(&mut self, evt: Event) { + let evt = evt as u8 as usize; + self.events[evt / 8] &= !(1 << (evt % 8)); + } +} + +/// Parameters for a wifi scan +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct ScanParams { + pub version: u32, + pub action: u16, + pub sync_id: u16, + pub ssid_len: u32, + pub ssid: [u8; 32], + pub bssid: [u8; 6], + pub bss_type: u8, + pub scan_type: u8, + pub nprobes: u32, + pub active_time: u32, + pub passive_time: u32, + pub home_time: u32, + pub channel_num: u32, + pub channel_list: [u16; 1], +} +impl_bytes!(ScanParams); + +/// Wifi Scan Results Header, followed by `bss_count` `BssInfo` +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct ScanResults { + pub buflen: u32, + pub version: u32, + pub sync_id: u16, + pub bss_count: u16, +} +impl_bytes!(ScanResults); + +impl ScanResults { + pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> { + if packet.len() < ScanResults::SIZE { + return None; + } + + let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); + let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); + + if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { + warn!("Scan result, incomplete BssInfo"); + return None; + } + + Some((scan_results, bssinfo)) + } +} + +/// Wifi Scan Result +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +#[non_exhaustive] +pub struct BssInfo { + /// Version. + pub version: u32, + /// Length. + pub length: u32, + /// BSSID. + pub bssid: [u8; 6], + /// Beacon period. + pub beacon_period: u16, + /// Capability. + pub capability: u16, + /// SSID length. + pub ssid_len: u8, + /// SSID. + pub ssid: [u8; 32], + reserved1: [u8; 1], + /// Number of rates in the rates field. + pub rateset_count: u32, + /// Rates in 500kpbs units. + pub rates: [u8; 16], + /// Channel specification. + pub chanspec: u16, + /// Announcement traffic indication message. + pub atim_window: u16, + /// Delivery traffic indication message. + pub dtim_period: u8, + reserved2: [u8; 1], + /// Receive signal strength (in dbM). + pub rssi: i16, + /// Received noise (in dbM). + pub phy_noise: i8, + /// 802.11n capability. + pub n_cap: u8, + reserved3: [u8; 2], + /// 802.11n BSS capabilities. + pub nbss_cap: u32, + /// 802.11n control channel number. + pub ctl_ch: u8, + reserved4: [u8; 3], + reserved32: [u32; 1], + /// Flags. + pub flags: u8, + /// VHT capability. + pub vht_cap: u8, + reserved5: [u8; 2], + /// 802.11n BSS required MCS. + pub basic_mcs: [u8; 16], + /// Information Elements (IE) offset. + pub ie_offset: u16, + /// Length of Information Elements (IE) in bytes. + pub ie_length: u32, + /// Average signal-to-noise (SNR) ratio during frame reception. + pub snr: i16, + // there will be more stuff here +} +impl_bytes!(BssInfo); + +impl BssInfo { + pub(crate) fn parse(packet: &mut [u8]) -> Option<&mut Self> { + if packet.len() < BssInfo::SIZE { + return None; + } + + Some(BssInfo::from_bytes_mut( + packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), + )) + } +} diff --git a/cyw43/src/util.rs b/cyw43/src/util.rs new file mode 100644 index 0000000..a4adbd4 --- /dev/null +++ b/cyw43/src/util.rs @@ -0,0 +1,20 @@ +#![allow(unused)] + +use core::slice; + +pub(crate) fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} + +pub(crate) fn is_aligned(a: u32, x: u32) -> bool { + (a & (x - 1)) == 0 +} + +pub(crate) fn round_down(x: u32, a: u32) -> u32 { + x & !(a - 1) +} + +pub(crate) fn round_up(x: u32, a: u32) -> u32 { + ((x + a - 1) / a) * a +} diff --git a/embassy-embedded-hal/CHANGELOG.md b/embassy-embedded-hal/CHANGELOG.md new file mode 100644 index 0000000..224036a --- /dev/null +++ b/embassy-embedded-hal/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog for embassy-embedded-hal + +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 + +## 0.3.0 - 2025-01-05 + +- The `std` feature has been removed +- Updated `embassy-time` to v0.4 + +## 0.2.0 - 2024-08-05 + +- Add Clone derive to flash Partition in embassy-embedded-hal +- Add support for all word sizes to async shared spi +- Add Copy and 'static constraint to Word type in SPI structs +- Improve flexibility by introducing SPI word size as a generic parameter +- Allow changing Spi/I2cDeviceWithConfig's config at runtime +- Impl `MultiwriteNorFlash` for `BlockingAsync` + +## 0.1.0 - 2024-01-10 + +- First release diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml new file mode 100644 index 0000000..f385963 --- /dev/null +++ b/embassy-embedded-hal/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "embassy-embedded-hal" +version = "0.3.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-embedded-hal" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/" +target = "x86_64-unknown-linux-gnu" + +[features] +time = ["dep:embassy-time"] +default = ["time"] + +[dependencies] +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-sync = { version = "0.6.2", path = "../embassy-sync" } +embassy-time = { version = "0.4.0", path = "../embassy-time", optional = true } +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-storage = "0.3.1" +embedded-storage-async = { version = "0.4.1" } +nb = "1.0.0" + +defmt = { version = "0.3", optional = true } + +[dev-dependencies] +critical-section = { version = "1.1.1", features = ["std"] } +futures-test = "0.3.17" diff --git a/embassy-embedded-hal/README.md b/embassy-embedded-hal/README.md new file mode 100644 index 0000000..69581c7 --- /dev/null +++ b/embassy-embedded-hal/README.md @@ -0,0 +1,12 @@ +# embassy-embedded-hal + +Collection of utilities to use `embedded-hal` and `embedded-storage` traits with Embassy. + +- Shared SPI and I2C buses, both blocking and async, with a `SetConfig` trait allowing changing bus configuration (e.g. frequency) between devices on the same bus. +- Async utilities + - Adapters to convert from blocking to (fake) async. + - Adapters to insert yields on trait operations. +- Flash utilities + - Split a flash memory into smaller partitions. + - Concatenate flash memories together. + - Simulated in-memory flash. diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs new file mode 100644 index 0000000..bafc315 --- /dev/null +++ b/embassy-embedded-hal/src/adapter/blocking_async.rs @@ -0,0 +1,149 @@ +use embedded_hal_02::blocking; + +/// Wrapper that implements async traits using blocking implementations. +/// +/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations. +/// +/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver. +/// +/// Driver users are then free to choose which implementation that is available to them. +pub struct BlockingAsync { + wrapped: T, +} + +impl BlockingAsync { + /// Create a new instance of a wrapper for a given peripheral. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementations +// +impl embedded_hal_1::i2c::ErrorType for BlockingAsync +where + E: embedded_hal_1::i2c::Error + 'static, + T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, +{ + type Error = E; +} + +impl embedded_hal_async::i2c::I2c for BlockingAsync +where + E: embedded_hal_1::i2c::Error + 'static, + T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, read) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, write) + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.write_read(address, write, read) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let _ = address; + let _ = operations; + todo!() + } +} + +// +// SPI implementatinos +// + +impl embedded_hal_async::spi::ErrorType for BlockingAsync +where + E: embedded_hal_1::spi::Error, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + type Error = E; +} + +impl embedded_hal_async::spi::SpiBus for BlockingAsync +where + E: embedded_hal_1::spi::Error + 'static, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(data)?; + Ok(()) + } + + async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + // Ensure we write the expected bytes + for i in 0..core::cmp::min(read.len(), write.len()) { + read[i] = write[i].clone(); + } + self.wrapped.transfer(read)?; + Ok(()) + } + + async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) + } +} + +/// NOR flash wrapper +use embedded_storage::nor_flash::{ErrorType, MultiwriteNorFlash, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{ + MultiwriteNorFlash as AsyncMultiwriteNorFlash, NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash, +}; + +impl ErrorType for BlockingAsync +where + T: ErrorType, +{ + type Error = T::Error; +} + +impl AsyncNorFlash for BlockingAsync +where + T: NorFlash, +{ + const WRITE_SIZE: usize = ::WRITE_SIZE; + const ERASE_SIZE: usize = ::ERASE_SIZE; + + async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(offset, data) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.wrapped.erase(from, to) + } +} + +impl AsyncReadNorFlash for BlockingAsync +where + T: ReadNorFlash, +{ + const READ_SIZE: usize = ::READ_SIZE; + async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, data) + } + + fn capacity(&self) -> usize { + self.wrapped.capacity() + } +} + +impl AsyncMultiwriteNorFlash for BlockingAsync where T: MultiwriteNorFlash {} diff --git a/embassy-embedded-hal/src/adapter/mod.rs b/embassy-embedded-hal/src/adapter/mod.rs new file mode 100644 index 0000000..28da561 --- /dev/null +++ b/embassy-embedded-hal/src/adapter/mod.rs @@ -0,0 +1,7 @@ +//! Adapters between embedded-hal traits. + +mod blocking_async; +mod yielding_async; + +pub use blocking_async::BlockingAsync; +pub use yielding_async::YieldingAsync; diff --git a/embassy-embedded-hal/src/adapter/yielding_async.rs b/embassy-embedded-hal/src/adapter/yielding_async.rs new file mode 100644 index 0000000..fe9c9c3 --- /dev/null +++ b/embassy-embedded-hal/src/adapter/yielding_async.rs @@ -0,0 +1,169 @@ +use embassy_futures::yield_now; + +/// Wrapper that yields for each operation to the wrapped instance +/// +/// This can be used in combination with BlockingAsync to enforce yields +/// between long running blocking operations. +pub struct YieldingAsync { + wrapped: T, +} + +impl YieldingAsync { + /// Create a new instance of a wrapper that yields after each operation. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementations +// +impl embedded_hal_1::i2c::ErrorType for YieldingAsync +where + T: embedded_hal_1::i2c::ErrorType, +{ + type Error = T::Error; +} + +impl embedded_hal_async::i2c::I2c for YieldingAsync +where + T: embedded_hal_async::i2c::I2c, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, read).await?; + yield_now().await; + Ok(()) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, write).await?; + yield_now().await; + Ok(()) + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.write_read(address, write, read).await?; + yield_now().await; + Ok(()) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.wrapped.transaction(address, operations).await?; + yield_now().await; + Ok(()) + } +} + +// +// SPI implementations +// + +impl embedded_hal_async::spi::ErrorType for YieldingAsync +where + T: embedded_hal_async::spi::ErrorType, +{ + type Error = T::Error; +} + +impl embedded_hal_async::spi::SpiBus for YieldingAsync +where + T: embedded_hal_async::spi::SpiBus, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + self.wrapped.flush().await?; + yield_now().await; + Ok(()) + } + + async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> { + self.wrapped.write(data).await?; + yield_now().await; + Ok(()) + } + + async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> { + self.wrapped.read(data).await?; + yield_now().await; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.wrapped.transfer(read, write).await?; + yield_now().await; + Ok(()) + } + + async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.wrapped.transfer_in_place(words).await?; + yield_now().await; + Ok(()) + } +} + +/// +/// NOR flash implementations +/// +impl embedded_storage::nor_flash::ErrorType for YieldingAsync { + type Error = T::Error; +} + +impl embedded_storage_async::nor_flash::ReadNorFlash + for YieldingAsync +{ + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(offset, bytes).await?; + Ok(()) + } + + fn capacity(&self) -> usize { + self.wrapped.capacity() + } +} + +impl embedded_storage_async::nor_flash::NorFlash for YieldingAsync { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(offset, bytes).await?; + yield_now().await; + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + // Yield between each actual erase + for from in (from..to).step_by(T::ERASE_SIZE) { + let to = core::cmp::min(from + T::ERASE_SIZE as u32, to); + self.wrapped.erase(from, to).await?; + yield_now().await; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage_async::nor_flash::NorFlash; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + let mut yielding = YieldingAsync::new(flash); + + yielding.erase(0, 256).await.unwrap(); + + let flash = yielding.wrapped; + assert_eq!(2, flash.erases.len()); + assert_eq!((0, 128), flash.erases[0]); + assert_eq!((128, 256), flash.erases[1]); + } +} diff --git a/embassy-embedded-hal/src/flash/concat_flash.rs b/embassy-embedded-hal/src/flash/concat_flash.rs new file mode 100644 index 0000000..499941d --- /dev/null +++ b/embassy-embedded-hal/src/flash/concat_flash.rs @@ -0,0 +1,225 @@ +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +/// Convenience helper for concatenating two consecutive flashes into one. +/// This is especially useful if used with "flash regions", where one may +/// want to concatenate multiple regions into one larger region. +pub struct ConcatFlash(First, Second); + +impl ConcatFlash { + /// Create a new flash that concatenates two consecutive flashes. + pub fn new(first: First, second: Second) -> Self { + Self(first, second) + } +} + +const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize { + if first_read_size != second_read_size { + panic!("The read size for the concatenated flashes must be the same"); + } + first_read_size +} + +const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize { + if first_write_size != second_write_size { + panic!("The write size for the concatenated flashes must be the same"); + } + first_write_size +} + +const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize { + let max_erase_size = if first_erase_size > second_erase_size { + first_erase_size + } else { + second_erase_size + }; + if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 { + panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size"); + } + max_erase_size +} + +impl ErrorType for ConcatFlash +where + First: ErrorType, + Second: ErrorType, + E: NorFlashError, +{ + type Error = E; +} + +impl ReadNorFlash for ConcatFlash +where + First: ReadNorFlash, + Second: ReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len])?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +impl NorFlash for ConcatFlash +where + First: NorFlash, + Second: NorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len])?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to)?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?; + } + + Ok(()) + } +} + +impl AsyncReadNorFlash for ConcatFlash +where + First: AsyncReadNorFlash, + Second: AsyncReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len]).await?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +impl AsyncNorFlash for ConcatFlash +where + First: AsyncNorFlash, + Second: AsyncNorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len]).await?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to).await?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32) + .await?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + + use super::ConcatFlash; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_write_and_read_across_flashes() { + let first = MemFlash::<64, 16, 4>::default(); + let second = MemFlash::<64, 64, 4>::default(); + let mut f = ConcatFlash::new(first, second); + + f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]); + assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]); + + let mut read_buf = [0; 8]; + f.read(60, &mut read_buf).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf); + } + + #[test] + fn can_erase_across_flashes() { + let first = MemFlash::<128, 16, 4>::new(0x00); + let second = MemFlash::<128, 64, 4>::new(0x00); + let mut f = ConcatFlash::new(first, second); + + f.erase(64, 192).unwrap(); + + assert_eq!(&[0x00; 64], &f.0.mem[0..64]); + assert_eq!(&[0xff; 64], &f.0.mem[64..128]); + assert_eq!(&[0xff; 64], &f.1.mem[0..64]); + assert_eq!(&[0x00; 64], &f.1.mem[64..128]); + } +} diff --git a/embassy-embedded-hal/src/flash/mem_flash.rs b/embassy-embedded-hal/src/flash/mem_flash.rs new file mode 100644 index 0000000..d24c618 --- /dev/null +++ b/embassy-embedded-hal/src/flash/mem_flash.rs @@ -0,0 +1,125 @@ +use alloc::vec::Vec; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +extern crate alloc; + +pub(crate) struct MemFlash { + pub mem: [u8; SIZE], + pub writes: Vec<(u32, usize)>, + pub erases: Vec<(u32, u32)>, +} + +impl MemFlash { + #[allow(unused)] + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + writes: Vec::new(), + erases: Vec::new(), + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + } + + fn write(&mut self, offset: u32, bytes: &[u8]) { + self.writes.push((offset, bytes.len())); + let offset = offset as usize; + assert_eq!(0, bytes.len() % WRITE_SIZE); + assert_eq!(0, offset % WRITE_SIZE); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + } + + fn erase(&mut self, from: u32, to: u32) { + self.erases.push((from, to)); + let from = from as usize; + let to = to as usize; + assert_eq!(0, from % ERASE_SIZE); + assert_eq!(0, to % ERASE_SIZE); + self.mem[from..to].fill(0xff); + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xff) + } +} + +impl ErrorType + for MemFlash +{ + type Error = core::convert::Infallible; +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} + +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} diff --git a/embassy-embedded-hal/src/flash/mod.rs b/embassy-embedded-hal/src/flash/mod.rs new file mode 100644 index 0000000..7e4ef29 --- /dev/null +++ b/embassy-embedded-hal/src/flash/mod.rs @@ -0,0 +1,8 @@ +//! Utilities related to flash. + +mod concat_flash; +#[cfg(test)] +pub(crate) mod mem_flash; +pub mod partition; + +pub use concat_flash::ConcatFlash; diff --git a/embassy-embedded-hal/src/flash/partition/asynch.rs b/embassy-embedded-hal/src/flash/partition/asynch.rs new file mode 100644 index 0000000..1b0c912 --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition/asynch.rs @@ -0,0 +1,149 @@ +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage::nor_flash::ErrorType; +use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash}; + +use super::Error; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct Partition<'a, M: RawMutex, T: NorFlash> { + flash: &'a Mutex, + offset: u32, + size: u32, +} + +impl<'a, M: RawMutex, T: NorFlash> Clone for Partition<'a, M, T> { + fn clone(&self) -> Self { + Self { + flash: self.flash, + offset: self.offset, + size: self.size, + } + } +} + +impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex, offset: u32, size: u32) -> Self { + if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 + { + panic!("Partition offset must be a multiple of read, write and erase size"); + } + if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { + panic!("Partition size must be a multiple of read, write and erase size"); + } + Self { flash, offset, size } + } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } +} + +impl ErrorType for Partition<'_, M, T> { + type Error = Error; +} + +impl ReadNorFlash for Partition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.read(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +impl NorFlash for Partition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.write(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash + .erase(self.offset + from, self.offset + to) + .await + .map_err(Error::Flash) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[132..132 + 8].fill(0xAA); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).await.unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + partition.erase(0, 128).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} diff --git a/embassy-embedded-hal/src/flash/partition/blocking.rs b/embassy-embedded-hal/src/flash/partition/blocking.rs new file mode 100644 index 0000000..a68df78 --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition/blocking.rs @@ -0,0 +1,159 @@ +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +use super::Error; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> { + flash: &'a Mutex>, + offset: u32, + size: u32, +} + +impl<'a, M: RawMutex, T: NorFlash> Clone for BlockingPartition<'a, M, T> { + fn clone(&self) -> Self { + Self { + flash: self.flash, + offset: self.offset, + size: self.size, + } + } +} + +impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex>, offset: u32, size: u32) -> Self { + if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 + { + panic!("Partition offset must be a multiple of read, write and erase size"); + } + if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { + panic!("Partition size must be a multiple of read, write and erase size"); + } + Self { flash, offset, size } + } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } +} + +impl ErrorType for BlockingPartition<'_, M, T> { + type Error = Error; +} + +impl ReadNorFlash for BlockingPartition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .read(self.offset + offset, bytes) + .map_err(Error::Flash) + }) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +impl NorFlash for BlockingPartition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .write(self.offset + offset, bytes) + .map_err(Error::Flash) + }) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .erase(self.offset + from, self.offset + to) + .map_err(Error::Flash) + }) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[132..132 + 8].fill(0xAA); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[test] + fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).unwrap(); + + let flash = flash.into_inner().take(); + assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); + } + + #[test] + fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + partition.erase(0, 128).unwrap(); + + let flash = flash.into_inner().take(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} diff --git a/embassy-embedded-hal/src/flash/partition/mod.rs b/embassy-embedded-hal/src/flash/partition/mod.rs new file mode 100644 index 0000000..6177ed9 --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition/mod.rs @@ -0,0 +1,28 @@ +//! Flash Partition utilities + +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +mod asynch; +mod blocking; + +pub use asynch::Partition; +pub use blocking::BlockingPartition; + +/// Partition error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The requested flash area is outside the partition + OutOfBounds, + /// Underlying flash error + Flash(T), +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Error::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Error::Flash(f) => f.kind(), + } + } +} diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs new file mode 100644 index 0000000..1db4fe0 --- /dev/null +++ b/embassy-embedded-hal/src/lib.rs @@ -0,0 +1,39 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +pub mod adapter; +pub mod flash; +pub mod shared_bus; + +/// Set the configuration of a peripheral driver. +/// +/// This trait is intended to be implemented by peripheral drivers such as SPI +/// and I2C. It allows changing the configuration at runtime. +/// +/// The exact type of the "configuration" is defined by each individual driver, since different +/// drivers support different options. Therefore it is defined as an associated type. +/// +/// For example, it is used by [`SpiDeviceWithConfig`](crate::shared_bus::asynch::spi::SpiDeviceWithConfig) and +/// [`I2cDeviceWithConfig`](crate::shared_bus::asynch::i2c::I2cDeviceWithConfig) to allow different +/// devices on the same bus to use different communication settings. +pub trait SetConfig { + /// The configuration type used by this driver. + type Config; + + /// The error type that can occur if `set_config` fails. + type ConfigError; + + /// Set the configuration of the driver. + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError>; +} + +/// Get the configuration of a peripheral driver. +pub trait GetConfig { + /// The configuration type used by this driver. + type Config; + + /// Get the configuration of the driver. + fn get_config(&self) -> Self::Config; +} diff --git a/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs b/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs new file mode 100644 index 0000000..71ce09d --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs @@ -0,0 +1,166 @@ +//! Asynchronous shared I2C bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice; +//! use embassy_sync::mutex::Mutex; +//! use embassy_sync::blocking_mutex::raw::NoopRawMutex; +//! +//! static I2C_BUS: StaticCell>> = StaticCell::new(); +//! let config = twim::Config::default(); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); +//! let i2c_bus = Mutex::new(i2c); +//! let i2c_bus = I2C_BUS.init(i2c_bus); +//! +//! // Device 1, using embedded-hal-async compatible driver for QMC5883L compass +//! let i2c_dev1 = I2cDevice::new(i2c_bus); +//! let compass = QMC5883L::new(i2c_dev1).await.unwrap(); +//! +//! // Device 2, using embedded-hal-async compatible driver for Mpu6050 accelerometer +//! let i2c_dev2 = I2cDevice::new(i2c_bus); +//! let mpu = Mpu6050::new(i2c_dev2); +//! ``` + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_hal_async::i2c; + +use crate::shared_bus::I2cDeviceError; +use crate::SetConfig; + +/// I2C device on a shared bus. +pub struct I2cDevice<'a, M: RawMutex, BUS> { + bus: &'a Mutex, +} + +impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> { + /// Create a new `I2cDevice`. + pub fn new(bus: &'a Mutex) -> Self { + Self { bus } + } +} + +impl<'a, M: RawMutex, BUS> i2c::ErrorType for I2cDevice<'a, M, BUS> +where + BUS: i2c::ErrorType, +{ + type Error = I2cDeviceError; +} + +impl i2c::I2c for I2cDevice<'_, M, BUS> +where + M: RawMutex + 'static, + BUS: i2c::I2c + 'static, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.read(address, read).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write(address, write).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write_read( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write_read(address, write, read) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_async::i2c::Operation<'_>], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.transaction(address, operations) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } +} + +/// I2C device on a shared bus, with its own configuration. +/// +/// This is like [`I2cDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> { + bus: &'a Mutex, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> { + /// Create a new `I2cDeviceWithConfig`. + pub fn new(bus: &'a Mutex, config: BUS::Config) -> Self { + Self { bus, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS> i2c::ErrorType for I2cDeviceWithConfig<'a, M, BUS> +where + BUS: i2c::ErrorType, + M: RawMutex, + BUS: SetConfig, +{ + type Error = I2cDeviceError; +} + +impl i2c::I2c for I2cDeviceWithConfig<'_, M, BUS> +where + M: RawMutex + 'static, + BUS: i2c::I2c + SetConfig + 'static, +{ + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn write_read( + &mut self, + address: u8, + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write_read(address, wr_buffer, rd_buffer) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } + + async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.transaction(address, operations) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) + } +} diff --git a/embassy-embedded-hal/src/shared_bus/asynch/mod.rs b/embassy-embedded-hal/src/shared_bus/asynch/mod.rs new file mode 100644 index 0000000..2e660b7 --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/asynch/mod.rs @@ -0,0 +1,3 @@ +//! Asynchronous shared bus implementations for embedded-hal-async +pub mod i2c; +pub mod spi; diff --git a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs new file mode 100644 index 0000000..78709b7 --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs @@ -0,0 +1,212 @@ +//! Asynchronous shared SPI bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::spi::SpiDevice; +//! use embassy_sync::mutex::Mutex; +//! use embassy_sync::blocking_mutex::raw::NoopRawMutex; +//! +//! static SPI_BUS: StaticCell>> = StaticCell::new(); +//! let mut config = spim::Config::default(); +//! config.frequency = spim::Frequency::M32; +//! let spi = spim::Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, config); +//! let spi_bus = Mutex::new(spi); +//! let spi_bus = SPI_BUS.init(spi_bus); +//! +//! // Device 1, using embedded-hal-async compatible driver for ST7735 LCD display +//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard); +//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1); +//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), 160, 128); +//! +//! // Device 2 +//! let cs_pin2 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard); +//! let spi_dev2 = SpiDevice::new(spi_bus, cs_pin2); +//! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128); +//! ``` + +use embassy_hal_internal::drop::OnDrop; +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::Operation; +use embedded_hal_async::spi; + +use crate::shared_bus::SpiDeviceError; +use crate::SetConfig; + +/// SPI device on a shared bus. +pub struct SpiDevice<'a, M: RawMutex, BUS, CS> { + bus: &'a Mutex, + cs: CS, +} + +impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> { + /// Create a new `SpiDevice`. + pub fn new(bus: &'a Mutex, cs: CS) -> Self { + Self { bus, cs } + } +} + +impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS> +where + BUS: spi::ErrorType, + CS: OutputPin, +{ + type Error = SpiDeviceError; +} + +impl spi::SpiDevice for SpiDevice<'_, M, BUS, CS> +where + M: RawMutex, + BUS: spi::SpiBus, + CS: OutputPin, + Word: Copy + 'static, +{ + async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + let mut bus = self.bus.lock().await; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let cs_drop = OnDrop::new(|| { + // This drop guard deasserts CS pin if the async operation is cancelled. + // Errors are ignored in this drop handler, as there's nothing we can do about them. + // If the async operation is completed without cancellation, this handler will not + // be run, and the CS pin will be deasserted with proper error handling. + let _ = self.cs.set_high(); + }); + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => bus.read(buf).await, + Operation::Write(buf) => bus.write(buf).await, + Operation::Transfer(read, write) => bus.transfer(read, write).await, + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await, + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => match bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + embassy_time::Timer::after_nanos(*ns as _).await; + Ok(()) + } + }, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + + // Now that all the async operations are done, we defuse the CS guard, + // and manually set the CS pin low (to better handle the possible errors). + cs_drop.defuse(); + let cs_res = self.cs.set_high(); + + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + + Ok(op_res) + } +} + +/// SPI device on a shared bus, with its own configuration. +/// +/// This is like [`SpiDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> { + bus: &'a Mutex, + cs: CS, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> { + /// Create a new `SpiDeviceWithConfig`. + pub fn new(bus: &'a Mutex, cs: CS, config: BUS::Config) -> Self { + Self { bus, cs, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS> +where + BUS: spi::ErrorType + SetConfig, + CS: OutputPin, + M: RawMutex, +{ + type Error = SpiDeviceError; +} + +impl spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> +where + M: RawMutex, + BUS: spi::SpiBus + SetConfig, + CS: OutputPin, + Word: Copy + 'static, +{ + async fn transaction(&mut self, operations: &mut [spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + let mut bus = self.bus.lock().await; + bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let cs_drop = OnDrop::new(|| { + // Please see comment in SpiDevice for an explanation of this drop handler. + let _ = self.cs.set_high(); + }); + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => bus.read(buf).await, + Operation::Write(buf) => bus.write(buf).await, + Operation::Transfer(read, write) => bus.transfer(read, write).await, + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await, + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => match bus.flush().await { + Err(e) => Err(e), + Ok(()) => { + embassy_time::Timer::after_nanos(*ns as _).await; + Ok(()) + } + }, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + cs_drop.defuse(); + let cs_res = self.cs.set_high(); + + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + + Ok(op_res) + } +} diff --git a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs new file mode 100644 index 0000000..627767c --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs @@ -0,0 +1,187 @@ +//! Blocking shared I2C bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; +//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; +//! +//! static I2C_BUS: StaticCell>>> = StaticCell::new(); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default()); +//! let i2c_bus = NoopMutex::new(RefCell::new(i2c)); +//! let i2c_bus = I2C_BUS.init(i2c_bus); +//! +//! let i2c_dev1 = I2cDevice::new(i2c_bus); +//! let mpu = Mpu6050::new(i2c_dev1); +//! ``` + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_hal_1::i2c::{ErrorType, I2c, Operation}; + +use crate::shared_bus::I2cDeviceError; +use crate::SetConfig; + +/// I2C device on a shared bus. +pub struct I2cDevice<'a, M: RawMutex, BUS> { + bus: &'a Mutex>, +} + +impl<'a, M: RawMutex, BUS> I2cDevice<'a, M, BUS> { + /// Create a new `I2cDevice`. + pub fn new(bus: &'a Mutex>) -> Self { + Self { bus } + } +} + +impl<'a, M: RawMutex, BUS> ErrorType for I2cDevice<'a, M, BUS> +where + BUS: ErrorType, +{ + type Error = I2cDeviceError; +} + +impl I2c for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: I2c, +{ + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().read(address, buffer).map_err(I2cDeviceError::I2c)) + } + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().write(address, bytes).map_err(I2cDeviceError::I2c)) + } + + fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + bus.borrow_mut() + .write_read(address, wr_buffer, rd_buffer) + .map_err(I2cDeviceError::I2c) + }) + } + + fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + bus.borrow_mut() + .transaction(address, operations) + .map_err(I2cDeviceError::I2c) + }) + } +} + +impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Write for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: embedded_hal_02::blocking::i2c::Write, +{ + type Error = I2cDeviceError; + + fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().write(addr, bytes).map_err(I2cDeviceError::I2c)) + } +} + +impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Read for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: embedded_hal_02::blocking::i2c::Read, +{ + type Error = I2cDeviceError; + + fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> Result<(), Self::Error> { + self.bus + .lock(|bus| bus.borrow_mut().read(addr, bytes).map_err(I2cDeviceError::I2c)) + } +} + +impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::WriteRead for I2cDevice<'_, M, BUS> +where + M: RawMutex, + BUS: embedded_hal_02::blocking::i2c::WriteRead, +{ + type Error = I2cDeviceError; + + fn write_read<'w>(&mut self, addr: u8, bytes: &'w [u8], buffer: &'w mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + bus.borrow_mut() + .write_read(addr, bytes, buffer) + .map_err(I2cDeviceError::I2c) + }) + } +} + +/// I2C device on a shared bus, with its own configuration. +/// +/// This is like [`I2cDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct I2cDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig> { + bus: &'a Mutex>, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig> I2cDeviceWithConfig<'a, M, BUS> { + /// Create a new `I2cDeviceWithConfig`. + pub fn new(bus: &'a Mutex>, config: BUS::Config) -> Self { + Self { bus, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS> ErrorType for I2cDeviceWithConfig<'a, M, BUS> +where + M: RawMutex, + BUS: ErrorType + SetConfig, +{ + type Error = I2cDeviceError; +} + +impl I2c for I2cDeviceWithConfig<'_, M, BUS> +where + M: RawMutex, + BUS: I2c + SetConfig, +{ + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.read(address, buffer).map_err(I2cDeviceError::I2c) + }) + } + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write(address, bytes).map_err(I2cDeviceError::I2c) + }) + } + + fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.write_read(address, wr_buffer, rd_buffer) + .map_err(I2cDeviceError::I2c) + }) + } + + fn transaction<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) -> Result<(), Self::Error> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| I2cDeviceError::Config)?; + bus.transaction(address, operations).map_err(I2cDeviceError::I2c) + }) + } +} diff --git a/embassy-embedded-hal/src/shared_bus/blocking/mod.rs b/embassy-embedded-hal/src/shared_bus/blocking/mod.rs new file mode 100644 index 0000000..c2063ed --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/blocking/mod.rs @@ -0,0 +1,3 @@ +//! Blocking shared bus implementations for embedded-hal +pub mod i2c; +pub mod spi; diff --git a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs new file mode 100644 index 0000000..eb9c0c4 --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs @@ -0,0 +1,167 @@ +//! Blocking shared SPI bus +//! +//! # Example (nrf52) +//! +//! ```rust,ignore +//! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; +//! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; +//! +//! static SPI_BUS: StaticCell>>> = StaticCell::new(); +//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default()); +//! let spi_bus = NoopMutex::new(RefCell::new(spi)); +//! let spi_bus = SPI_BUS.init(spi_bus); +//! +//! // Device 1, using embedded-hal compatible driver for ST7735 LCD display +//! let cs_pin1 = Output::new(p.P0_24, Level::Low, OutputDrive::Standard); +//! let spi_dev1 = SpiDevice::new(spi_bus, cs_pin1); +//! let display1 = ST7735::new(spi_dev1, dc1, rst1, Default::default(), false, 160, 128); +//! ``` + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::{self, Operation, SpiBus}; + +use crate::shared_bus::SpiDeviceError; +use crate::SetConfig; + +/// SPI device on a shared bus. +pub struct SpiDevice<'a, M: RawMutex, BUS, CS> { + bus: &'a Mutex>, + cs: CS, +} + +impl<'a, M: RawMutex, BUS, CS> SpiDevice<'a, M, BUS, CS> { + /// Create a new `SpiDevice`. + pub fn new(bus: &'a Mutex>, cs: CS) -> Self { + Self { bus, cs } + } +} + +impl<'a, M: RawMutex, BUS, CS> spi::ErrorType for SpiDevice<'a, M, BUS, CS> +where + BUS: spi::ErrorType, + CS: OutputPin, +{ + type Error = SpiDeviceError; +} + +impl embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS> +where + M: RawMutex, + BUS: SpiBus, + CS: OutputPin, + Word: Copy + 'static, +{ + fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => { + embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _)); + Ok(()) + } + }); + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush(); + let cs_res = self.cs.set_high(); + + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + + Ok(op_res) + }) + } +} + +/// SPI device on a shared bus, with its own configuration. +/// +/// This is like [`SpiDevice`], with an additional bus configuration that's applied +/// to the bus before each use using [`SetConfig`]. This allows different +/// devices on the same bus to use different communication settings. +pub struct SpiDeviceWithConfig<'a, M: RawMutex, BUS: SetConfig, CS> { + bus: &'a Mutex>, + cs: CS, + config: BUS::Config, +} + +impl<'a, M: RawMutex, BUS: SetConfig, CS> SpiDeviceWithConfig<'a, M, BUS, CS> { + /// Create a new `SpiDeviceWithConfig`. + pub fn new(bus: &'a Mutex>, cs: CS, config: BUS::Config) -> Self { + Self { bus, cs, config } + } + + /// Change the device's config at runtime + pub fn set_config(&mut self, config: BUS::Config) { + self.config = config; + } +} + +impl<'a, M, BUS, CS> spi::ErrorType for SpiDeviceWithConfig<'a, M, BUS, CS> +where + M: RawMutex, + BUS: spi::ErrorType + SetConfig, + CS: OutputPin, +{ + type Error = SpiDeviceError; +} + +impl embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> +where + M: RawMutex, + BUS: SpiBus + SetConfig, + CS: OutputPin, + Word: Copy + 'static, +{ + fn transaction(&mut self, operations: &mut [embedded_hal_1::spi::Operation<'_, Word>]) -> Result<(), Self::Error> { + if cfg!(not(feature = "time")) && operations.iter().any(|op| matches!(op, Operation::DelayNs(_))) { + return Err(SpiDeviceError::DelayNotSupported); + } + + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus.set_config(&self.config).map_err(|_| SpiDeviceError::Config)?; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; + + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + #[cfg(not(feature = "time"))] + Operation::DelayNs(_) => unreachable!(), + #[cfg(feature = "time")] + Operation::DelayNs(ns) => { + embassy_time::block_for(embassy_time::Duration::from_nanos(*ns as _)); + Ok(()) + } + }); + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush(); + let cs_res = self.cs.set_high(); + + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; + Ok(op_res) + }) + } +} diff --git a/embassy-embedded-hal/src/shared_bus/mod.rs b/embassy-embedded-hal/src/shared_bus/mod.rs new file mode 100644 index 0000000..d835306 --- /dev/null +++ b/embassy-embedded-hal/src/shared_bus/mod.rs @@ -0,0 +1,59 @@ +//! Shared bus implementations +use core::fmt::Debug; + +use embedded_hal_1::{i2c, spi}; + +pub mod asynch; +pub mod blocking; + +/// Error returned by I2C device implementations in this crate. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum I2cDeviceError { + /// An operation on the inner I2C bus failed. + I2c(BUS), + /// Configuration of the inner I2C bus failed. + Config, +} + +impl i2c::Error for I2cDeviceError +where + BUS: i2c::Error + Debug, +{ + fn kind(&self) -> i2c::ErrorKind { + match self { + Self::I2c(e) => e.kind(), + Self::Config => i2c::ErrorKind::Other, + } + } +} + +/// Error returned by SPI device implementations in this crate. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum SpiDeviceError { + /// An operation on the inner SPI bus failed. + Spi(BUS), + /// Setting the value of the Chip Select (CS) pin failed. + Cs(CS), + /// Delay operations are not supported when the `time` Cargo feature is not enabled. + DelayNotSupported, + /// The SPI bus could not be configured. + Config, +} + +impl spi::Error for SpiDeviceError +where + BUS: spi::Error + Debug, + CS: Debug, +{ + fn kind(&self) -> spi::ErrorKind { + match self { + Self::Spi(e) => e.kind(), + Self::Cs(_) => spi::ErrorKind::Other, + Self::DelayNotSupported => spi::ErrorKind::Other, + Self::Config => spi::ErrorKind::Other, + } + } +} diff --git a/embassy-executor-macros/Cargo.toml b/embassy-executor-macros/Cargo.toml new file mode 100644 index 0000000..5a38f2f --- /dev/null +++ b/embassy-executor-macros/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "embassy-executor-macros" +version = "0.6.2" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "macros for creating the entry point and tasks for embassy-executor" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-executor-macros" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[dependencies] +syn = { version = "2.0.15", features = ["full", "visit"] } +quote = "1.0.9" +darling = "0.20.1" +proc-macro2 = "1.0.29" + +[lib] +proc-macro = true + +[features] +nightly = [] diff --git a/embassy-executor-macros/README.md b/embassy-executor-macros/README.md new file mode 100644 index 0000000..3a8d49a --- /dev/null +++ b/embassy-executor-macros/README.md @@ -0,0 +1,5 @@ +# embassy-executor-macros + +An [Embassy](https://embassy.dev) project. + +NOTE: Do not use this crate directly. The macros are re-exported by `embassy-executor`. diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs new file mode 100644 index 0000000..5f2182f --- /dev/null +++ b/embassy-executor-macros/src/lib.rs @@ -0,0 +1,175 @@ +#![doc = include_str!("../README.md")] +extern crate proc_macro; + +use proc_macro::TokenStream; + +mod macros; +mod util; +use macros::*; + +/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how +/// many concurrent tasks can be spawned (default is 1) for the function. +/// +/// +/// The following restrictions apply: +/// +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * The optional `pool_size` attribute must be 1 or greater. +/// +/// +/// ## Examples +/// +/// Declaring a task taking no arguments: +/// +/// ``` rust +/// #[embassy_executor::task] +/// async fn mytask() { +/// // Function body +/// } +/// ``` +/// +/// Declaring a task with a given pool size: +/// +/// ``` rust +/// #[embassy_executor::task(pool_size = 4)] +/// async fn mytask() { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { + task::run(args.into(), item.into()).into() +} + +#[proc_macro_attribute] +pub fn main_avr(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_AVR).into() +} + +/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_CORTEX_M).into() +} + +/// Creates a new `executor` instance and declares an architecture agnostic application entry point spawning +/// the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro must provided via the `entry` argument +/// +/// ## Examples +/// Spawning a task: +/// ``` rust +/// #[embassy_executor::main(entry = "qingke_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_spin(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_SPIN).into() +} + +/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +/// +/// Spawning a task using a custom entry macro: +/// ``` rust +/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_RISCV).into() +} + +/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_STD).into() +} + +/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { + main::run(args.into(), item.into(), &main::ARCH_WASM).into() +} diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs new file mode 100644 index 0000000..24f61f3 --- /dev/null +++ b/embassy-executor-macros/src/macros/main.rs @@ -0,0 +1,184 @@ +use std::str::FromStr; + +use darling::export::NestedMeta; +use darling::FromMeta; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{ReturnType, Type}; + +use crate::util::*; + +enum Flavor { + Standard, + Wasm, +} + +pub(crate) struct Arch { + default_entry: Option<&'static str>, + flavor: Flavor, +} + +pub static ARCH_AVR: Arch = Arch { + default_entry: Some("avr_device::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_RISCV: Arch = Arch { + default_entry: Some("riscv_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_CORTEX_M: Arch = Arch { + default_entry: Some("cortex_m_rt::entry"), + flavor: Flavor::Standard, +}; + +pub static ARCH_SPIN: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_STD: Arch = Arch { + default_entry: None, + flavor: Flavor::Standard, +}; + +pub static ARCH_WASM: Arch = Arch { + default_entry: Some("wasm_bindgen::prelude::wasm_bindgen(start)"), + flavor: Flavor::Wasm, +}; + +#[derive(Debug, FromMeta, Default)] +struct Args { + #[darling(default)] + entry: Option, +} + +pub fn run(args: TokenStream, item: TokenStream, arch: &Arch) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; + + let fargs = f.sig.inputs.clone(); + + if f.sig.asyncness.is_none() { + error(&mut errors, &f.sig, "main function must be async"); + } + if !f.sig.generics.params.is_empty() { + error(&mut errors, &f.sig, "main function must not be generic"); + } + if !f.sig.generics.where_clause.is_none() { + error(&mut errors, &f.sig, "main function must not have `where` clauses"); + } + if !f.sig.abi.is_none() { + error(&mut errors, &f.sig, "main function must not have an ABI qualifier"); + } + if !f.sig.variadic.is_none() { + error(&mut errors, &f.sig, "main function must not be variadic"); + } + match &f.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => error( + &mut errors, + &f.sig, + "main function must either not return a value, return `()` or return `!`", + ), + }, + } + + if fargs.len() != 1 { + error(&mut errors, &f.sig, "main function must have 1 argument: the spawner."); + } + + let entry = match args.entry.as_deref().or(arch.default_entry) { + None => TokenStream::new(), + Some(x) => match TokenStream::from_str(x) { + Ok(x) => quote!(#[#x]), + Err(e) => { + error(&mut errors, &f.sig, e); + TokenStream::new() + } + }, + }; + + let f_body = f.body; + let out = &f.sig.output; + + let (main_ret, mut main_body) = match arch.flavor { + Flavor::Standard => ( + quote!(!), + quote! { + unsafe fn __make_static(t: &mut T) -> &'static mut T { + ::core::mem::transmute(t) + } + + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + }, + ), + Flavor::Wasm => ( + quote!(Result<(), wasm_bindgen::JsValue>), + quote! { + let executor = ::std::boxed::Box::leak(::std::boxed::Box::new(::embassy_executor::Executor::new())); + + executor.start(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }); + + Ok(()) + }, + ), + }; + + let mut main_attrs = TokenStream::new(); + for attr in f.attrs { + main_attrs.extend(quote!(#attr)); + } + + if !errors.is_empty() { + main_body = quote! {loop{}}; + } + + let result = quote! { + #[::embassy_executor::task()] + #[allow(clippy::future_not_send)] + async fn __embassy_main(#fargs) #out { + #f_body + } + + #entry + #main_attrs + fn main() -> #main_ret { + #main_body + } + + #errors + }; + + result +} diff --git a/embassy-executor-macros/src/macros/mod.rs b/embassy-executor-macros/src/macros/mod.rs new file mode 100644 index 0000000..572094c --- /dev/null +++ b/embassy-executor-macros/src/macros/mod.rs @@ -0,0 +1,2 @@ +pub mod main; +pub mod task; diff --git a/embassy-executor-macros/src/macros/task.rs b/embassy-executor-macros/src/macros/task.rs new file mode 100644 index 0000000..e8134c6 --- /dev/null +++ b/embassy-executor-macros/src/macros/task.rs @@ -0,0 +1,220 @@ +use std::str::FromStr; + +use darling::export::NestedMeta; +use darling::FromMeta; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::visit::{self, Visit}; +use syn::{Expr, ExprLit, Lit, LitInt, ReturnType, Type}; + +use crate::util::*; + +#[derive(Debug, FromMeta, Default)] +struct Args { + #[darling(default)] + pool_size: Option, + /// Use this to override the `embassy_executor` crate path. Defaults to `::embassy_executor`. + #[darling(default)] + embassy_executor: Option, +} + +pub fn run(args: TokenStream, item: TokenStream) -> TokenStream { + let mut errors = TokenStream::new(); + + // If any of the steps for this macro fail, we still want to expand to an item that is as close + // to the expected output as possible. This helps out IDEs such that completions and other + // related features keep working. + let f: ItemFn = match syn::parse2(item.clone()) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match NestedMeta::parse_meta_list(args) { + Ok(x) => x, + Err(e) => return token_stream_with_error(item, e), + }; + + let args = match Args::from_list(&args) { + Ok(x) => x, + Err(e) => { + errors.extend(e.write_errors()); + Args::default() + } + }; + + let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { + attrs: vec![], + lit: Lit::Int(LitInt::new("1", Span::call_site())), + })); + + let embassy_executor = args + .embassy_executor + .unwrap_or(Expr::Verbatim(TokenStream::from_str("::embassy_executor").unwrap())); + + if f.sig.asyncness.is_none() { + error(&mut errors, &f.sig, "task functions must be async"); + } + if !f.sig.generics.params.is_empty() { + error(&mut errors, &f.sig, "task functions must not be generic"); + } + if !f.sig.generics.where_clause.is_none() { + error(&mut errors, &f.sig, "task functions must not have `where` clauses"); + } + if !f.sig.abi.is_none() { + error(&mut errors, &f.sig, "task functions must not have an ABI qualifier"); + } + if !f.sig.variadic.is_none() { + error(&mut errors, &f.sig, "task functions must not be variadic"); + } + match &f.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => error( + &mut errors, + &f.sig, + "task functions must either not return a value, return `()` or return `!`", + ), + }, + } + + let mut args = Vec::new(); + let mut fargs = f.sig.inputs.clone(); + + for arg in fargs.iter_mut() { + match arg { + syn::FnArg::Receiver(_) => { + error(&mut errors, arg, "task functions must not have `self` arguments"); + } + syn::FnArg::Typed(t) => { + check_arg_ty(&mut errors, &t.ty); + match t.pat.as_mut() { + syn::Pat::Ident(id) => { + id.mutability = None; + args.push((id.clone(), t.attrs.clone())); + } + _ => { + error( + &mut errors, + arg, + "pattern matching in task arguments is not yet supported", + ); + } + } + } + } + } + + let task_ident = f.sig.ident.clone(); + let task_inner_ident = format_ident!("__{}_task", task_ident); + + let mut task_inner = f.clone(); + let visibility = task_inner.vis.clone(); + task_inner.vis = syn::Visibility::Inherited; + task_inner.sig.ident = task_inner_ident.clone(); + + // assemble the original input arguments, + // including any attributes that may have + // been applied previously + let mut full_args = Vec::new(); + for (arg, cfgs) in args { + full_args.push(quote!( + #(#cfgs)* + #arg + )); + } + + #[cfg(feature = "nightly")] + let mut task_outer_body = quote! { + trait _EmbassyInternalTaskTrait { + type Fut: ::core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut; + } + + impl _EmbassyInternalTaskTrait for () { + type Fut = impl core::future::Future + 'static; + fn construct(#fargs) -> Self::Fut { + #task_inner_ident(#(#full_args,)*) + } + } + + const POOL_SIZE: usize = #pool_size; + static POOL: #embassy_executor::raw::TaskPool<<() as _EmbassyInternalTaskTrait>::Fut, POOL_SIZE> = #embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || <() as _EmbassyInternalTaskTrait>::construct(#(#full_args,)*)) } + }; + #[cfg(not(feature = "nightly"))] + let mut task_outer_body = quote! { + const POOL_SIZE: usize = #pool_size; + static POOL: #embassy_executor::_export::TaskPoolRef = #embassy_executor::_export::TaskPoolRef::new(); + unsafe { POOL.get::<_, POOL_SIZE>()._spawn_async_fn(move || #task_inner_ident(#(#full_args,)*)) } + }; + + let task_outer_attrs = task_inner.attrs.clone(); + + if !errors.is_empty() { + task_outer_body = quote! { + #![allow(unused_variables, unreachable_code)] + let _x: #embassy_executor::SpawnToken<()> = ::core::todo!(); + _x + }; + } + + // Copy the generics + where clause to avoid more spurious errors. + let generics = &f.sig.generics; + let where_clause = &f.sig.generics.where_clause; + + let result = quote! { + // This is the user's task function, renamed. + // We put it outside the #task_ident fn below, because otherwise + // the items defined there (such as POOL) would be in scope + // in the user's code. + #[doc(hidden)] + #task_inner + + #(#task_outer_attrs)* + #visibility fn #task_ident #generics (#fargs) -> #embassy_executor::SpawnToken #where_clause{ + #task_outer_body + } + + #errors + }; + + result +} + +fn check_arg_ty(errors: &mut TokenStream, ty: &Type) { + struct Visitor<'a> { + errors: &'a mut TokenStream, + } + + impl<'a, 'ast> Visit<'ast> for Visitor<'a> { + fn visit_type_reference(&mut self, i: &'ast syn::TypeReference) { + // only check for elided lifetime here. If not elided, it's checked by `visit_lifetime`. + if i.lifetime.is_none() { + error( + self.errors, + i.and_token, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + visit::visit_type_reference(self, i); + } + + fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { + if i.ident.to_string() != "static" { + error( + self.errors, + i, + "Arguments for tasks must live forever. Try using the `'static` lifetime.", + ) + } + } + + fn visit_type_impl_trait(&mut self, i: &'ast syn::TypeImplTrait) { + error(self.errors, i, "`impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic."); + } + } + + Visit::visit_type(&mut Visitor { errors }, ty); +} diff --git a/embassy-executor-macros/src/util.rs b/embassy-executor-macros/src/util.rs new file mode 100644 index 0000000..ebd032a --- /dev/null +++ b/embassy-executor-macros/src/util.rs @@ -0,0 +1,74 @@ +use std::fmt::Display; + +use proc_macro2::{TokenStream, TokenTree}; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::{Parse, ParseStream}; +use syn::{braced, bracketed, token, AttrStyle, Attribute, Signature, Token, Visibility}; + +pub fn token_stream_with_error(mut tokens: TokenStream, error: syn::Error) -> TokenStream { + tokens.extend(error.into_compile_error()); + tokens +} + +pub fn error(s: &mut TokenStream, obj: A, msg: T) { + s.extend(syn::Error::new_spanned(obj.into_token_stream(), msg).into_compile_error()) +} + +/// Function signature and body. +/// +/// Same as `syn`'s `ItemFn` except we keep the body as a TokenStream instead of +/// parsing it. This makes the macro not error if there's a syntax error in the body, +/// which helps IDE autocomplete work better. +#[derive(Debug, Clone)] +pub struct ItemFn { + pub attrs: Vec, + pub vis: Visibility, + pub sig: Signature, + pub brace_token: token::Brace, + pub body: TokenStream, +} + +impl Parse for ItemFn { + fn parse(input: ParseStream) -> syn::Result { + let mut attrs = input.call(Attribute::parse_outer)?; + let vis: Visibility = input.parse()?; + let sig: Signature = input.parse()?; + + let content; + let brace_token = braced!(content in input); + while content.peek(Token![#]) && content.peek2(Token![!]) { + let content2; + attrs.push(Attribute { + pound_token: content.parse()?, + style: AttrStyle::Inner(content.parse()?), + bracket_token: bracketed!(content2 in content), + meta: content2.parse()?, + }); + } + + let mut body = Vec::new(); + while !content.is_empty() { + body.push(content.parse::()?); + } + let body = body.into_iter().collect(); + + Ok(ItemFn { + attrs, + vis, + sig, + brace_token, + body, + }) + } +} + +impl ToTokens for ItemFn { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.append_all(self.attrs.iter().filter(|a| matches!(a.style, AttrStyle::Outer))); + self.vis.to_tokens(tokens); + self.sig.to_tokens(tokens); + self.brace_token.surround(tokens, |tokens| { + tokens.append_all(self.body.clone()); + }); + } +} diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md new file mode 100644 index 0000000..5d2468c --- /dev/null +++ b/embassy-executor/CHANGELOG.md @@ -0,0 +1,97 @@ +# Changelog + +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). + +## 0.7.0 - 2025-01-02 + +- Performance optimizations. +- Remove feature `integrated-timers`. Starting with `embassy-time-driver` v0.2, `embassy-time` v0.4 the timer queue is now part of the time driver, so it's no longer the executor's responsibility. Therefore, `embassy-executor` no longer provides an `embassy-time-queue-driver` implementation. +- Added the possibility for timer driver implementations to store arbitrary data in task headers. This can be used to make a timer queue intrusive list, similar to the previous `integrated-timers` feature. Payload size is controlled by the `timer-item-payload-size-X` features. +- Added `TaskRef::executor` to obtain a reference to a task's executor + +## 0.6.3 - 2024-11-12 + +- Building with the `nightly` feature now works with the Xtensa Rust compiler 1.82. +- Compare vtable address instead of contents. Saves 44 bytes of flash on cortex-m. + +## 0.6.2 - 2024-11-06 + +- The `nightly` feature no longer requires `nightly-2024-09-06` or newer. + +## 0.6.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. +- Add an architecture-agnostic executor that spins waiting for tasks to run, enabled with the `arch-spin` feature. +- Update for breaking change in the nightly waker_getters API. The `nightly` feature now requires `nightly-2024-09-06` or newer. +- Improve macro error messages. + +## 0.6.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- initial support for AVR +- use nightly waker_getters APIs + +## 0.5.1 - 2024-10-21 + +- Soundness fix: Deny using `impl Trait` in task arguments. This was previously accidentally allowed when not using the `nightly` feature, + and could cause out of bounds memory accesses if spawning the same task mulitple times with different underlying types + for the `impl Trait`. Affected versions are 0.4.x, 0.5.0 and 0.6.0, which have been yanked. + +## 0.5.0 - 2024-01-11 + +- Updated to `embassy-time-driver 0.1`, `embassy-time-queue-driver 0.1`, compatible with `embassy-time v0.3` and higher. + +## 0.4.0 - 2023-12-05 + +- Removed `arch-xtensa`. Use the executor provided by the HAL crate (`esp-hal`, `esp32s3-hal`, etc...) instead. +- Added an arena allocator for tasks, allowing using the `main` and `task` macros on Rust 1.75 stable. (it is only used if the `nightly` feature is not enabled. When `nightly` is enabled, `type_alias_impl_trait` is used to statically allocate tasks, as before). + +## 0.3.3 - 2023-11-15 + +- Add `main` macro reexport for Xtensa arch. +- Remove use of `atomic-polyfill`. The executor now has multiple implementations of its internal data structures for cases where the target supports atomics or doesn't. + +## 0.3.2 - 2023-11-06 + +- Use `atomic-polyfill` for `riscv32` +- Removed unused dependencies (static_cell, futures-util) + +## 0.3.1 - 2023-11-01 + +- Fix spurious "Found waker not created by the Embassy executor" error in recent nightlies. + +## 0.3.0 - 2023-08-25 + +- Replaced Pender. Implementations now must define an extern function called `__pender`. +- Made `raw::AvailableTask` public +- Made `SpawnToken::new_failed` public +- You can now use arbitrary expressions to specify `#[task(pool_size = X)]` + +## 0.2.1 - 2023-08-10 + +- Avoid calling `pend()` when waking expired timers +- Properly reset finished task state with `integrated-timers` enabled +- Introduce `InterruptExecutor::spawner()` +- Fix incorrect critical section in Xtensa executor + +## 0.2.0 - 2023-04-27 + +- Replace unnecessary atomics in runqueue +- add Pender, rework Cargo features. +- add support for turbo-wakers. +- Allow TaskStorage to auto-implement `Sync` +- Use AtomicPtr for signal_ctx, removes 1 unsafe. +- Replace unsound critical sections with atomics + +## 0.1.1 - 2022-11-23 + +- Fix features for documentation + +## 0.1.0 - 2022-11-23 + +- First release diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml new file mode 100644 index 0000000..d6f24ce --- /dev/null +++ b/embassy-executor/Cargo.toml @@ -0,0 +1,205 @@ +[package] +name = "embassy-executor" +version = "0.7.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "async/await executor designed for embedded usage" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-executor" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" +features = ["defmt"] +flavors = [ + { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, + { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, + { name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] }, + { name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] }, +] + +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabi" +targets = ["thumbv7em-none-eabi"] +features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +rtos-trace = { version = "0.1.3", optional = true } + +embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +critical-section = "1.1" + +document-features = "0.2.7" + +# needed for AVR +portable-atomic = { version = "1.5", optional = true } + +# arch-cortex-m dependencies +cortex-m = { version = "0.7.6", optional = true } + +# arch-wasm dependencies +wasm-bindgen = { version = "0.2.82", optional = true } +js-sys = { version = "0.3", optional = true } + +# arch-avr dependencies +avr-device = { version = "0.5.3", optional = true } + +[dev-dependencies] +critical-section = { version = "1.1", features = ["std"] } +trybuild = "1.0" +embassy-sync = { path = "../embassy-sync" } + +[features] + +## Enable nightly-only features +nightly = ["embassy-executor-macros/nightly"] + +# Enables turbo wakers, which requires patching core. Not surfaced in the docs by default due to +# being an complicated advanced and undocumented feature. +# See: https://github.com/embassy-rs/embassy/pull/1263 +turbowakers = [] + +#! ### Architecture +_arch = [] # some arch was picked +## std +arch-std = ["_arch", "critical-section/std"] +## Cortex-M +arch-cortex-m = ["_arch", "dep:cortex-m"] +## RISC-V 32 +arch-riscv32 = ["_arch"] +## WASM +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys", "critical-section/std"] +## AVR +arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] +## spin (architecture agnostic; never sleeps) +arch-spin = ["_arch"] + +#! ### Executor + +## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) +executor-thread = [] +## Enable the interrupt-mode executor (available in Cortex-M only) +executor-interrupt = [] +## Enable tracing support (adds some overhead) +trace = [] +## Enable support for rtos-trace framework +rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] + +#! ### Timer Item Payload Size +#! Sets the size of the payload for timer items, allowing integrated timer implementors to store +#! additional data in the timer item. The payload field will be aligned to this value as well. +#! If these features are not defined, the timer item will contain no payload field. + +_timer-item-payload = [] # A size was picked + +## 1 bytes +timer-item-payload-size-1 = ["_timer-item-payload"] +## 2 bytes +timer-item-payload-size-2 = ["_timer-item-payload"] +## 4 bytes +timer-item-payload-size-4 = ["_timer-item-payload"] +## 8 bytes +timer-item-payload-size-8 = ["_timer-item-payload"] + +#! ### Task Arena Size +#! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`. +#! +#!

+#! Preconfigured Task Arena Sizes: +#! +#! + +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +## 64 +task-arena-size-64 = [] +## 128 +task-arena-size-128 = [] +## 192 +task-arena-size-192 = [] +## 256 +task-arena-size-256 = [] +## 320 +task-arena-size-320 = [] +## 384 +task-arena-size-384 = [] +## 512 +task-arena-size-512 = [] +## 640 +task-arena-size-640 = [] +## 768 +task-arena-size-768 = [] +## 1024 +task-arena-size-1024 = [] +## 1280 +task-arena-size-1280 = [] +## 1536 +task-arena-size-1536 = [] +## 2048 +task-arena-size-2048 = [] +## 2560 +task-arena-size-2560 = [] +## 3072 +task-arena-size-3072 = [] +## 4096 (default) +task-arena-size-4096 = [] # Default +## 5120 +task-arena-size-5120 = [] +## 6144 +task-arena-size-6144 = [] +## 8192 +task-arena-size-8192 = [] +## 10240 +task-arena-size-10240 = [] +## 12288 +task-arena-size-12288 = [] +## 16384 +task-arena-size-16384 = [] +## 20480 +task-arena-size-20480 = [] +## 24576 +task-arena-size-24576 = [] +## 32768 +task-arena-size-32768 = [] +## 40960 +task-arena-size-40960 = [] +## 49152 +task-arena-size-49152 = [] +## 65536 +task-arena-size-65536 = [] +## 81920 +task-arena-size-81920 = [] +## 98304 +task-arena-size-98304 = [] +## 131072 +task-arena-size-131072 = [] +## 163840 +task-arena-size-163840 = [] +## 196608 +task-arena-size-196608 = [] +## 262144 +task-arena-size-262144 = [] +## 327680 +task-arena-size-327680 = [] +## 393216 +task-arena-size-393216 = [] +## 524288 +task-arena-size-524288 = [] +## 655360 +task-arena-size-655360 = [] +## 786432 +task-arena-size-786432 = [] +## 1048576 +task-arena-size-1048576 = [] + +# END AUTOGENERATED CONFIG FEATURES + +#!
diff --git a/embassy-executor/README.md b/embassy-executor/README.md new file mode 100644 index 0000000..074c735 --- /dev/null +++ b/embassy-executor/README.md @@ -0,0 +1,37 @@ +# embassy-executor + +An async/await executor designed for embedded usage. + +- No `alloc`, no heap needed. +- With nightly Rust, task futures can be fully statically allocated. +- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. +- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`. +- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. +- Efficient polling: a wake will only poll the woken task, not all of them. +- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. +- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. + +## Task arena + +When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator). + +If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks. + +Tasks are allocated from the arena when spawned for the first time. If the task exits, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load). + +The arena size can be configured in two ways: + +- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values + is available, see [Task Area Sizes](#task-arena-size) for reference. +- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example + `EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. + Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +## Statically allocating tasks + +When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible. + +The configured arena size is ignored, no arena is used at all. diff --git a/embassy-executor/build.rs b/embassy-executor/build.rs new file mode 100644 index 0000000..8a41d75 --- /dev/null +++ b/embassy-executor/build.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +#[path = "./build_common.rs"] +mod common; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("TASK_ARENA_SIZE", 4096), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); + + let mut rustc_cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut rustc_cfgs); +} diff --git a/embassy-executor/build_common.rs b/embassy-executor/build_common.rs new file mode 100644 index 0000000..b15a836 --- /dev/null +++ b/embassy-executor/build_common.rs @@ -0,0 +1,126 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub struct CompilerDate { + year: u16, + month: u8, + day: u8, +} + +impl CompilerDate { + fn parse(date: &str) -> Option { + let mut parts = date.split('-'); + let year = parts.next()?.parse().ok()?; + let month = parts.next()?.parse().ok()?; + let day = parts.next()?.parse().ok()?; + Some(Self { year, month, day }) + } +} + +impl PartialEq<&str> for CompilerDate { + fn eq(&self, other: &&str) -> bool { + let Some(other) = Self::parse(other) else { + return false; + }; + self.eq(&other) + } +} + +impl PartialOrd<&str> for CompilerDate { + fn partial_cmp(&self, other: &&str) -> Option { + Self::parse(other).map(|other| self.cmp(&other)) + } +} diff --git a/embassy-executor/gen_config.py b/embassy-executor/gen_config.py new file mode 100644 index 0000000..cf32bd5 --- /dev/null +++ b/embassy-executor/gen_config.py @@ -0,0 +1,88 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]): + if vals is None: + assert min is not None + assert max is not None + + vals = set() + val = min + while val <= max: + vals.add(val) + for f in factors: + if val * f <= max: + vals.add(val * f) + if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0: + val *= 2 + else: + val += 1 + vals.add(default) + vals = sorted(list(vals)) + + features.append( + { + "name": name, + "default": default, + "vals": vals, + } + ) + + +feature( + "task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5] +) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"## {val}" + if val == f["default"]: + things += " (default)\n" + else: + things += "\n" + + things += f"{name}-{val} = []" + if val == f["default"]: + things += " # Default" + things += "\n" + things += "\n" + +SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" +HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" +with open("Cargo.toml", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after +with open("Cargo.toml", "w") as f: + f.write(data) + + +# ========= Update build.rs + +things = "" +for f in features: + name = f["name"].upper() + things += f' ("{name}", {f["default"]}),\n' + +SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" +HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" +with open("build.rs", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after +with open("build.rs", "w") as f: + f.write(data) diff --git a/embassy-executor/src/arch/avr.rs b/embassy-executor/src/arch/avr.rs new file mode 100644 index 0000000..70085d0 --- /dev/null +++ b/embassy-executor/src/arch/avr.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-avr`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_avr as main; + use portable_atomic::{AtomicBool, Ordering}; + + use crate::{raw, Spawner}; + + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + + /// avr Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + avr_device::interrupt::disable(); + if !SIGNAL_WORK_THREAD_MODE.swap(false, Ordering::SeqCst) { + avr_device::interrupt::enable(); + avr_device::asm::sleep(); + } else { + avr_device::interrupt::enable(); + self.inner.poll(); + } + } + } + } + } +} diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs new file mode 100644 index 0000000..5c517e0 --- /dev/null +++ b/embassy-executor/src/arch/cortex_m.rs @@ -0,0 +1,231 @@ +#[export_name = "__pender"] +#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))] +fn __pender(context: *mut ()) { + unsafe { + // Safety: `context` is either `usize::MAX` created by `Executor::run`, or a valid interrupt + // request number given to `InterruptExecutor::start`. + + let context = context as usize; + + #[cfg(feature = "executor-thread")] + // Try to make Rust optimize the branching away if we only use thread mode. + if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER { + core::arch::asm!("sev"); + return; + } + + #[cfg(feature = "executor-interrupt")] + { + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + + #[derive(Clone, Copy)] + struct Irq(u16); + unsafe impl InterruptNumber for Irq { + fn number(self) -> u16 { + self.0 + } + } + + let irq = Irq(context as u16); + + // STIR is faster, but is only available in v7 and higher. + #[cfg(not(armv6m))] + { + let mut nvic: NVIC = core::mem::transmute(()); + nvic.request(irq); + } + + #[cfg(armv6m)] + NVIC::pend(irq); + } + } +} + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + pub(super) const THREAD_PENDER: usize = usize::MAX; + + use core::arch::asm; + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_cortex_m as main; + + use crate::{raw, Spawner}; + + /// Thread mode executor, using WFE/SEV. + /// + /// This is the simplest and most common kind of executor. It runs on + /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction + /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction + /// is executed, to make the `WFE` exit from sleep and poll the task. + /// + /// This executor allows for ultra low power consumption for chips where `WFE` + /// triggers low-power sleep without extra steps. If your chip requires extra steps, + /// you may use [`raw::Executor`] directly to program custom behavior. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(THREAD_PENDER as *mut ()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + asm!("wfe"); + }; + } + } + } +} + +#[cfg(feature = "executor-interrupt")] +pub use interrupt::*; +#[cfg(feature = "executor-interrupt")] +mod interrupt { + use core::cell::{Cell, UnsafeCell}; + use core::mem::MaybeUninit; + + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + use critical_section::Mutex; + + use crate::raw; + + /// Interrupt mode executor. + /// + /// This executor runs tasks in interrupt mode. The interrupt handler is set up + /// to poll tasks, and when a task is woken the interrupt is pended from software. + /// + /// This allows running async tasks at a priority higher than thread mode. One + /// use case is to leave thread mode free for non-async tasks. Another use case is + /// to run multiple executors: one in thread mode for low priority tasks and another in + /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower + /// priority ones. + /// + /// It is even possible to run multiple interrupt mode executors at different priorities, + /// by assigning different priorities to the interrupts. For an example on how to do this, + /// See the 'multiprio' example for 'embassy-nrf'. + /// + /// To use it, you have to pick an interrupt that won't be used by the hardware. + /// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). + /// If this is not the case, you may use an interrupt from any unused peripheral. + /// + /// It is somewhat more complex to use, it's recommended to use the thread-mode + /// [`Executor`] instead, if it works for your use case. + pub struct InterruptExecutor { + started: Mutex>, + executor: UnsafeCell>, + } + + unsafe impl Send for InterruptExecutor {} + unsafe impl Sync for InterruptExecutor {} + + impl InterruptExecutor { + /// Create a new, not started `InterruptExecutor`. + #[inline] + pub const fn new() -> Self { + Self { + started: Mutex::new(Cell::new(false)), + executor: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + /// Executor interrupt callback. + /// + /// # Safety + /// + /// - You MUST call this from the interrupt handler, and from nowhere else. + /// - You must not call this before calling `start()`. + pub unsafe fn on_interrupt(&'static self) { + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.poll(); + } + + /// Start the executor. + /// + /// This initializes the executor, enables the interrupt, and returns. + /// The executor keeps running in the background through the interrupt. + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] + /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a + /// different "thread" (the interrupt), so spawning tasks on it is effectively + /// sending them. + /// + /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from + /// a task running in it. + /// + /// # Interrupt requirements + /// + /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt). + /// + /// This method already enables (unmasks) the interrupt, you must NOT do it yourself. + /// + /// You must set the interrupt priority before calling this method. You MUST NOT + /// do it after. + /// + pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { + if critical_section::with(|cs| self.started.borrow(cs).replace(true)) { + panic!("InterruptExecutor::start() called multiple times on the same executor."); + } + + unsafe { + (&mut *self.executor.get()) + .as_mut_ptr() + .write(raw::Executor::new(irq.number() as *mut ())) + } + + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + + unsafe { NVIC::unmask(irq) } + + executor.spawner().make_send() + } + + /// Get a SendSpawner for this executor + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on this + /// executor. + /// + /// This MUST only be called on an executor that has already been started. + /// The function will panic otherwise. + pub fn spawner(&'static self) -> crate::SendSpawner { + if !critical_section::with(|cs| self.started.borrow(cs).get()) { + panic!("InterruptExecutor::spawner() called on uninitialized executor."); + } + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.spawner().make_send() + } + } +} diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs new file mode 100644 index 0000000..01e63a9 --- /dev/null +++ b/embassy-executor/src/arch/riscv32.rs @@ -0,0 +1,80 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-riscv32`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + use core::sync::atomic::{AtomicBool, Ordering}; + + pub use embassy_executor_macros::main_riscv as main; + + use crate::{raw, Spawner}; + + /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + + /// RISCV32 Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + // we do not care about race conditions between the load and store operations, interrupts + //will only set this value to true. + critical_section::with(|_| { + // if there is work to do, loop back to polling + // TODO can we relax this? + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } + // if not, wait for interrupt + else { + core::arch::asm!("wfi"); + } + }); + // if an interrupt occurred while waiting, it will be serviced here + } + } + } + } +} diff --git a/embassy-executor/src/arch/spin.rs b/embassy-executor/src/arch/spin.rs new file mode 100644 index 0000000..3400236 --- /dev/null +++ b/embassy-executor/src/arch/spin.rs @@ -0,0 +1,58 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-spin`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_spin as main; + + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) {} + + /// Spin Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(core::ptr::null_mut()), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + } + } + } +} diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs new file mode 100644 index 0000000..b02b159 --- /dev/null +++ b/embassy-executor/src/arch/std.rs @@ -0,0 +1,94 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-std`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use std::marker::PhantomData; + use std::sync::{Condvar, Mutex}; + + pub use embassy_executor_macros::main_std as main; + + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(context: *mut ()) { + let signaler: &'static Signaler = unsafe { std::mem::transmute(context) }; + signaler.signal() + } + + /// Single-threaded std-based executor. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + signaler: &'static Signaler, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let signaler = Box::leak(Box::new(Signaler::new())); + Self { + inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), + not_send: PhantomData, + signaler, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + self.signaler.wait() + } + } + } + + struct Signaler { + mutex: Mutex, + condvar: Condvar, + } + + impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait(&self) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + signaled = self.condvar.wait(signaled).unwrap(); + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } + } +} diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs new file mode 100644 index 0000000..f9d0f93 --- /dev/null +++ b/embassy-executor/src/arch/wasm.rs @@ -0,0 +1,83 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-wasm`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_wasm as main; + use js_sys::Promise; + use wasm_bindgen::prelude::*; + + use crate::raw::util::UninitCell; + use crate::{raw, Spawner}; + + #[export_name = "__pender"] + fn __pender(context: *mut ()) { + let signaler: &'static WasmContext = unsafe { std::mem::transmute(context) }; + let _ = signaler.promise.then(unsafe { signaler.closure.as_mut() }); + } + + pub(crate) struct WasmContext { + promise: Promise, + closure: UninitCell>, + } + + impl WasmContext { + pub fn new() -> Self { + Self { + promise: Promise::resolve(&JsValue::undefined()), + closure: UninitCell::uninit(), + } + } + } + + /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. + pub struct Executor { + inner: raw::Executor, + ctx: &'static WasmContext, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let ctx = Box::leak(Box::new(WasmContext::new())); + Self { + inner: raw::Executor::new(ctx as *mut WasmContext as *mut ()), + ctx, + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { + unsafe { + let executor = &self.inner; + let future = Closure::new(move |_| { + executor.poll(); + }); + self.ctx.closure.write_in_place(|| future); + init(self.inner.spawner()); + } + } + } +} diff --git a/embassy-executor/src/fmt.rs b/embassy-executor/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-executor/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-executor/src/lib.rs b/embassy-executor/src/lib.rs new file mode 100644 index 0000000..d816539 --- /dev/null +++ b/embassy-executor/src/lib.rs @@ -0,0 +1,150 @@ +#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] +#![allow(clippy::new_without_default)] +#![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; + +pub use embassy_executor_macros::task; + +macro_rules! check_at_most_one { + (@amo [$($feats:literal)*] [] [$($res:tt)*]) => { + #[cfg(any($($res)*))] + compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*)); + }; + (@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => { + check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]); + }; + ($($f:literal),*$(,)?) => { + check_at_most_one!(@amo [$($f)*] [$($f)*] []); + }; +} +check_at_most_one!( + "arch-avr", + "arch-cortex-m", + "arch-riscv32", + "arch-std", + "arch-wasm", + "arch-spin", +); + +#[cfg(feature = "_arch")] +#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] +#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] +#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] +#[cfg_attr(feature = "arch-std", path = "arch/std.rs")] +#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] +#[cfg_attr(feature = "arch-spin", path = "arch/spin.rs")] +mod arch; + +#[cfg(feature = "_arch")] +#[allow(unused_imports)] // don't warn if the module is empty. +pub use arch::*; + +pub mod raw; + +mod spawner; +pub use spawner::*; + +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +/// Implementation details for embassy macros. +/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. +#[doc(hidden)] +#[cfg(not(feature = "nightly"))] +pub mod _export { + use core::alloc::Layout; + use core::cell::{Cell, UnsafeCell}; + use core::future::Future; + use core::mem::MaybeUninit; + use core::ptr::null_mut; + + use critical_section::{CriticalSection, Mutex}; + + use crate::raw::TaskPool; + + struct Arena { + buf: UnsafeCell>, + ptr: Mutex>, + } + + unsafe impl Sync for Arena {} + unsafe impl Send for Arena {} + + impl Arena { + const fn new() -> Self { + Self { + buf: UnsafeCell::new(MaybeUninit::uninit()), + ptr: Mutex::new(Cell::new(null_mut())), + } + } + + fn alloc(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit { + let layout = Layout::new::(); + + let start = self.buf.get().cast::(); + let end = unsafe { start.add(N) }; + + let mut ptr = self.ptr.borrow(cs).get(); + if ptr.is_null() { + ptr = self.buf.get().cast::(); + } + + let bytes_left = (end as usize) - (ptr as usize); + let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize); + + if align_offset + layout.size() > bytes_left { + panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/"); + } + + let res = unsafe { ptr.add(align_offset) }; + let ptr = unsafe { ptr.add(align_offset + layout.size()) }; + + self.ptr.borrow(cs).set(ptr); + + unsafe { &mut *(res as *mut MaybeUninit) } + } + } + + static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new(); + + pub struct TaskPoolRef { + // type-erased `&'static mut TaskPool` + // Needed because statics can't have generics. + ptr: Mutex>, + } + unsafe impl Sync for TaskPoolRef {} + unsafe impl Send for TaskPoolRef {} + + impl TaskPoolRef { + pub const fn new() -> Self { + Self { + ptr: Mutex::new(Cell::new(null_mut())), + } + } + + /// Get the pool for this ref, allocating it from the arena the first time. + /// + /// safety: for a given TaskPoolRef instance, must always call with the exact + /// same generic params. + pub unsafe fn get(&'static self) -> &'static TaskPool { + critical_section::with(|cs| { + let ptr = self.ptr.borrow(cs); + if ptr.get().is_null() { + let pool = ARENA.alloc::>(cs); + pool.write(TaskPool::new()); + ptr.set(pool as *mut _ as _); + } + + unsafe { &*(ptr.get() as *const _) } + }) + } + } +} diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs new file mode 100644 index 0000000..e38a2af --- /dev/null +++ b/embassy-executor/src/raw/mod.rs @@ -0,0 +1,571 @@ +//! Raw executor. +//! +//! This module exposes "raw" Executor and Task structs for more low level control. +//! +//! ## WARNING: here be dragons! +//! +//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe +//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe. + +#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")] +#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")] +mod run_queue; + +#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")] +#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")] +#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] +mod state; + +pub mod timer_queue; +#[cfg(feature = "trace")] +mod trace; +pub(crate) mod util; +#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] +mod waker; + +use core::future::Future; +use core::marker::PhantomData; +use core::mem; +use core::pin::Pin; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::task::{Context, Poll}; + +use self::run_queue::{RunQueue, RunQueueItem}; +use self::state::State; +use self::util::{SyncUnsafeCell, UninitCell}; +pub use self::waker::task_from_waker; +use super::SpawnToken; + +/// Raw task header for use in task pointers. +/// +/// A task can be in one of the following states: +/// +/// - Not spawned: the task is ready to spawn. +/// - `SPAWNED`: the task is currently spawned and may be running. +/// - `RUN_ENQUEUED`: the task is enqueued to be polled. Note that the task may be `!SPAWNED`. +/// In this case, the `RUN_ENQUEUED` state will be cleared when the task is next polled, without +/// polling the task's future. +/// +/// A task's complete life cycle is as follows: +/// +/// ```text +/// ┌────────────┐ ┌────────────────────────┐ +/// │Not spawned │◄─5┤Not spawned|Run enqueued│ +/// │ ├6─►│ │ +/// └─────┬──────┘ └──────▲─────────────────┘ +/// 1 │ +/// │ ┌────────────┘ +/// │ 4 +/// ┌─────▼────┴─────────┐ +/// │Spawned|Run enqueued│ +/// │ │ +/// └─────┬▲─────────────┘ +/// 2│ +/// │3 +/// ┌─────▼┴─────┐ +/// │ Spawned │ +/// │ │ +/// └────────────┘ +/// ``` +/// +/// Transitions: +/// - 1: Task is spawned - `AvailableTask::claim -> Executor::spawn` +/// - 2: During poll - `RunQueue::dequeue_all -> State::run_dequeue` +/// - 3: Task wakes itself, waker wakes task, or task exits - `Waker::wake -> wake_task -> State::run_enqueue` +/// - 4: A run-queued task exits - `TaskStorage::poll -> Poll::Ready` +/// - 5: Task is dequeued. The task's future is not polled, because exiting the task replaces its `poll_fn`. +/// - 6: A task is waken when it is not spawned - `wake_task -> State::run_enqueue` +pub(crate) struct TaskHeader { + pub(crate) state: State, + pub(crate) run_queue_item: RunQueueItem, + pub(crate) executor: AtomicPtr, + poll_fn: SyncUnsafeCell>, + + /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. + pub(crate) timer_queue_item: timer_queue::TimerQueueItem, +} + +/// This is essentially a `&'static TaskStorage` where the type of the future has been erased. +#[derive(Clone, Copy, PartialEq)] +pub struct TaskRef { + ptr: NonNull, +} + +unsafe impl Send for TaskRef where &'static TaskHeader: Send {} +unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {} + +impl TaskRef { + fn new(task: &'static TaskStorage) -> Self { + Self { + ptr: NonNull::from(task).cast(), + } + } + + /// Safety: The pointer must have been obtained with `Task::as_ptr` + pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self { + Self { + ptr: NonNull::new_unchecked(ptr as *mut TaskHeader), + } + } + + /// # Safety + /// + /// The result of this function must only be compared + /// for equality, or stored, but not used. + pub const unsafe fn dangling() -> Self { + Self { + ptr: NonNull::dangling(), + } + } + + pub(crate) fn header(self) -> &'static TaskHeader { + unsafe { self.ptr.as_ref() } + } + + /// Returns a reference to the executor that the task is currently running on. + pub unsafe fn executor(self) -> Option<&'static Executor> { + let executor = self.header().executor.load(Ordering::Relaxed); + executor.as_ref().map(|e| Executor::wrap(e)) + } + + /// Returns a reference to the timer queue item. + pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem { + &self.header().timer_queue_item + } + + /// The returned pointer is valid for the entire TaskStorage. + pub(crate) fn as_ptr(self) -> *const TaskHeader { + self.ptr.as_ptr() + } +} + +/// Raw storage in which a task can be spawned. +/// +/// This struct holds the necessary memory to spawn one task whose future is `F`. +/// At a given time, the `TaskStorage` may be in spawned or not-spawned state. You +/// may spawn it with [`TaskStorage::spawn()`], which will fail if it is already spawned. +/// +/// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished +/// running. Hence the relevant methods require `&'static self`. It may be reused, however. +/// +/// Internally, the [embassy_executor::task](embassy_executor_macros::task) macro allocates an array of `TaskStorage`s +/// in a `static`. The most common reason to use the raw `Task` is to have control of where +/// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc. + +// repr(C) is needed to guarantee that the Task is located at offset 0 +// This makes it safe to cast between TaskHeader and TaskStorage pointers. +#[repr(C)] +pub struct TaskStorage { + raw: TaskHeader, + future: UninitCell, // Valid if STATE_SPAWNED +} + +unsafe fn poll_exited(_p: TaskRef) { + // Nothing to do, the task is already !SPAWNED and dequeued. +} + +impl TaskStorage { + const NEW: Self = Self::new(); + + /// Create a new TaskStorage, in not-spawned state. + pub const fn new() -> Self { + Self { + raw: TaskHeader { + state: State::new(), + run_queue_item: RunQueueItem::new(), + executor: AtomicPtr::new(core::ptr::null_mut()), + // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` + poll_fn: SyncUnsafeCell::new(None), + + timer_queue_item: timer_queue::TimerQueueItem::new(), + }, + future: UninitCell::uninit(), + } + } + + /// Try to spawn the task. + /// + /// The `future` closure constructs the future. It's only called if spawning is + /// actually possible. It is a closure instead of a simple `future: F` param to ensure + /// the future is constructed in-place, avoiding a temporary copy in the stack thanks to + /// NRVO optimizations. + /// + /// This function will fail if the task is already spawned and has not finished running. + /// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will + /// cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. + /// + /// Once the task has finished running, you may spawn it again. It is allowed to spawn it + /// on a different executor. + pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + let task = AvailableTask::claim(self); + match task { + Some(task) => task.initialize(future), + None => SpawnToken::new_failed(), + } + } + + unsafe fn poll(p: TaskRef) { + let this = &*p.as_ptr().cast::>(); + + let future = Pin::new_unchecked(this.future.as_mut()); + let waker = waker::from_task(p); + let mut cx = Context::from_waker(&waker); + match future.poll(&mut cx) { + Poll::Ready(_) => { + // As the future has finished and this function will not be called + // again, we can safely drop the future here. + this.future.drop_in_place(); + + // We replace the poll_fn with a despawn function, so that the task is cleaned up + // when the executor polls it next. + this.raw.poll_fn.set(Some(poll_exited)); + + // Make sure we despawn last, so that other threads can only spawn the task + // after we're done with it. + this.raw.state.despawn(); + } + Poll::Pending => {} + } + + // the compiler is emitting a virtual call for waker drop, but we know + // it's a noop for our waker. + mem::forget(waker); + } + + #[doc(hidden)] + #[allow(dead_code)] + fn _assert_sync(self) { + fn assert_sync(_: T) {} + + assert_sync(self) + } +} + +/// An uninitialized [`TaskStorage`]. +pub struct AvailableTask { + task: &'static TaskStorage, +} + +impl AvailableTask { + /// Try to claim a [`TaskStorage`]. + /// + /// This function returns `None` if a task has already been spawned and has not finished running. + pub fn claim(task: &'static TaskStorage) -> Option { + task.raw.state.spawn().then(|| Self { task }) + } + + fn initialize_impl(self, future: impl FnOnce() -> F) -> SpawnToken { + unsafe { + self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); + self.task.future.write_in_place(future); + + let task = TaskRef::new(self.task); + + SpawnToken::new(task) + } + } + + /// Initialize the [`TaskStorage`] to run the given future. + pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken { + self.initialize_impl::(future) + } + + /// Initialize the [`TaskStorage`] to run the given future. + /// + /// # Safety + /// + /// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn __initialize_async_fn(self, future: impl FnOnce() -> F) -> SpawnToken { + // When send-spawning a task, we construct the future in this thread, and effectively + // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, + // send-spawning should require the future `F` to be `Send`. + // + // The problem is this is more restrictive than needed. Once the future is executing, + // it is never sent to another thread. It is only sent when spawning. It should be + // enough for the task's arguments to be Send. (and in practice it's super easy to + // accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.) + // + // We can do it by sending the task args and constructing the future in the executor thread + // on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy + // of the args. + // + // Luckily, an `async fn` future contains just the args when freshly constructed. So, if the + // args are Send, it's OK to send a !Send future, as long as we do it before first polling it. + // + // (Note: this is how the generators are implemented today, it's not officially guaranteed yet, + // but it's possible it'll be guaranteed in the future. See zulip thread: + // https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures ) + // + // The `FutFn` captures all the args, so if it's Send, the task can be send-spawned. + // This is why we return `SpawnToken` below. + // + // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly + // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. + self.initialize_impl::(future) + } +} + +/// Raw storage that can hold up to N tasks of the same type. +/// +/// This is essentially a `[TaskStorage; N]`. +pub struct TaskPool { + pool: [TaskStorage; N], +} + +impl TaskPool { + /// Create a new TaskPool, with all tasks in non-spawned state. + pub const fn new() -> Self { + Self { + pool: [TaskStorage::NEW; N], + } + } + + fn spawn_impl(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + match self.pool.iter().find_map(AvailableTask::claim) { + Some(task) => task.initialize_impl::(future), + None => SpawnToken::new_failed(), + } + } + + /// Try to spawn a task in the pool. + /// + /// See [`TaskStorage::spawn()`] for details. + /// + /// This will loop over the pool and spawn the task in the first storage that + /// is currently free. If none is free, a "poisoned" SpawnToken is returned, + /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. + pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + self.spawn_impl::(future) + } + + /// Like spawn(), but allows the task to be send-spawned if the args are Send even if + /// the future is !Send. + /// + /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used + /// by the Embassy macros ONLY. + /// + /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken + where + FutFn: FnOnce() -> F, + { + // See the comment in AvailableTask::__initialize_async_fn for explanation. + self.spawn_impl::(future) + } +} + +#[derive(Clone, Copy)] +pub(crate) struct Pender(*mut ()); + +unsafe impl Send for Pender {} +unsafe impl Sync for Pender {} + +impl Pender { + pub(crate) fn pend(self) { + extern "Rust" { + fn __pender(context: *mut ()); + } + unsafe { __pender(self.0) }; + } +} + +pub(crate) struct SyncExecutor { + run_queue: RunQueue, + pender: Pender, +} + +impl SyncExecutor { + pub(crate) fn new(pender: Pender) -> Self { + Self { + run_queue: RunQueue::new(), + pender, + } + } + + /// Enqueue a task in the task queue + /// + /// # Safety + /// - `task` must be a valid pointer to a spawned task. + /// - `task` must be set up to run in this executor. + /// - `task` must NOT be already enqueued (in this executor or another one). + #[inline(always)] + unsafe fn enqueue(&self, task: TaskRef, l: state::Token) { + #[cfg(feature = "trace")] + trace::task_ready_begin(self, &task); + + if self.run_queue.enqueue(task, l) { + self.pender.pend(); + } + } + + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + task.header() + .executor + .store((self as *const Self).cast_mut(), Ordering::Relaxed); + + #[cfg(feature = "trace")] + trace::task_new(self, &task); + + state::locked(|l| { + self.enqueue(task, l); + }) + } + + /// # Safety + /// + /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. + pub(crate) unsafe fn poll(&'static self) { + self.run_queue.dequeue_all(|p| { + let task = p.header(); + + #[cfg(feature = "trace")] + trace::task_exec_begin(self, &p); + + // Run the task + task.poll_fn.get().unwrap_unchecked()(p); + + #[cfg(feature = "trace")] + trace::task_exec_end(self, &p); + }); + + #[cfg(feature = "trace")] + trace::executor_idle(self) + } +} + +/// Raw executor. +/// +/// This is the core of the Embassy executor. It is low-level, requiring manual +/// handling of wakeups and task polling. If you can, prefer using one of the +/// [higher level executors](crate::Executor). +/// +/// The raw executor leaves it up to you to handle wakeups and scheduling: +/// +/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks +/// that "want to run"). +/// - You must supply a pender function, as shown below. The executor will call it to notify you +/// it has work to do. You must arrange for `poll()` to be called as soon as possible. +/// - Enabling `arch-xx` features will define a pender function for you. This means that you +/// are limited to using the executors provided to you by the architecture/platform +/// implementation. If you need a different executor, you must not enable `arch-xx` features. +/// +/// The pender can be called from *any* context: any thread, any interrupt priority +/// level, etc. It may be called synchronously from any `Executor` method call as well. +/// You must deal with this correctly. +/// +/// In particular, you must NOT call `poll` directly from the pender callback, as this violates +/// the requirement for `poll` to not be called reentrantly. +/// +/// The pender function must be exported with the name `__pender` and have the following signature: +/// +/// ```rust +/// #[export_name = "__pender"] +/// fn pender(context: *mut ()) { +/// // schedule `poll()` to be called +/// } +/// ``` +/// +/// The `context` argument is a piece of arbitrary data the executor will pass to the pender. +/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example, +/// differentiate between executors, or to pass a pointer to a callback that should be called. +#[repr(transparent)] +pub struct Executor { + pub(crate) inner: SyncExecutor, + + _not_sync: PhantomData<*mut ()>, +} + +impl Executor { + pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self { + mem::transmute(inner) + } + + /// Create a new executor. + /// + /// When the executor has work to do, it will call the pender function and pass `context` to it. + /// + /// See [`Executor`] docs for details on the pender. + pub fn new(context: *mut ()) -> Self { + Self { + inner: SyncExecutor::new(Pender(context)), + _not_sync: PhantomData, + } + } + + /// Spawn a task in this executor. + /// + /// # Safety + /// + /// `task` must be a valid pointer to an initialized but not-already-spawned task. + /// + /// It is OK to use `unsafe` to call this from a thread that's not the executor thread. + /// In this case, the task's Future must be Send. This is because this is effectively + /// sending the task to the executor thread. + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + self.inner.spawn(task) + } + + /// Poll all queued tasks in this executor. + /// + /// This loops over all tasks that are queued to be polled (i.e. they're + /// freshly spawned or they've been woken). Other tasks are not polled. + /// + /// You must call `poll` after receiving a call to the pender. It is OK + /// to call `poll` even when not requested by the pender, but it wastes + /// energy. + /// + /// # Safety + /// + /// You must call `initialize` before calling this method. + /// + /// You must NOT call `poll` reentrantly on the same executor. + /// + /// In particular, note that `poll` may call the pender synchronously. Therefore, you + /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to + /// somehow schedule for `poll()` to be called later, at a time you know for sure there's + /// no `poll()` already running. + pub unsafe fn poll(&'static self) { + self.inner.poll() + } + + /// Get a spawner that spawns tasks in this executor. + /// + /// It is OK to call this method multiple times to obtain multiple + /// `Spawner`s. You may also copy `Spawner`s. + pub fn spawner(&'static self) -> super::Spawner { + super::Spawner::new(self) + } +} + +/// Wake a task by `TaskRef`. +/// +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task(task: TaskRef) { + let header = task.header(); + header.state.run_enqueue(|l| { + // We have just marked the task as scheduled, so enqueue it. + unsafe { + let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); + executor.enqueue(task, l); + } + }); +} + +/// Wake a task by `TaskRef` without calling pend. +/// +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task_no_pend(task: TaskRef) { + let header = task.header(); + header.state.run_enqueue(|l| { + // We have just marked the task as scheduled, so enqueue it. + unsafe { + let executor = header.executor.load(Ordering::Relaxed).as_ref().unwrap_unchecked(); + executor.run_queue.enqueue(task, l); + } + }); +} diff --git a/embassy-executor/src/raw/run_queue_atomics.rs b/embassy-executor/src/raw/run_queue_atomics.rs new file mode 100644 index 0000000..ce511d7 --- /dev/null +++ b/embassy-executor/src/raw/run_queue_atomics.rs @@ -0,0 +1,88 @@ +use core::ptr; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; + +use super::{TaskHeader, TaskRef}; +use crate::raw::util::SyncUnsafeCell; + +pub(crate) struct RunQueueItem { + next: SyncUnsafeCell>, +} + +impl RunQueueItem { + pub const fn new() -> Self { + Self { + next: SyncUnsafeCell::new(None), + } + } +} + +/// Atomic task queue using a very, very simple lock-free linked-list queue: +/// +/// To enqueue a task, task.next is set to the old head, and head is atomically set to task. +/// +/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with +/// null. Then the batch is iterated following the next pointers until null is reached. +/// +/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK +/// for our purposes: it can't create fairness problems since the next batch won't run until the +/// current batch is completely processed, so even if a task enqueues itself instantly (for example +/// by waking its own waker) can't prevent other tasks from running. +pub(crate) struct RunQueue { + head: AtomicPtr, +} + +impl RunQueue { + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Enqueues an item. Returns true if the queue was empty. + /// + /// # Safety + /// + /// `item` must NOT be already enqueued in any queue. + #[inline(always)] + pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool { + let mut was_empty = false; + + self.head + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| { + was_empty = prev.is_null(); + unsafe { + // safety: the pointer is either null or valid + let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())); + // safety: there are no concurrent accesses to `next` + task.header().run_queue_item.next.set(prev); + } + Some(task.as_ptr() as *mut _) + }) + .ok(); + + was_empty + } + + /// Empty the queue, then call `on_task` for each task that was in the queue. + /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue + /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. + pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { + // Atomically empty the queue. + let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + + // safety: the pointer is either null or valid + let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) }; + + // Iterate the linked list of tasks that were previously in the queue. + while let Some(task) = next { + // If the task re-enqueues itself, the `next` pointer will get overwritten. + // Therefore, first read the next pointer, and only then process the task. + // safety: there are no concurrent accesses to `next` + next = unsafe { task.header().run_queue_item.next.get() }; + + task.header().state.run_dequeue(); + on_task(task); + } + } +} diff --git a/embassy-executor/src/raw/run_queue_critical_section.rs b/embassy-executor/src/raw/run_queue_critical_section.rs new file mode 100644 index 0000000..86c4085 --- /dev/null +++ b/embassy-executor/src/raw/run_queue_critical_section.rs @@ -0,0 +1,74 @@ +use core::cell::Cell; + +use critical_section::{CriticalSection, Mutex}; + +use super::TaskRef; + +pub(crate) struct RunQueueItem { + next: Mutex>>, +} + +impl RunQueueItem { + pub const fn new() -> Self { + Self { + next: Mutex::new(Cell::new(None)), + } + } +} + +/// Atomic task queue using a very, very simple lock-free linked-list queue: +/// +/// To enqueue a task, task.next is set to the old head, and head is atomically set to task. +/// +/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with +/// null. Then the batch is iterated following the next pointers until null is reached. +/// +/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK +/// for our purposes: it can't create fairness problems since the next batch won't run until the +/// current batch is completely processed, so even if a task enqueues itself instantly (for example +/// by waking its own waker) can't prevent other tasks from running. +pub(crate) struct RunQueue { + head: Mutex>>, +} + +impl RunQueue { + pub const fn new() -> Self { + Self { + head: Mutex::new(Cell::new(None)), + } + } + + /// Enqueues an item. Returns true if the queue was empty. + /// + /// # Safety + /// + /// `item` must NOT be already enqueued in any queue. + #[inline(always)] + pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool { + let prev = self.head.borrow(cs).replace(Some(task)); + task.header().run_queue_item.next.borrow(cs).set(prev); + + prev.is_none() + } + + /// Empty the queue, then call `on_task` for each task that was in the queue. + /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue + /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. + pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { + // Atomically empty the queue. + let mut next = critical_section::with(|cs| self.head.borrow(cs).take()); + + // Iterate the linked list of tasks that were previously in the queue. + while let Some(task) = next { + // If the task re-enqueues itself, the `next` pointer will get overwritten. + // Therefore, first read the next pointer, and only then process the task. + + critical_section::with(|cs| { + next = task.header().run_queue_item.next.borrow(cs).get(); + task.header().state.run_dequeue(cs); + }); + + on_task(task); + } + } +} diff --git a/embassy-executor/src/raw/state_atomics.rs b/embassy-executor/src/raw/state_atomics.rs new file mode 100644 index 0000000..b6576bf --- /dev/null +++ b/embassy-executor/src/raw/state_atomics.rs @@ -0,0 +1,58 @@ +use core::sync::atomic::{AtomicU32, Ordering}; + +#[derive(Clone, Copy)] +pub(crate) struct Token(()); + +/// Creates a token and passes it to the closure. +/// +/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. +pub(crate) fn locked(f: impl FnOnce(Token) -> R) -> R { + f(Token(())) +} + +/// Task is spawned (has a future) +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +/// Task is in the executor run queue +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; + +pub(crate) struct State { + state: AtomicU32, +} + +impl State { + pub const fn new() -> State { + Self { + state: AtomicU32::new(0), + } + } + + /// If task is idle, mark it as spawned + run_queued and return true. + #[inline(always)] + pub fn spawn(&self) -> bool { + self.state + .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) + .is_ok() + } + + /// Unmark the task as spawned. + #[inline(always)] + pub fn despawn(&self) { + self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); + } + + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. + #[inline(always)] + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + let prev = self.state.fetch_or(STATE_RUN_QUEUED, Ordering::AcqRel); + if prev & STATE_RUN_QUEUED == 0 { + locked(f); + } + } + + /// Unmark the task as run-queued. Return whether the task is spawned. + #[inline(always)] + pub fn run_dequeue(&self) { + self.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); + } +} diff --git a/embassy-executor/src/raw/state_atomics_arm.rs b/embassy-executor/src/raw/state_atomics_arm.rs new file mode 100644 index 0000000..b743dcc --- /dev/null +++ b/embassy-executor/src/raw/state_atomics_arm.rs @@ -0,0 +1,83 @@ +use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; + +#[derive(Clone, Copy)] +pub(crate) struct Token(()); + +/// Creates a token and passes it to the closure. +/// +/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking. +pub(crate) fn locked(f: impl FnOnce(Token) -> R) -> R { + f(Token(())) +} + +// Must be kept in sync with the layout of `State`! +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8; + +#[repr(C, align(4))] +pub(crate) struct State { + /// Task is spawned (has a future) + spawned: AtomicBool, + /// Task is in the executor run queue + run_queued: AtomicBool, + pad: AtomicBool, + pad2: AtomicBool, +} + +impl State { + pub const fn new() -> State { + Self { + spawned: AtomicBool::new(false), + run_queued: AtomicBool::new(false), + pad: AtomicBool::new(false), + pad2: AtomicBool::new(false), + } + } + + fn as_u32(&self) -> &AtomicU32 { + unsafe { &*(self as *const _ as *const AtomicU32) } + } + + /// If task is idle, mark it as spawned + run_queued and return true. + #[inline(always)] + pub fn spawn(&self) -> bool { + compiler_fence(Ordering::Release); + let r = self + .as_u32() + .compare_exchange( + 0, + STATE_SPAWNED | STATE_RUN_QUEUED, + Ordering::Relaxed, + Ordering::Relaxed, + ) + .is_ok(); + compiler_fence(Ordering::Acquire); + r + } + + /// Unmark the task as spawned. + #[inline(always)] + pub fn despawn(&self) { + compiler_fence(Ordering::Release); + self.spawned.store(false, Ordering::Relaxed); + } + + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. + #[inline(always)] + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + let old = self.run_queued.swap(true, Ordering::AcqRel); + + if !old { + locked(f); + } + } + + /// Unmark the task as run-queued. Return whether the task is spawned. + #[inline(always)] + pub fn run_dequeue(&self) { + compiler_fence(Ordering::Release); + + self.run_queued.store(false, Ordering::Relaxed); + } +} diff --git a/embassy-executor/src/raw/state_critical_section.rs b/embassy-executor/src/raw/state_critical_section.rs new file mode 100644 index 0000000..6b627ff --- /dev/null +++ b/embassy-executor/src/raw/state_critical_section.rs @@ -0,0 +1,73 @@ +use core::cell::Cell; + +pub(crate) use critical_section::{with as locked, CriticalSection as Token}; +use critical_section::{CriticalSection, Mutex}; + +/// Task is spawned (has a future) +pub(crate) const STATE_SPAWNED: u32 = 1 << 0; +/// Task is in the executor run queue +pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; + +pub(crate) struct State { + state: Mutex>, +} + +impl State { + pub const fn new() -> State { + Self { + state: Mutex::new(Cell::new(0)), + } + } + + fn update(&self, f: impl FnOnce(&mut u32) -> R) -> R { + critical_section::with(|cs| self.update_with_cs(cs, f)) + } + + fn update_with_cs(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R { + let s = self.state.borrow(cs); + let mut val = s.get(); + let r = f(&mut val); + s.set(val); + r + } + + /// If task is idle, mark it as spawned + run_queued and return true. + #[inline(always)] + pub fn spawn(&self) -> bool { + self.update(|s| { + if *s == 0 { + *s = STATE_SPAWNED | STATE_RUN_QUEUED; + true + } else { + false + } + }) + } + + /// Unmark the task as spawned. + #[inline(always)] + pub fn despawn(&self) { + self.update(|s| *s &= !STATE_SPAWNED); + } + + /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given + /// function if the task was successfully marked. + #[inline(always)] + pub fn run_enqueue(&self, f: impl FnOnce(Token)) { + critical_section::with(|cs| { + if self.update_with_cs(cs, |s| { + let ok = *s & STATE_RUN_QUEUED == 0; + *s |= STATE_RUN_QUEUED; + ok + }) { + f(cs); + } + }); + } + + /// Unmark the task as run-queued. Return whether the task is spawned. + #[inline(always)] + pub fn run_dequeue(&self, cs: CriticalSection<'_>) { + self.update_with_cs(cs, |s| *s &= !STATE_RUN_QUEUED) + } +} diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs new file mode 100644 index 0000000..e52453b --- /dev/null +++ b/embassy-executor/src/raw/timer_queue.rs @@ -0,0 +1,73 @@ +//! Timer queue operations. + +use core::cell::Cell; + +use super::TaskRef; + +#[cfg(feature = "_timer-item-payload")] +macro_rules! define_opaque { + ($size:tt) => { + /// An opaque data type. + #[repr(align($size))] + pub struct OpaqueData { + data: [u8; $size], + } + + impl OpaqueData { + const fn new() -> Self { + Self { data: [0; $size] } + } + + /// Access the data as a reference to a type `T`. + /// + /// Safety: + /// + /// The caller must ensure that the size of the type `T` is less than, or equal to + /// the size of the payload, and must ensure that the alignment of the type `T` is + /// less than, or equal to the alignment of the payload. + /// + /// The type must be valid when zero-initialized. + pub unsafe fn as_ref(&self) -> &T { + &*(self.data.as_ptr() as *const T) + } + } + }; +} + +#[cfg(feature = "timer-item-payload-size-1")] +define_opaque!(1); +#[cfg(feature = "timer-item-payload-size-2")] +define_opaque!(2); +#[cfg(feature = "timer-item-payload-size-4")] +define_opaque!(4); +#[cfg(feature = "timer-item-payload-size-8")] +define_opaque!(8); + +/// An item in the timer queue. +pub struct TimerQueueItem { + /// The next item in the queue. + /// + /// If this field contains `Some`, the item is in the queue. The last item in the queue has a + /// value of `Some(dangling_pointer)` + pub next: Cell>, + + /// The time at which this item expires. + pub expires_at: Cell, + + /// Some implementation-defined, zero-initialized piece of data. + #[cfg(feature = "_timer-item-payload")] + pub payload: OpaqueData, +} + +unsafe impl Sync for TimerQueueItem {} + +impl TimerQueueItem { + pub(crate) const fn new() -> Self { + Self { + next: Cell::new(None), + expires_at: Cell::new(0), + #[cfg(feature = "_timer-item-payload")] + payload: OpaqueData::new(), + } + } +} diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs new file mode 100644 index 0000000..b34387b --- /dev/null +++ b/embassy-executor/src/raw/trace.rs @@ -0,0 +1,84 @@ +#![allow(unused)] +use crate::raw::{SyncExecutor, TaskRef}; + +#[cfg(not(feature = "rtos-trace"))] +extern "Rust" { + fn _embassy_trace_task_new(executor_id: u32, task_id: u32); + fn _embassy_trace_task_exec_begin(executor_id: u32, task_id: u32); + fn _embassy_trace_task_exec_end(excutor_id: u32, task_id: u32); + fn _embassy_trace_task_ready_begin(executor_id: u32, task_id: u32); + fn _embassy_trace_executor_idle(executor_id: u32); +} + +#[inline] +pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32) + } + + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_new(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_ready_begin(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_exec_begin(task.as_ptr() as u32); +} + +#[inline] +pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::task_exec_end(); +} + +#[inline] +pub(crate) fn executor_idle(executor: &SyncExecutor) { + #[cfg(not(feature = "rtos-trace"))] + unsafe { + _embassy_trace_executor_idle(executor as *const _ as u32) + } + #[cfg(feature = "rtos-trace")] + rtos_trace::trace::system_idle(); +} + +#[cfg(feature = "rtos-trace")] +impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { + fn task_list() { + // We don't know what tasks exist, so we can't send them. + } + fn time() -> u64 { + const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } + } + + const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000); + embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M) + } +} + +#[cfg(feature = "rtos-trace")] +rtos_trace::global_os_callbacks! {SyncExecutor} diff --git a/embassy-executor/src/raw/util.rs b/embassy-executor/src/raw/util.rs new file mode 100644 index 0000000..c46085e --- /dev/null +++ b/embassy-executor/src/raw/util.rs @@ -0,0 +1,57 @@ +use core::cell::UnsafeCell; +use core::mem::MaybeUninit; +use core::ptr; + +pub(crate) struct UninitCell(MaybeUninit>); +impl UninitCell { + pub const fn uninit() -> Self { + Self(MaybeUninit::uninit()) + } + + pub unsafe fn as_mut_ptr(&self) -> *mut T { + (*self.0.as_ptr()).get() + } + + #[allow(clippy::mut_from_ref)] + pub unsafe fn as_mut(&self) -> &mut T { + &mut *self.as_mut_ptr() + } + + #[inline(never)] + pub unsafe fn write_in_place(&self, func: impl FnOnce() -> T) { + ptr::write(self.as_mut_ptr(), func()) + } + + pub unsafe fn drop_in_place(&self) { + ptr::drop_in_place(self.as_mut_ptr()) + } +} + +unsafe impl Sync for UninitCell {} + +#[repr(transparent)] +pub struct SyncUnsafeCell { + value: UnsafeCell, +} + +unsafe impl Sync for SyncUnsafeCell {} + +impl SyncUnsafeCell { + #[inline] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + pub unsafe fn set(&self, value: T) { + *self.value.get() = value; + } + + pub unsafe fn get(&self) -> T + where + T: Copy, + { + *self.value.get() + } +} diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs new file mode 100644 index 0000000..b7d57c3 --- /dev/null +++ b/embassy-executor/src/raw/waker.rs @@ -0,0 +1,42 @@ +use core::task::{RawWaker, RawWakerVTable, Waker}; + +use super::{wake_task, TaskHeader, TaskRef}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); + +unsafe fn clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &VTABLE) +} + +unsafe fn wake(p: *const ()) { + wake_task(TaskRef::from_ptr(p as *const TaskHeader)) +} + +unsafe fn drop(_: *const ()) { + // nop +} + +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { + Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) +} + +/// Get a task pointer from a waker. +/// +/// This can be used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. +pub fn task_from_waker(waker: &Waker) -> TaskRef { + // make sure to compare vtable addresses. Doing `==` on the references + // will compare the contents, which is slower. + if waker.vtable() as *const _ != &VTABLE as *const _ { + panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") + } + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(waker.data() as *const TaskHeader) } +} diff --git a/embassy-executor/src/raw/waker_turbo.rs b/embassy-executor/src/raw/waker_turbo.rs new file mode 100644 index 0000000..435a0ff --- /dev/null +++ b/embassy-executor/src/raw/waker_turbo.rs @@ -0,0 +1,34 @@ +use core::ptr::NonNull; +use core::task::Waker; + +use super::{wake_task, TaskHeader, TaskRef}; + +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { + Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _)) +} + +/// Get a task pointer from a waker. +/// +/// This can be used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. +pub fn task_from_waker(waker: &Waker) -> TaskRef { + let ptr = waker.as_turbo_ptr().as_ptr(); + + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) } +} + +#[inline(never)] +#[no_mangle] +fn _turbo_wake(ptr: NonNull<()>) { + // safety: our wakers are always created with `TaskRef::as_ptr` + let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) }; + wake_task(task) +} diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs new file mode 100644 index 0000000..7e76a09 --- /dev/null +++ b/embassy-executor/src/spawner.rs @@ -0,0 +1,232 @@ +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::Ordering; +use core::task::Poll; + +use super::raw; + +/// Token to spawn a newly-created task in an executor. +/// +/// When calling a task function (like `#[embassy_executor::task] async fn my_task() { ... }`), the returned +/// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must +/// then spawn it into an executor, typically with [`Spawner::spawn()`]. +/// +/// The generic parameter `S` determines whether the task can be spawned in executors +/// in other threads or not. If `S: Send`, it can, which allows spawning it into a [`SendSpawner`]. +/// If not, it can't, so it can only be spawned into the current thread's executor, with [`Spawner`]. +/// +/// # Panics +/// +/// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way. +/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. +#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] +pub struct SpawnToken { + raw_task: Option, + phantom: PhantomData<*mut S>, +} + +impl SpawnToken { + pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { + Self { + raw_task: Some(raw_task), + phantom: PhantomData, + } + } + + /// Return a SpawnToken that represents a failed spawn. + pub fn new_failed() -> Self { + Self { + raw_task: None, + phantom: PhantomData, + } + } +} + +impl Drop for SpawnToken { + fn drop(&mut self) { + // TODO deallocate the task instead. + panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()") + } +} + +/// Error returned when spawning a task. +#[derive(Copy, Clone)] +pub enum SpawnError { + /// Too many instances of this task are already running. + /// + /// By default, a task marked with `#[embassy_executor::task]` can only have one instance + /// running at a time. You may allow multiple instances to run in parallel with + /// `#[embassy_executor::task(pool_size = 4)]`, at the cost of higher RAM usage. + Busy, +} + +impl core::fmt::Debug for SpawnError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } +} + +impl core::fmt::Display for SpawnError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + SpawnError::Busy => write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."), + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for SpawnError { + fn format(&self, f: defmt::Formatter) { + match self { + SpawnError::Busy => defmt::write!(f, "Busy - Too many instances of this task are already running. Check the `pool_size` attribute of the task."), + } + } +} + +impl core::error::Error for SpawnError {} + +/// Handle to spawn tasks into an executor. +/// +/// This Spawner can spawn any task (Send and non-Send ones), but it can +/// only be used in the executor thread (it is not Send itself). +/// +/// If you want to spawn tasks from another thread, use [SendSpawner]. +#[derive(Copy, Clone)] +pub struct Spawner { + executor: &'static raw::Executor, + not_send: PhantomData<*mut ()>, +} + +impl Spawner { + pub(crate) fn new(executor: &'static raw::Executor) -> Self { + Self { + executor, + not_send: PhantomData, + } + } + + /// Get a Spawner for the current executor. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + /// + /// # Panics + /// + /// Panics if the current executor is not an Embassy executor. + pub fn for_current_executor() -> impl Future { + poll_fn(|cx| { + let task = raw::task_from_waker(cx.waker()); + let executor = unsafe { + task.header() + .executor + .load(Ordering::Relaxed) + .as_ref() + .unwrap_unchecked() + }; + let executor = unsafe { raw::Executor::wrap(executor) }; + Poll::Ready(Self::new(executor)) + }) + } + + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). + pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { + let task = token.raw_task; + mem::forget(token); + + match task { + Some(task) => { + unsafe { self.executor.spawn(task) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } + + // Used by the `embassy_executor_macros::main!` macro to throw an error when spawn + // fails. This is here to allow conditional use of `defmt::unwrap!` + // without introducing a `defmt` feature in the `embassy_executor_macros` package, + // which would require use of `-Z namespaced-features`. + /// Spawn a task into an executor, panicking on failure. + /// + /// # Panics + /// + /// Panics if the spawning fails. + pub fn must_spawn(&self, token: SpawnToken) { + unwrap!(self.spawn(token)); + } + + /// Convert this Spawner to a SendSpawner. This allows you to send the + /// spawner to other threads, but the spawner loses the ability to spawn + /// non-Send tasks. + pub fn make_send(&self) -> SendSpawner { + SendSpawner::new(&self.executor.inner) + } +} + +/// Handle to spawn tasks into an executor from any thread. +/// +/// This Spawner can be used from any thread (it is Send), but it can +/// only spawn Send tasks. The reason for this is spawning is effectively +/// "sending" the tasks to the executor thread. +/// +/// If you want to spawn non-Send tasks, use [Spawner]. +#[derive(Copy, Clone)] +pub struct SendSpawner { + executor: &'static raw::SyncExecutor, +} + +impl SendSpawner { + pub(crate) fn new(executor: &'static raw::SyncExecutor) -> Self { + Self { executor } + } + + /// Get a Spawner for the current executor. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + /// + /// # Panics + /// + /// Panics if the current executor is not an Embassy executor. + pub fn for_current_executor() -> impl Future { + poll_fn(|cx| { + let task = raw::task_from_waker(cx.waker()); + let executor = unsafe { + task.header() + .executor + .load(Ordering::Relaxed) + .as_ref() + .unwrap_unchecked() + }; + Poll::Ready(Self::new(executor)) + }) + } + + /// Spawn a task into an executor. + /// + /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). + pub fn spawn(&self, token: SpawnToken) -> Result<(), SpawnError> { + let header = token.raw_task; + mem::forget(token); + + match header { + Some(header) => { + unsafe { self.executor.spawn(header) }; + Ok(()) + } + None => Err(SpawnError::Busy), + } + } + + /// Spawn a task into an executor, panicking on failure. + /// + /// # Panics + /// + /// Panics if the spawning fails. + pub fn must_spawn(&self, token: SpawnToken) { + unwrap!(self.spawn(token)); + } +} diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs new file mode 100644 index 0000000..78c49c0 --- /dev/null +++ b/embassy-executor/tests/test.rs @@ -0,0 +1,285 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +use std::boxed::Box; +use std::future::poll_fn; +use std::sync::{Arc, Mutex}; +use std::task::Poll; + +use embassy_executor::raw::Executor; +use embassy_executor::task; + +#[export_name = "__pender"] +fn __pender(context: *mut ()) { + unsafe { + let trace = &*(context as *const Trace); + trace.push("pend"); + } +} + +#[derive(Clone)] +struct Trace { + trace: Arc>>, +} + +impl Trace { + fn new() -> Self { + Self { + trace: Arc::new(Mutex::new(Vec::new())), + } + } + fn push(&self, value: &'static str) { + self.trace.lock().unwrap().push(value) + } + + fn get(&self) -> Vec<&'static str> { + self.trace.lock().unwrap().clone() + } +} + +fn setup() -> (&'static Executor, Trace) { + let trace = Trace::new(); + let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut (); + let executor = &*Box::leak(Box::new(Executor::new(context))); + + (executor, trace) +} + +#[test] +fn executor_noop() { + let (executor, trace) = setup(); + unsafe { executor.poll() }; + assert!(trace.get().is_empty()) +} + +#[test] +fn executor_task() { + #[task] + async fn task1(trace: Trace) { + trace.push("poll task1") + } + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone())).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // poll only once. + ] + ) +} + +#[test] +fn executor_task_self_wake() { + #[task] + async fn task1(trace: Trace) { + poll_fn(|cx| { + trace.push("poll task1"); + cx.waker().wake_by_ref(); + Poll::Pending + }) + .await + } + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone())).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // task self-wakes + "poll task1", // + "pend", // task self-wakes + ] + ) +} + +#[test] +fn executor_task_self_wake_twice() { + #[task] + async fn task1(trace: Trace) { + poll_fn(|cx| { + trace.push("poll task1"); + cx.waker().wake_by_ref(); + trace.push("poll task1 wake 2"); + cx.waker().wake_by_ref(); + Poll::Pending + }) + .await + } + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone())).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // task self-wakes + "poll task1 wake 2", // task self-wakes again, shouldn't pend + "poll task1", // + "pend", // task self-wakes + "poll task1 wake 2", // task self-wakes again, shouldn't pend + ] + ) +} + +#[test] +fn waking_after_completion_does_not_poll() { + use embassy_sync::waitqueue::AtomicWaker; + + #[task] + async fn task1(trace: Trace, waker: &'static AtomicWaker) { + poll_fn(|cx| { + trace.push("poll task1"); + waker.register(cx.waker()); + Poll::Ready(()) + }) + .await + } + + let waker = Box::leak(Box::new(AtomicWaker::new())); + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + waker.wake(); + unsafe { executor.poll() }; + + // Exited task may be waken but is not polled + waker.wake(); + waker.wake(); + unsafe { executor.poll() }; // Clears running status + + // Can respawn waken-but-dead task + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "poll task1", // + "pend", // manual wake, gets cleared by poll + "pend", // manual wake, single pend for two wakes + "pend", // respawning a task pends the executor + "poll task1", // + ] + ) +} + +#[test] +fn waking_with_old_waker_after_respawn() { + use embassy_sync::waitqueue::AtomicWaker; + + async fn yield_now(trace: Trace) { + let mut yielded = false; + poll_fn(|cx| { + if yielded { + Poll::Ready(()) + } else { + trace.push("yield_now"); + yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + .await + } + + #[task] + async fn task1(trace: Trace, waker: &'static AtomicWaker) { + yield_now(trace.clone()).await; + poll_fn(|cx| { + trace.push("poll task1"); + waker.register(cx.waker()); + Poll::Ready(()) + }) + .await; + } + + let waker = Box::leak(Box::new(AtomicWaker::new())); + + let (executor, trace) = setup(); + executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); + + unsafe { executor.poll() }; + unsafe { executor.poll() }; // progress to registering the waker + waker.wake(); + unsafe { executor.poll() }; + // Task has exited + + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // yield_now wakes the task + "poll task1", // + "pend", // task self-wakes + ] + ); + + // Can respawn task on another executor + let (other_executor, other_trace) = setup(); + other_executor + .spawner() + .spawn(task1(other_trace.clone(), waker)) + .unwrap(); + + unsafe { other_executor.poll() }; // just run to the yield_now + waker.wake(); // trigger old waker registration + unsafe { executor.poll() }; + unsafe { other_executor.poll() }; + + // First executor's trace has not changed + assert_eq!( + trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // yield_now wakes the task + "poll task1", // + "pend", // task self-wakes + ] + ); + + assert_eq!( + other_trace.get(), + &[ + "pend", // spawning a task pends the executor + "yield_now", // + "pend", // manual wake, gets cleared by poll + "poll task1", // + ] + ); +} + +#[test] +fn executor_task_cfg_args() { + // simulate cfg'ing away argument c + #[task] + async fn task1(a: u32, b: u32, #[cfg(any())] c: u32) { + let (_, _) = (a, b); + } + + #[task] + async fn task2(a: u32, b: u32, #[cfg(all())] c: u32) { + let (_, _, _) = (a, b, c); + } +} diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs new file mode 100644 index 0000000..be46794 --- /dev/null +++ b/embassy-executor/tests/ui.rs @@ -0,0 +1,23 @@ +#[cfg(not(miri))] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/abi.rs"); + t.compile_fail("tests/ui/bad_return.rs"); + t.compile_fail("tests/ui/generics.rs"); + t.compile_fail("tests/ui/impl_trait_nested.rs"); + t.compile_fail("tests/ui/impl_trait.rs"); + t.compile_fail("tests/ui/impl_trait_static.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon_nested.rs"); + t.compile_fail("tests/ui/nonstatic_ref_anon.rs"); + t.compile_fail("tests/ui/nonstatic_ref_elided.rs"); + t.compile_fail("tests/ui/nonstatic_ref_generic.rs"); + t.compile_fail("tests/ui/nonstatic_struct_anon.rs"); + #[cfg(not(feature = "nightly"))] // we can't catch this case with the macro, so the output changes on nightly. + t.compile_fail("tests/ui/nonstatic_struct_elided.rs"); + t.compile_fail("tests/ui/nonstatic_struct_generic.rs"); + t.compile_fail("tests/ui/not_async.rs"); + t.compile_fail("tests/ui/self_ref.rs"); + t.compile_fail("tests/ui/self.rs"); + t.compile_fail("tests/ui/where_clause.rs"); +} diff --git a/embassy-executor/tests/ui/abi.rs b/embassy-executor/tests/ui/abi.rs new file mode 100644 index 0000000..fd52f7e --- /dev/null +++ b/embassy-executor/tests/ui/abi.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async extern "C" fn task() {} + +fn main() {} diff --git a/embassy-executor/tests/ui/abi.stderr b/embassy-executor/tests/ui/abi.stderr new file mode 100644 index 0000000..e264e37 --- /dev/null +++ b/embassy-executor/tests/ui/abi.stderr @@ -0,0 +1,5 @@ +error: task functions must not have an ABI qualifier + --> tests/ui/abi.rs:6:1 + | +6 | async extern "C" fn task() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/bad_return.rs b/embassy-executor/tests/ui/bad_return.rs new file mode 100644 index 0000000..f09a520 --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.rs @@ -0,0 +1,10 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() -> u32 { + 5 +} + +fn main() {} diff --git a/embassy-executor/tests/ui/bad_return.stderr b/embassy-executor/tests/ui/bad_return.stderr new file mode 100644 index 0000000..e9d94df --- /dev/null +++ b/embassy-executor/tests/ui/bad_return.stderr @@ -0,0 +1,5 @@ +error: task functions must either not return a value, return `()` or return `!` + --> tests/ui/bad_return.rs:6:1 + | +6 | async fn task() -> u32 { + | ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/generics.rs b/embassy-executor/tests/ui/generics.rs new file mode 100644 index 0000000..b83123b --- /dev/null +++ b/embassy-executor/tests/ui/generics.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_t: T) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/generics.stderr b/embassy-executor/tests/ui/generics.stderr new file mode 100644 index 0000000..197719a --- /dev/null +++ b/embassy-executor/tests/ui/generics.stderr @@ -0,0 +1,5 @@ +error: task functions must not be generic + --> tests/ui/generics.rs:6:1 + | +6 | async fn task(_t: T) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait.rs b/embassy-executor/tests/ui/impl_trait.rs new file mode 100644 index 0000000..a21402a --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait.stderr b/embassy-executor/tests/ui/impl_trait.stderr new file mode 100644 index 0000000..099b182 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait.rs:4:18 + | +4 | async fn foo(_x: impl Sized) {} + | ^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_nested.rs b/embassy-executor/tests/ui/impl_trait_nested.rs new file mode 100644 index 0000000..07442b8 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo(T); + +#[embassy_executor::task] +async fn foo(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_nested.stderr b/embassy-executor/tests/ui/impl_trait_nested.stderr new file mode 100644 index 0000000..39592f9 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_nested.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_nested.rs:6:22 + | +6 | async fn foo(_x: Foo) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/impl_trait_static.rs b/embassy-executor/tests/ui/impl_trait_static.rs new file mode 100644 index 0000000..272470f --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: impl Sized + 'static) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/impl_trait_static.stderr b/embassy-executor/tests/ui/impl_trait_static.stderr new file mode 100644 index 0000000..0032a20 --- /dev/null +++ b/embassy-executor/tests/ui/impl_trait_static.stderr @@ -0,0 +1,5 @@ +error: `impl Trait` is not allowed in task arguments. It is syntax sugar for generics, and tasks can't be generic. + --> tests/ui/impl_trait_static.rs:4:18 + | +4 | async fn foo(_x: impl Sized + 'static) {} + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.rs b/embassy-executor/tests/ui/nonstatic_ref_anon.rs new file mode 100644 index 0000000..417c360 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr new file mode 100644 index 0000000..0544de8 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon.rs:4:19 + | +4 | async fn foo(_x: &'_ u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs new file mode 100644 index 0000000..175ebcc --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &'static &'_ u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr new file mode 100644 index 0000000..79f262e --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_anon_nested.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_anon_nested.rs:4:28 + | +4 | async fn foo(_x: &'static &'_ u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.rs b/embassy-executor/tests/ui/nonstatic_ref_elided.rs new file mode 100644 index 0000000..cf49ad7 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo(_x: &u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_elided.stderr b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr new file mode 100644 index 0000000..7e2b9eb --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_elided.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_elided.rs:4:18 + | +4 | async fn foo(_x: &u32) {} + | ^ diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.rs b/embassy-executor/tests/ui/nonstatic_ref_generic.rs new file mode 100644 index 0000000..3f8a26c --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.rs @@ -0,0 +1,6 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +#[embassy_executor::task] +async fn foo<'a>(_x: &'a u32) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_ref_generic.stderr b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr new file mode 100644 index 0000000..af8491a --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_ref_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_ref_generic.rs:4:1 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_ref_generic.rs:4:23 + | +4 | async fn foo<'a>(_x: &'a u32) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.rs b/embassy-executor/tests/ui/nonstatic_struct_anon.rs new file mode 100644 index 0000000..ba95d14 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo<'_>) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_anon.stderr b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr new file mode 100644 index 0000000..5df2a6e --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_anon.stderr @@ -0,0 +1,5 @@ +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_anon.rs:6:23 + | +6 | async fn task(_x: Foo<'_>) {} + | ^^ diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.rs b/embassy-executor/tests/ui/nonstatic_struct_elided.rs new file mode 100644 index 0000000..4cfe296 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(_x: Foo) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr new file mode 100644 index 0000000..099ef8b --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr @@ -0,0 +1,10 @@ +error[E0726]: implicit elided lifetime not allowed here + --> tests/ui/nonstatic_struct_elided.rs:6:19 + | +6 | async fn task(_x: Foo) {} + | ^^^ expected lifetime parameter + | +help: indicate the anonymous lifetime + | +6 | async fn task(_x: Foo<'_>) {} + | ++++ diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.rs b/embassy-executor/tests/ui/nonstatic_struct_generic.rs new file mode 100644 index 0000000..ec3d908 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task<'a>(_x: Foo<'a>) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/nonstatic_struct_generic.stderr b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr new file mode 100644 index 0000000..61d5231 --- /dev/null +++ b/embassy-executor/tests/ui/nonstatic_struct_generic.stderr @@ -0,0 +1,11 @@ +error: task functions must not be generic + --> tests/ui/nonstatic_struct_generic.rs:6:1 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: Arguments for tasks must live forever. Try using the `'static` lifetime. + --> tests/ui/nonstatic_struct_generic.rs:6:27 + | +6 | async fn task<'a>(_x: Foo<'a>) {} + | ^^ diff --git a/embassy-executor/tests/ui/not_async.rs b/embassy-executor/tests/ui/not_async.rs new file mode 100644 index 0000000..f3f7e9b --- /dev/null +++ b/embassy-executor/tests/ui/not_async.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +fn task() {} + +fn main() {} diff --git a/embassy-executor/tests/ui/not_async.stderr b/embassy-executor/tests/ui/not_async.stderr new file mode 100644 index 0000000..27f040d --- /dev/null +++ b/embassy-executor/tests/ui/not_async.stderr @@ -0,0 +1,5 @@ +error: task functions must be async + --> tests/ui/not_async.rs:6:1 + | +6 | fn task() {} + | ^^^^^^^^^ diff --git a/embassy-executor/tests/ui/self.rs b/embassy-executor/tests/ui/self.rs new file mode 100644 index 0000000..f83a962 --- /dev/null +++ b/embassy-executor/tests/ui/self.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(self) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/self.stderr b/embassy-executor/tests/ui/self.stderr new file mode 100644 index 0000000..aaf0315 --- /dev/null +++ b/embassy-executor/tests/ui/self.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self.rs:6:15 + | +6 | async fn task(self) {} + | ^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy-executor/tests/ui/self_ref.rs b/embassy-executor/tests/ui/self_ref.rs new file mode 100644 index 0000000..5e49bba --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.rs @@ -0,0 +1,8 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task(&mut self) {} + +fn main() {} diff --git a/embassy-executor/tests/ui/self_ref.stderr b/embassy-executor/tests/ui/self_ref.stderr new file mode 100644 index 0000000..dd20529 --- /dev/null +++ b/embassy-executor/tests/ui/self_ref.stderr @@ -0,0 +1,13 @@ +error: task functions must not have `self` arguments + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ + +error: `self` parameter is only allowed in associated functions + --> tests/ui/self_ref.rs:6:15 + | +6 | async fn task(&mut self) {} + | ^^^^^^^^^ not semantically valid as function parameter + | + = note: associated functions are those in `impl` or `trait` definitions diff --git a/embassy-executor/tests/ui/where_clause.rs b/embassy-executor/tests/ui/where_clause.rs new file mode 100644 index 0000000..848d781 --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.rs @@ -0,0 +1,12 @@ +#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] + +struct Foo<'a>(&'a ()); + +#[embassy_executor::task] +async fn task() +where + (): Sized, +{ +} + +fn main() {} diff --git a/embassy-executor/tests/ui/where_clause.stderr b/embassy-executor/tests/ui/where_clause.stderr new file mode 100644 index 0000000..eba45af --- /dev/null +++ b/embassy-executor/tests/ui/where_clause.stderr @@ -0,0 +1,7 @@ +error: task functions must not have `where` clauses + --> tests/ui/where_clause.rs:6:1 + | +6 | / async fn task() +7 | | where +8 | | (): Sized, + | |______________^ diff --git a/embassy-futures/Cargo.toml b/embassy-futures/Cargo.toml new file mode 100644 index 0000000..47cefa5 --- /dev/null +++ b/embassy-futures/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "embassy-futures" +version = "0.1.1" +edition = "2021" +description = "no-std, no-alloc utilities for working with futures" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-futures" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-futures-v$VERSION/embassy-futures/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-futures/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } diff --git a/embassy-futures/README.md b/embassy-futures/README.md new file mode 100644 index 0000000..b28a843 --- /dev/null +++ b/embassy-futures/README.md @@ -0,0 +1,13 @@ +# embassy-futures + +An [Embassy](https://embassy.dev) project. + +Utilities for working with futures, compatible with `no_std` and not using `alloc`. Optimized for code size, +ideal for embedded systems. + +- Future combinators, like [`join`](join) and [`select`](select) +- Utilities to use `async` without a fully fledged executor: [`block_on`](block_on::block_on) and [`yield_now`](yield_now::yield_now). + +## Interoperability + +Futures from this crate can run on any executor. diff --git a/embassy-futures/src/block_on.rs b/embassy-futures/src/block_on.rs new file mode 100644 index 0000000..7769521 --- /dev/null +++ b/embassy-futures/src/block_on.rs @@ -0,0 +1,45 @@ +use core::future::Future; +use core::pin::Pin; +use core::ptr; +use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| RawWaker::new(ptr::null(), &VTABLE), |_| {}, |_| {}, |_| {}); + +/// Run a future to completion using a busy loop. +/// +/// This calls `.poll()` on the future in a busy loop, which blocks +/// the current thread at 100% cpu usage until the future is done. The +/// future's `Waker` mechanism is not used. +/// +/// You can use this to run multiple futures concurrently with [`join`][crate::join]. +/// +/// It's suitable for systems with no or limited concurrency and without +/// strict requirements around power consumption. For more complex use +/// cases, prefer using a "real" executor like `embassy-executor`, which +/// supports multiple tasks, and putting the core to sleep when no task +/// needs to do work. +pub fn block_on(mut fut: F) -> F::Output { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + loop { + if let Poll::Ready(res) = fut.as_mut().poll(&mut cx) { + return res; + } + } +} + +/// Poll a future once. +pub fn poll_once(mut fut: F) -> Poll { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + + fut.as_mut().poll(&mut cx) +} diff --git a/embassy-futures/src/fmt.rs b/embassy-futures/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-futures/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-futures/src/join.rs b/embassy-futures/src/join.rs new file mode 100644 index 0000000..bc0cb53 --- /dev/null +++ b/embassy-futures/src/join.rs @@ -0,0 +1,322 @@ +//! Wait for multiple futures to complete. + +use core::future::Future; +use core::mem::MaybeUninit; +use core::pin::Pin; +use core::task::{Context, Poll}; +use core::{fmt, mem}; + +#[derive(Debug)] +enum MaybeDone { + /// A not-yet-completed future + Future(/* #[pin] */ Fut), + /// The output of the completed future + Done(Fut::Output), + /// The empty variant after the result of a [`MaybeDone`] has been + /// taken using the [`take_output`](MaybeDone::take_output) method. + Gone, +} + +impl MaybeDone { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool { + let this = unsafe { self.get_unchecked_mut() }; + match this { + Self::Future(fut) => match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(res) => { + *this = Self::Done(res); + true + } + Poll::Pending => false, + }, + _ => true, + } + } + + fn take_output(&mut self) -> Fut::Output { + match &*self { + Self::Done(_) => {} + Self::Future(_) | Self::Gone => panic!("take_output when MaybeDone is not done."), + } + match mem::replace(self, Self::Gone) { + MaybeDone::Done(output) => output, + _ => unreachable!(), + } + } +} + +impl Unpin for MaybeDone {} + +macro_rules! generate { + ($( + $(#[$doc:meta])* + ($Join:ident, <$($Fut:ident),*>), + )*) => ($( + $(#[$doc])* + #[must_use = "futures do nothing unless you `.await` or poll them"] + #[allow(non_snake_case)] + pub struct $Join<$($Fut: Future),*> { + $( + $Fut: MaybeDone<$Fut>, + )* + } + + impl<$($Fut),*> fmt::Debug for $Join<$($Fut),*> + where + $( + $Fut: Future + fmt::Debug, + $Fut::Output: fmt::Debug, + )* + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($Join)) + $(.field(stringify!($Fut), &self.$Fut))* + .finish() + } + } + + impl<$($Fut: Future),*> $Join<$($Fut),*> { + #[allow(non_snake_case)] + fn new($($Fut: $Fut),*) -> Self { + Self { + $($Fut: MaybeDone::Future($Fut)),* + } + } + } + + impl<$($Fut: Future),*> Future for $Join<$($Fut),*> { + type Output = ($($Fut::Output),*); + + fn poll( + self: Pin<&mut Self>, cx: &mut Context<'_> + ) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + $( + all_done &= unsafe { Pin::new_unchecked(&mut this.$Fut) }.poll(cx); + )* + + if all_done { + Poll::Ready(($(this.$Fut.take_output()), *)) + } else { + Poll::Pending + } + } + } + )*) +} + +generate! { + /// Future for the [`join`](join()) function. + (Join, ), + + /// Future for the [`join3`] function. + (Join3, ), + + /// Future for the [`join4`] function. + (Join4, ), + + /// Future for the [`join5`] function. + (Join5, ), +} + +/// Joins the result of two futures, waiting for them both to complete. +/// +/// This function will return a new future which awaits both futures to +/// complete. The returned future will finish with a tuple of both results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let pair = embassy_futures::join::join(a, b).await; +/// +/// assert_eq!(pair, (1, 2)); +/// # }); +/// ``` +pub fn join(future1: Fut1, future2: Fut2) -> Join +where + Fut1: Future, + Fut2: Future, +{ + Join::new(future1, future2) +} + +/// Joins the result of three futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let res = embassy_futures::join::join3(a, b, c).await; +/// +/// assert_eq!(res, (1, 2, 3)); +/// # }); +/// ``` +pub fn join3(future1: Fut1, future2: Fut2, future3: Fut3) -> Join3 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, +{ + Join3::new(future1, future2, future3) +} + +/// Joins the result of four futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let res = embassy_futures::join::join4(a, b, c, d).await; +/// +/// assert_eq!(res, (1, 2, 3, 4)); +/// # }); +/// ``` +pub fn join4( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, +) -> Join4 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, +{ + Join4::new(future1, future2, future3, future4) +} + +/// Joins the result of five futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let e = async { 5 }; +/// let res = embassy_futures::join::join5(a, b, c, d, e).await; +/// +/// assert_eq!(res, (1, 2, 3, 4, 5)); +/// # }); +/// ``` +pub fn join5( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, + future5: Fut5, +) -> Join5 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, + Fut5: Future, +{ + Join5::new(future1, future2, future3, future4, future5) +} + +// ===================================================== + +/// Future for the [`join_array`] function. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct JoinArray { + futures: [MaybeDone; N], +} + +impl fmt::Debug for JoinArray +where + Fut: Future + fmt::Debug, + Fut::Output: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("JoinArray").field("futures", &self.futures).finish() + } +} + +impl Future for JoinArray { + type Output = [Fut::Output; N]; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + for f in this.futures.iter_mut() { + all_done &= unsafe { Pin::new_unchecked(f) }.poll(cx); + } + + if all_done { + let mut array: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..N { + array[i].write(this.futures[i].take_output()); + } + Poll::Ready(unsafe { (&array as *const _ as *const [Fut::Output; N]).read() }) + } else { + Poll::Pending + } + } +} + +/// Joins the result of an array of futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// async fn foo(n: u32) -> u32 { n } +/// let a = foo(1); +/// let b = foo(2); +/// let c = foo(3); +/// let res = embassy_futures::join::join_array([a, b, c]).await; +/// +/// assert_eq!(res, [1, 2, 3]); +/// # }); +/// ``` +pub fn join_array(futures: [Fut; N]) -> JoinArray { + JoinArray { + futures: futures.map(MaybeDone::Future), + } +} diff --git a/embassy-futures/src/lib.rs b/embassy-futures/src/lib.rs new file mode 100644 index 0000000..8c769bd --- /dev/null +++ b/embassy-futures/src/lib.rs @@ -0,0 +1,15 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +mod block_on; +mod yield_now; + +pub mod join; +pub mod select; + +pub use block_on::*; +pub use yield_now::*; diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs new file mode 100644 index 0000000..014fee5 --- /dev/null +++ b/embassy-futures/src/select.rs @@ -0,0 +1,545 @@ +//! Wait for the first of several futures to complete. + +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +/// Result for [`select`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), +} + +impl Either { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either::Second(_)) + } +} + +/// Wait for one of two futures to complete. +/// +/// This function returns a new future which polls all the futures. +/// When one of them completes, it will complete with its result value. +/// +/// The other future is dropped. +pub fn select(a: A, b: B) -> Select +where + A: Future, + B: Future, +{ + Select { a, b } +} + +/// Future for the [`select`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select { + a: A, + b: B, +} + +impl Unpin for Select {} + +impl Future for Select +where + A: Future, + B: Future, +{ + type Output = Either; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either::Second(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select3`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either3 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), +} + +impl Either3 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either3::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either3::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either3::Third(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select3(a: A, b: B, c: C) -> Select3 +where + A: Future, + B: Future, + C: Future, +{ + Select3 { a, b, c } +} + +/// Future for the [`select3`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select3 { + a: A, + b: B, + c: C, +} + +impl Future for Select3 +where + A: Future, + B: Future, + C: Future, +{ + type Output = Either3; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either3::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either3::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either3::Third(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select4`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either4 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), +} + +impl Either4 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either4::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either4::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either4::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either4::Fourth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select4(a: A, b: B, c: C, d: D) -> Select4 +where + A: Future, + B: Future, + C: Future, + D: Future, +{ + Select4 { a, b, c, d } +} + +/// Future for the [`select4`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select4 { + a: A, + b: B, + c: C, + d: D, +} + +impl Future for Select4 +where + A: Future, + B: Future, + C: Future, + D: Future, +{ + type Output = Either4; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either4::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either4::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either4::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either4::Fourth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select5`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either5 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), +} + +impl Either5 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either5::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either5::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either5::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either5::Fourth(_)) + } + + /// Did the fifth future complete first? + pub fn is_fifth(&self) -> bool { + matches!(self, Either5::Fifth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select5(a: A, b: B, c: C, d: D, e: E) -> Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + Select5 { a, b, c, d, e } +} + +/// Future for the [`select5`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select5 { + a: A, + b: B, + c: C, + d: D, + e: E, +} + +impl Future for Select5 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, +{ + type Output = Either5; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either5::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either5::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either5::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either5::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either5::Fifth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Result for [`select6`]. +#[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Either6 { + /// First future finished first. + First(A), + /// Second future finished first. + Second(B), + /// Third future finished first. + Third(C), + /// Fourth future finished first. + Fourth(D), + /// Fifth future finished first. + Fifth(E), + /// Sixth future finished first. + Sixth(F), +} + +impl Either6 { + /// Did the first future complete first? + pub fn is_first(&self) -> bool { + matches!(self, Either6::First(_)) + } + + /// Did the second future complete first? + pub fn is_second(&self) -> bool { + matches!(self, Either6::Second(_)) + } + + /// Did the third future complete first? + pub fn is_third(&self) -> bool { + matches!(self, Either6::Third(_)) + } + + /// Did the fourth future complete first? + pub fn is_fourth(&self) -> bool { + matches!(self, Either6::Fourth(_)) + } + + /// Did the fifth future complete first? + pub fn is_fifth(&self) -> bool { + matches!(self, Either6::Fifth(_)) + } + + /// Did the sixth future complete first? + pub fn is_sixth(&self) -> bool { + matches!(self, Either6::Sixth(_)) + } +} + +/// Same as [`select`], but with more futures. +pub fn select6(a: A, b: B, c: C, d: D, e: E, f: F) -> Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + Select6 { a, b, c, d, e, f } +} + +/// Future for the [`select6`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Select6 { + a: A, + b: B, + c: C, + d: D, + e: E, + f: F, +} + +impl Future for Select6 +where + A: Future, + B: Future, + C: Future, + D: Future, + E: Future, + F: Future, +{ + type Output = Either6; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let a = unsafe { Pin::new_unchecked(&mut this.a) }; + let b = unsafe { Pin::new_unchecked(&mut this.b) }; + let c = unsafe { Pin::new_unchecked(&mut this.c) }; + let d = unsafe { Pin::new_unchecked(&mut this.d) }; + let e = unsafe { Pin::new_unchecked(&mut this.e) }; + let f = unsafe { Pin::new_unchecked(&mut this.f) }; + if let Poll::Ready(x) = a.poll(cx) { + return Poll::Ready(Either6::First(x)); + } + if let Poll::Ready(x) = b.poll(cx) { + return Poll::Ready(Either6::Second(x)); + } + if let Poll::Ready(x) = c.poll(cx) { + return Poll::Ready(Either6::Third(x)); + } + if let Poll::Ready(x) = d.poll(cx) { + return Poll::Ready(Either6::Fourth(x)); + } + if let Poll::Ready(x) = e.poll(cx) { + return Poll::Ready(Either6::Fifth(x)); + } + if let Poll::Ready(x) = f.poll(cx) { + return Poll::Ready(Either6::Sixth(x)); + } + Poll::Pending + } +} + +// ==================================================================== + +/// Future for the [`select_array`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SelectArray { + inner: [Fut; N], +} + +/// Creates a new future which will select over an array of futures. +/// +/// The returned future will wait for any future to be ready. Upon +/// completion the item resolved will be returned, along with the index of the +/// future that was ready. +/// +/// If the array is empty, the resulting future will be Pending forever. +pub fn select_array(arr: [Fut; N]) -> SelectArray { + SelectArray { inner: arr } +} + +impl Future for SelectArray { + type Output = (Fut::Output, usize); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, + // its elements also cannot move. Therefore it is safe to access `inner` and pin + // references to the contained futures. + let item = unsafe { + self.get_unchecked_mut() + .inner + .iter_mut() + .enumerate() + .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { + Poll::Pending => None, + Poll::Ready(e) => Some((i, e)), + }) + }; + + match item { + Some((idx, res)) => Poll::Ready((res, idx)), + None => Poll::Pending, + } + } +} + +// ==================================================================== + +/// Future for the [`select_slice`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SelectSlice<'a, Fut> { + inner: Pin<&'a mut [Fut]>, +} + +/// Creates a new future which will select over a slice of futures. +/// +/// The returned future will wait for any future to be ready. Upon +/// completion the item resolved will be returned, along with the index of the +/// future that was ready. +/// +/// If the slice is empty, the resulting future will be Pending forever. +pub fn select_slice<'a, Fut: Future>(slice: Pin<&'a mut [Fut]>) -> SelectSlice<'a, Fut> { + SelectSlice { inner: slice } +} + +impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { + type Output = (Fut::Output, usize); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: refer to + // https://users.rust-lang.org/t/working-with-pinned-slices-are-there-any-structurally-pinning-vec-like-collection-types/50634/2 + #[inline(always)] + fn pin_iter(slice: Pin<&mut [T]>) -> impl Iterator> { + unsafe { slice.get_unchecked_mut().iter_mut().map(|v| Pin::new_unchecked(v)) } + } + for (i, fut) in pin_iter(self.inner.as_mut()).enumerate() { + if let Poll::Ready(res) = fut.poll(cx) { + return Poll::Ready((res, i)); + } + } + + Poll::Pending + } +} diff --git a/embassy-futures/src/yield_now.rs b/embassy-futures/src/yield_now.rs new file mode 100644 index 0000000..4d4e535 --- /dev/null +++ b/embassy-futures/src/yield_now.rs @@ -0,0 +1,49 @@ +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +/// Yield from the current task once, allowing other tasks to run. +/// +/// This can be used to easily and quickly implement simple async primitives +/// without using wakers. The following snippet will wait for a condition to +/// hold, while still allowing other tasks to run concurrently (not monopolizing +/// the executor thread). +/// +/// ```rust +/// # use embassy_futures::{block_on, yield_now}; +/// # async fn test_fn() { +/// # let mut iter_count: u32 = 0; +/// # let mut some_condition = || { iter_count += 1; iter_count > 10 }; +/// while !some_condition() { +/// yield_now().await; +/// } +/// # } +/// # block_on(test_fn()); +/// ``` +/// +/// The downside is this will spin in a busy loop, using 100% of the CPU, while +/// using wakers correctly would allow the CPU to sleep while waiting. +/// +/// The internal implementation is: on first poll the future wakes itself and +/// returns `Poll::Pending`. On second poll, it returns `Poll::Ready`. +pub fn yield_now() -> impl Future { + YieldNowFuture { yielded: false } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +struct YieldNowFuture { + yielded: bool, +} + +impl Future for YieldNowFuture { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.yielded { + Poll::Ready(()) + } else { + self.yielded = true; + cx.waker().wake_by_ref(); + Poll::Pending + } + } +} diff --git a/embassy-hal-internal/Cargo.toml b/embassy-hal-internal/Cargo.toml new file mode 100644 index 0000000..d5ca95a --- /dev/null +++ b/embassy-hal-internal/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "embassy-hal-internal" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-hal-internal" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[features] + +# Define the number of NVIC priority bits. +prio-bits-0 = [] +prio-bits-1 = [] +prio-bits-2 = [] +prio-bits-3 = [] +prio-bits-4 = [] +prio-bits-5 = [] +prio-bits-6 = [] +prio-bits-7 = [] +prio-bits-8 = [] + +cortex-m = ["dep:cortex-m", "dep:critical-section"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +num-traits = { version = "0.2.14", default-features = false } + +cortex-m = { version = "0.7.6", optional = true } +critical-section = { version = "1", optional = true } diff --git a/embassy-hal-internal/README.md b/embassy-hal-internal/README.md new file mode 100644 index 0000000..1adce5b --- /dev/null +++ b/embassy-hal-internal/README.md @@ -0,0 +1,6 @@ +# embassy-hal-internal + +An [Embassy](https://embassy.dev) project. + +Internal implementation details for Embassy HALs. DO NOT USE DIRECTLY. Embassy HALs (`embassy-nrf`, `embassy-stm32`, `embassy-rp`) already reexport +everything you need to use them effectively. diff --git a/embassy-hal-internal/build.rs b/embassy-hal-internal/build.rs new file mode 100644 index 0000000..ecd2c0c --- /dev/null +++ b/embassy-hal-internal/build.rs @@ -0,0 +1,7 @@ +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); +} diff --git a/embassy-hal-internal/build_common.rs b/embassy-hal-internal/build_common.rs new file mode 100644 index 0000000..4f24e6d --- /dev/null +++ b/embassy-hal-internal/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy-hal-internal/src/atomic_ring_buffer.rs b/embassy-hal-internal/src/atomic_ring_buffer.rs new file mode 100644 index 0000000..00b7a12 --- /dev/null +++ b/embassy-hal-internal/src/atomic_ring_buffer.rs @@ -0,0 +1,604 @@ +//! Atomic reusable ringbuffer. +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; +use core::{ptr, slice}; + +/// Atomic reusable ringbuffer +/// +/// This ringbuffer implementation is designed to be stored in a `static`, +/// therefore all methods take `&self` and not `&mut self`. +/// +/// It is "reusable": when created it has no backing buffer, you can give it +/// one with `init` and take it back with `deinit`, and init it again in the +/// future if needed. This is very non-idiomatic, but helps a lot when storing +/// it in a `static`. +/// +/// One concurrent writer and one concurrent reader are supported, even at +/// different execution priorities (like main and irq). +pub struct RingBuffer { + #[doc(hidden)] + pub buf: AtomicPtr, + len: AtomicUsize, + + // start and end wrap at len*2, not at len. + // This allows distinguishing "full" and "empty". + // full is when start+len == end (modulo len*2) + // empty is when start == end + // + // This avoids having to consider the ringbuffer "full" at len-1 instead of len. + // The usual solution is adding a "full" flag, but that can't be made atomic + #[doc(hidden)] + pub start: AtomicUsize, + #[doc(hidden)] + pub end: AtomicUsize, +} + +/// A type which can only read from a ring buffer. +pub struct Reader<'a>(&'a RingBuffer); + +/// A type which can only write to a ring buffer. +pub struct Writer<'a>(&'a RingBuffer); + +impl RingBuffer { + /// Create a new empty ringbuffer. + pub const fn new() -> Self { + Self { + buf: AtomicPtr::new(core::ptr::null_mut()), + len: AtomicUsize::new(0), + start: AtomicUsize::new(0), + end: AtomicUsize::new(0), + } + } + + /// Initialize the ring buffer with a buffer. + /// + /// # Safety + /// - The buffer (`buf .. buf+len`) must be valid memory until `deinit` is called. + /// - Must not be called concurrently with any other methods. + pub unsafe fn init(&self, buf: *mut u8, len: usize) { + // Ordering: it's OK to use `Relaxed` because this is not called + // concurrently with other methods. + self.buf.store(buf, Ordering::Relaxed); + self.len.store(len, Ordering::Relaxed); + self.start.store(0, Ordering::Relaxed); + self.end.store(0, Ordering::Relaxed); + } + + /// Deinitialize the ringbuffer. + /// + /// After calling this, the ringbuffer becomes empty, as if it was + /// just created with `new()`. + /// + /// # Safety + /// - Must not be called concurrently with any other methods. + pub unsafe fn deinit(&self) { + // Ordering: it's OK to use `Relaxed` because this is not called + // concurrently with other methods. + self.buf.store(ptr::null_mut(), Ordering::Relaxed); + self.len.store(0, Ordering::Relaxed); + self.start.store(0, Ordering::Relaxed); + self.end.store(0, Ordering::Relaxed); + } + + /// Create a reader. + /// + /// # Safety + /// + /// - Only one reader can exist at a time. + /// - Ringbuffer must be initialized. + pub unsafe fn reader(&self) -> Reader<'_> { + Reader(self) + } + + /// Try creating a reader, fails if not initialized. + /// + /// # Safety + /// + /// Only one reader can exist at a time. + pub unsafe fn try_reader(&self) -> Option> { + if self.buf.load(Ordering::Relaxed).is_null() { + return None; + } + Some(Reader(self)) + } + + /// Create a writer. + /// + /// # Safety + /// + /// - Only one writer can exist at a time. + /// - Ringbuffer must be initialized. + pub unsafe fn writer(&self) -> Writer<'_> { + Writer(self) + } + + /// Try creating a writer, fails if not initialized. + /// + /// # Safety + /// + /// Only one writer can exist at a time. + pub unsafe fn try_writer(&self) -> Option> { + if self.buf.load(Ordering::Relaxed).is_null() { + return None; + } + Some(Writer(self)) + } + + /// Return if buffer is available. + pub fn is_available(&self) -> bool { + !self.buf.load(Ordering::Relaxed).is_null() && self.len.load(Ordering::Relaxed) != 0 + } + + /// Return length of buffer. + pub fn len(&self) -> usize { + self.len.load(Ordering::Relaxed) + } + + /// Check if buffer is full. + pub fn is_full(&self) -> bool { + let len = self.len.load(Ordering::Relaxed); + let start = self.start.load(Ordering::Relaxed); + let end = self.end.load(Ordering::Relaxed); + + self.wrap(start + len) == end + } + + /// Check if buffer is empty. + pub fn is_empty(&self) -> bool { + let start = self.start.load(Ordering::Relaxed); + let end = self.end.load(Ordering::Relaxed); + + start == end + } + + fn wrap(&self, mut n: usize) -> usize { + let len = self.len.load(Ordering::Relaxed); + + if n >= len * 2 { + n -= len * 2 + } + n + } +} + +impl<'a> Writer<'a> { + /// Push data into the buffer in-place. + /// + /// The closure `f` is called with a free part of the buffer, it must write + /// some data to it and return the amount of bytes written. + pub fn push(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize { + let (p, n) = self.push_buf(); + let buf = unsafe { slice::from_raw_parts_mut(p, n) }; + let n = f(buf); + self.push_done(n); + n + } + + /// Push one data byte. + /// + /// Returns true if pushed successfully. + pub fn push_one(&mut self, val: u8) -> bool { + let n = self.push(|f| match f { + [] => 0, + [x, ..] => { + *x = val; + 1 + } + }); + n != 0 + } + + /// Get a buffer where data can be pushed to. + /// + /// Equivalent to [`Self::push_buf`] but returns a slice. + pub fn push_slice(&mut self) -> &mut [u8] { + let (data, len) = self.push_buf(); + unsafe { slice::from_raw_parts_mut(data, len) } + } + + /// Get up to two buffers where data can be pushed to. + /// + /// Equivalent to [`Self::push_bufs`] but returns slices. + pub fn push_slices(&mut self) -> [&mut [u8]; 2] { + let [(d0, l0), (d1, l1)] = self.push_bufs(); + unsafe { [slice::from_raw_parts_mut(d0, l0), slice::from_raw_parts_mut(d1, l1)] } + } + + /// Get a buffer where data can be pushed to. + /// + /// Write data to the start of the buffer, then call `push_done` with + /// however many bytes you've pushed. + /// + /// The buffer is suitable to DMA to. + /// + /// If the ringbuf is full, size=0 will be returned. + /// + /// The buffer stays valid as long as no other `Writer` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn push_buf(&mut self) -> (*mut u8, usize) { + // Ordering: popping writes `start` last, so we read `start` first. + // Read it with Acquire ordering, so that the next accesses can't be reordered up past it. + let mut start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut end = self.0.end.load(Ordering::Relaxed); + + let empty = start == end; + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + if start == end && !empty { + // full + return (buf, 0); + } + let n = if start > end { start - end } else { len - end }; + + trace!(" ringbuf: push_buf {:?}..{:?}", end, end + n); + (unsafe { buf.add(end) }, n) + } + + /// Get up to two buffers where data can be pushed to. + /// + /// Write data starting at the beginning of the first buffer, then call + /// `push_done` with however many bytes you've pushed. + /// + /// The buffers are suitable to DMA to. + /// + /// If the ringbuf is full, both buffers will be zero length. + /// If there is only area available, the second buffer will be zero length. + /// + /// The buffer stays valid as long as no other `Writer` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn push_bufs(&mut self) -> [(*mut u8, usize); 2] { + // Ordering: as per push_buf() + let mut start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut end = self.0.end.load(Ordering::Relaxed); + + let empty = start == end; + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + if start == end && !empty { + // full + return [(buf, 0), (buf, 0)]; + } + let n0 = if start > end { start - end } else { len - end }; + let n1 = if start <= end { start } else { 0 }; + + trace!(" ringbuf: push_bufs [{:?}..{:?}, {:?}..{:?}]", end, end + n0, 0, n1); + [(unsafe { buf.add(end) }, n0), (buf, n1)] + } + + /// Mark n bytes as written and advance the write index. + pub fn push_done(&mut self, n: usize) { + trace!(" ringbuf: push {:?}", n); + let end = self.0.end.load(Ordering::Relaxed); + + // Ordering: write `end` last, with Release ordering. + // The ordering ensures no preceding memory accesses (such as writing + // the actual data in the buffer) can be reordered down past it, which + // will guarantee the reader sees them after reading from `end`. + self.0.end.store(self.0.wrap(end + n), Ordering::Release); + } +} + +impl<'a> Reader<'a> { + /// Pop data from the buffer in-place. + /// + /// The closure `f` is called with the next data, it must process + /// some data from it and return the amount of bytes processed. + pub fn pop(&mut self, f: impl FnOnce(&[u8]) -> usize) -> usize { + let (p, n) = self.pop_buf(); + let buf = unsafe { slice::from_raw_parts(p, n) }; + let n = f(buf); + self.pop_done(n); + n + } + + /// Pop one data byte. + /// + /// Returns true if popped successfully. + pub fn pop_one(&mut self) -> Option { + let mut res = None; + self.pop(|f| match f { + &[] => 0, + &[x, ..] => { + res = Some(x); + 1 + } + }); + res + } + + /// Get a buffer where data can be popped from. + /// + /// Equivalent to [`Self::pop_buf`] but returns a slice. + pub fn pop_slice(&mut self) -> &mut [u8] { + let (data, len) = self.pop_buf(); + unsafe { slice::from_raw_parts_mut(data, len) } + } + + /// Get a buffer where data can be popped from. + /// + /// Read data from the start of the buffer, then call `pop_done` with + /// however many bytes you've processed. + /// + /// The buffer is suitable to DMA from. + /// + /// If the ringbuf is empty, size=0 will be returned. + /// + /// The buffer stays valid as long as no other `Reader` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn pop_buf(&mut self) -> (*mut u8, usize) { + // Ordering: pushing writes `end` last, so we read `end` first. + // Read it with Acquire ordering, so that the next accesses can't be reordered up past it. + // This is needed to guarantee we "see" the data written by the writer. + let mut end = self.0.end.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut start = self.0.start.load(Ordering::Relaxed); + + if start == end { + return (buf, 0); + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", start, start + n); + (unsafe { buf.add(start) }, n) + } + + /// Mark n bytes as read and allow advance the read index. + pub fn pop_done(&mut self, n: usize) { + trace!(" ringbuf: pop {:?}", n); + + let start = self.0.start.load(Ordering::Relaxed); + + // Ordering: write `start` last, with Release ordering. + // The ordering ensures no preceding memory accesses (such as reading + // the actual data) can be reordered down past it. This is necessary + // because writing to `start` is effectively freeing the read part of the + // buffer, which "gives permission" to the writer to write to it again. + // Therefore, all buffer accesses must be completed before this. + self.0.start.store(self.0.wrap(start + n), Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_pop() { + let mut b = [0; 4]; + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), 4); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + assert_eq!(4, buf.len()); + buf[0] = 1; + buf[1] = 2; + buf[2] = 3; + buf[3] = 4; + 4 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + + rb.writer().push(|buf| { + // If it's full, we can push 0 bytes. + assert_eq!(0, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + + rb.reader().pop(|buf| { + assert_eq!(4, buf.len()); + assert_eq!(1, buf[0]); + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(2, buf[0]); + assert_eq!(3, buf[1]); + 2 + }); + rb.reader().pop(|buf| { + assert_eq!(1, buf.len()); + assert_eq!(4, buf[0]); + 1 + }); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + + rb.writer().push(|buf| { + assert_eq!(4, buf.len()); + buf[0] = 10; + 1 + }); + + rb.writer().push(|buf| { + assert_eq!(3, buf.len()); + buf[0] = 11; + buf[1] = 12; + 2 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + assert_eq!(1, buf.len()); + buf[0] = 13; + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + } + } + + #[test] + fn zero_len() { + let mut b = [0; 0]; + + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), b.len()); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), true); + + rb.writer().push(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + + rb.reader().pop(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + } + } + + #[test] + fn push_slices() { + let mut b = [0; 4]; + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), 4); + + /* push 3 -> [1 2 3 x] */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(4, ps[0].len()); + assert_eq!(0, ps[1].len()); + ps[0][0] = 1; + ps[0][1] = 2; + ps[0][2] = 3; + w.push_done(3); + drop(w); + + /* pop 2 -> [x x 3 x] */ + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(1, buf[0]); + assert_eq!(2, buf[1]); + assert_eq!(3, buf[2]); + 2 + }); + + /* push 3 -> [5 6 3 4] */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(1, ps[0].len()); + assert_eq!(2, ps[1].len()); + ps[0][0] = 4; + ps[1][0] = 5; + ps[1][1] = 6; + w.push_done(3); + drop(w); + + /* buf is now full */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(0, ps[0].len()); + assert_eq!(0, ps[1].len()); + + /* pop 2 -> [5 6 x x] */ + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(3, buf[0]); + assert_eq!(4, buf[1]); + 2 + }); + + /* should now have one push slice again */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(2, ps[0].len()); + assert_eq!(0, ps[1].len()); + drop(w); + + /* pop 2 -> [x x x x] */ + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(5, buf[0]); + assert_eq!(6, buf[1]); + 2 + }); + + /* should now have two push slices */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(2, ps[0].len()); + assert_eq!(2, ps[1].len()); + drop(w); + + /* make sure we exercise all wrap around cases properly */ + for _ in 0..10 { + /* should be empty, push 1 */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(4, ps[0].len() + ps[1].len()); + w.push_done(1); + drop(w); + + /* should have 1 element */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(3, ps[0].len() + ps[1].len()); + drop(w); + + /* pop 1 */ + rb.reader().pop(|buf| { + assert_eq!(1, buf.len()); + 1 + }); + } + } + } +} diff --git a/embassy-hal-internal/src/drop.rs b/embassy-hal-internal/src/drop.rs new file mode 100644 index 0000000..8383fcc --- /dev/null +++ b/embassy-hal-internal/src/drop.rs @@ -0,0 +1,56 @@ +//! Types for controlling when drop is invoked. +use core::mem; +use core::mem::MaybeUninit; + +/// A type to delay the drop handler invocation. +#[must_use = "to delay the drop handler invocation to the end of the scope"] +pub struct OnDrop { + f: MaybeUninit, +} + +impl OnDrop { + /// Create a new instance. + pub fn new(f: F) -> Self { + Self { f: MaybeUninit::new(f) } + } + + /// Prevent drop handler from running. + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } +} + +/// An explosive ordinance that panics if it is improperly disposed of. +/// +/// This is to forbid dropping futures, when there is absolutely no other choice. +/// +/// To correctly dispose of this device, call the [defuse](struct.DropBomb.html#method.defuse) +/// method before this object is dropped. +#[must_use = "to delay the drop bomb invokation to the end of the scope"] +pub struct DropBomb { + _private: (), +} + +impl DropBomb { + /// Create a new instance. + pub fn new() -> Self { + Self { _private: () } + } + + /// Defuses the bomb, rendering it safe to drop. + pub fn defuse(self) { + mem::forget(self) + } +} + +impl Drop for DropBomb { + fn drop(&mut self) { + panic!("boom") + } +} diff --git a/embassy-hal-internal/src/fmt.rs b/embassy-hal-internal/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-hal-internal/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-hal-internal/src/interrupt.rs b/embassy-hal-internal/src/interrupt.rs new file mode 100644 index 0000000..5e64dce --- /dev/null +++ b/embassy-hal-internal/src/interrupt.rs @@ -0,0 +1,870 @@ +//! Interrupt handling for cortex-m devices. +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; + +use cortex_m::interrupt::InterruptNumber; +use cortex_m::peripheral::NVIC; +use critical_section::CriticalSection; + +/// Generate a standard `mod interrupt` for a HAL. +#[macro_export] +macro_rules! interrupt_mod { + ($($irqs:ident),* $(,)?) => { + #[cfg(feature = "rt")] + pub use cortex_m_rt::interrupt; + + /// Interrupt definitions. + pub mod interrupt { + pub use $crate::interrupt::{InterruptExt, Priority}; + pub use crate::pac::Interrupt::*; + pub use crate::pac::Interrupt; + + /// Type-level interrupt infrastructure. + /// + /// This module contains one *type* per interrupt. This is used for checking at compile time that + /// the interrupts are correctly bound to HAL drivers. + /// + /// As an end user, you shouldn't need to use this module directly. Use the [`crate::bind_interrupts!`] macro + /// to bind interrupts, and the [`crate::interrupt`] module to manually register interrupt handlers and manipulate + /// interrupts directly (pending/unpending, enabling/disabling, setting the priority, etc...) + pub mod typelevel { + use super::InterruptExt; + + trait SealedInterrupt {} + + /// Type-level interrupt. + /// + /// This trait is implemented for all typelevel interrupt types in this module. + pub trait Interrupt: SealedInterrupt { + + /// Interrupt enum variant. + /// + /// This allows going from typelevel interrupts (one type per interrupt) to + /// non-typelevel interrupts (a single `Interrupt` enum type, with one variant per interrupt). + const IRQ: super::Interrupt; + + /// Enable the interrupt. + #[inline] + unsafe fn enable() { + Self::IRQ.enable() + } + + /// Disable the interrupt. + #[inline] + fn disable() { + Self::IRQ.disable() + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled() -> bool { + Self::IRQ.is_enabled() + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending() -> bool { + Self::IRQ.is_pending() + } + + /// Set interrupt pending. + #[inline] + fn pend() { + Self::IRQ.pend() + } + + /// Unset interrupt pending. + #[inline] + fn unpend() { + Self::IRQ.unpend() + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority() -> crate::interrupt::Priority { + Self::IRQ.get_priority() + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(prio: crate::interrupt::Priority) { + Self::IRQ.set_priority(prio) + } + + /// Set the interrupt priority with an already-acquired critical section + #[inline] + fn set_priority_with_cs(cs: critical_section::CriticalSection, prio: crate::interrupt::Priority) { + Self::IRQ.set_priority_with_cs(cs, prio) + } + } + + $( + #[allow(non_camel_case_types)] + #[doc=stringify!($irqs)] + #[doc=" typelevel interrupt."] + pub enum $irqs {} + impl SealedInterrupt for $irqs{} + impl Interrupt for $irqs { + const IRQ: super::Interrupt = super::Interrupt::$irqs; + } + )* + + /// Interrupt handler trait. + /// + /// Drivers that need to handle interrupts implement this trait. + /// The user must ensure `on_interrupt()` is called every time the interrupt fires. + /// Drivers must use use [`Binding`] to assert at compile time that the user has done so. + pub trait Handler { + /// Interrupt handler function. + /// + /// Must be called every time the `I` interrupt fires, synchronously from + /// the interrupt handler context. + /// + /// # Safety + /// + /// This function must ONLY be called from the interrupt handler for `I`. + unsafe fn on_interrupt(); + } + + /// Compile-time assertion that an interrupt has been bound to a handler. + /// + /// For the vast majority of cases, you should use the `bind_interrupts!` + /// macro instead of writing `unsafe impl`s of this trait. + /// + /// # Safety + /// + /// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()` + /// to be called every time the `I` interrupt fires. + /// + /// This allows drivers to check bindings at compile-time. + pub unsafe trait Binding> {} + } + } + }; +} + +/// Represents an interrupt type that can be configured by embassy to handle +/// interrupts. +pub unsafe trait InterruptExt: InterruptNumber + Copy { + /// Enable the interrupt. + #[inline] + unsafe fn enable(self) { + compiler_fence(Ordering::SeqCst); + NVIC::unmask(self) + } + + /// Disable the interrupt. + #[inline] + fn disable(self) { + NVIC::mask(self); + compiler_fence(Ordering::SeqCst); + } + + /// Check if interrupt is being handled. + #[inline] + #[cfg(not(armv6m))] + fn is_active(self) -> bool { + NVIC::is_active(self) + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled(self) -> bool { + NVIC::is_enabled(self) + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending(self) -> bool { + NVIC::is_pending(self) + } + + /// Set interrupt pending. + #[inline] + fn pend(self) { + NVIC::pend(self) + } + + /// Unset interrupt pending. + #[inline] + fn unpend(self) { + NVIC::unpend(self) + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority(self) -> Priority { + Priority::from(NVIC::get_priority(self)) + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(self, prio: Priority) { + unsafe { + let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(()); + + // On thumbv6, set_priority must do a RMW to change 8bit in a 32bit reg. + #[cfg(armv6m)] + critical_section::with(|_| nvic.set_priority(self, prio.into())); + // On thumbv7+, set_priority does an atomic 8bit write, so no CS needed. + #[cfg(not(armv6m))] + nvic.set_priority(self, prio.into()); + } + } + + /// Set the interrupt priority with an already-acquired critical section + /// + /// Equivalent to `set_priority`, except you pass a `CriticalSection` to prove + /// you've already acquired a critical section. This prevents acquiring another + /// one, which saves code size. + #[inline] + fn set_priority_with_cs(self, _cs: CriticalSection, prio: Priority) { + unsafe { + let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(()); + nvic.set_priority(self, prio.into()); + } + } +} + +unsafe impl InterruptExt for T {} + +impl From for Priority { + fn from(priority: u8) -> Self { + unsafe { mem::transmute(priority & PRIO_MASK) } + } +} + +impl From for u8 { + fn from(p: Priority) -> Self { + p as u8 + } +} + +#[cfg(feature = "prio-bits-0")] +const PRIO_MASK: u8 = 0x00; +#[cfg(feature = "prio-bits-1")] +const PRIO_MASK: u8 = 0x80; +#[cfg(feature = "prio-bits-2")] +const PRIO_MASK: u8 = 0xc0; +#[cfg(feature = "prio-bits-3")] +const PRIO_MASK: u8 = 0xe0; +#[cfg(feature = "prio-bits-4")] +const PRIO_MASK: u8 = 0xf0; +#[cfg(feature = "prio-bits-5")] +const PRIO_MASK: u8 = 0xf8; +#[cfg(feature = "prio-bits-6")] +const PRIO_MASK: u8 = 0xfc; +#[cfg(feature = "prio-bits-7")] +const PRIO_MASK: u8 = 0xfe; +#[cfg(feature = "prio-bits-8")] +const PRIO_MASK: u8 = 0xff; + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-0")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-1")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x80, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-2")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x40, + P2 = 0x80, + P3 = 0xc0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-3")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x20, + P2 = 0x40, + P3 = 0x60, + P4 = 0x80, + P5 = 0xa0, + P6 = 0xc0, + P7 = 0xe0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-4")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x10, + P2 = 0x20, + P3 = 0x30, + P4 = 0x40, + P5 = 0x50, + P6 = 0x60, + P7 = 0x70, + P8 = 0x80, + P9 = 0x90, + P10 = 0xa0, + P11 = 0xb0, + P12 = 0xc0, + P13 = 0xd0, + P14 = 0xe0, + P15 = 0xf0, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-5")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x8, + P2 = 0x10, + P3 = 0x18, + P4 = 0x20, + P5 = 0x28, + P6 = 0x30, + P7 = 0x38, + P8 = 0x40, + P9 = 0x48, + P10 = 0x50, + P11 = 0x58, + P12 = 0x60, + P13 = 0x68, + P14 = 0x70, + P15 = 0x78, + P16 = 0x80, + P17 = 0x88, + P18 = 0x90, + P19 = 0x98, + P20 = 0xa0, + P21 = 0xa8, + P22 = 0xb0, + P23 = 0xb8, + P24 = 0xc0, + P25 = 0xc8, + P26 = 0xd0, + P27 = 0xd8, + P28 = 0xe0, + P29 = 0xe8, + P30 = 0xf0, + P31 = 0xf8, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-6")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x4, + P2 = 0x8, + P3 = 0xc, + P4 = 0x10, + P5 = 0x14, + P6 = 0x18, + P7 = 0x1c, + P8 = 0x20, + P9 = 0x24, + P10 = 0x28, + P11 = 0x2c, + P12 = 0x30, + P13 = 0x34, + P14 = 0x38, + P15 = 0x3c, + P16 = 0x40, + P17 = 0x44, + P18 = 0x48, + P19 = 0x4c, + P20 = 0x50, + P21 = 0x54, + P22 = 0x58, + P23 = 0x5c, + P24 = 0x60, + P25 = 0x64, + P26 = 0x68, + P27 = 0x6c, + P28 = 0x70, + P29 = 0x74, + P30 = 0x78, + P31 = 0x7c, + P32 = 0x80, + P33 = 0x84, + P34 = 0x88, + P35 = 0x8c, + P36 = 0x90, + P37 = 0x94, + P38 = 0x98, + P39 = 0x9c, + P40 = 0xa0, + P41 = 0xa4, + P42 = 0xa8, + P43 = 0xac, + P44 = 0xb0, + P45 = 0xb4, + P46 = 0xb8, + P47 = 0xbc, + P48 = 0xc0, + P49 = 0xc4, + P50 = 0xc8, + P51 = 0xcc, + P52 = 0xd0, + P53 = 0xd4, + P54 = 0xd8, + P55 = 0xdc, + P56 = 0xe0, + P57 = 0xe4, + P58 = 0xe8, + P59 = 0xec, + P60 = 0xf0, + P61 = 0xf4, + P62 = 0xf8, + P63 = 0xfc, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-7")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x2, + P2 = 0x4, + P3 = 0x6, + P4 = 0x8, + P5 = 0xa, + P6 = 0xc, + P7 = 0xe, + P8 = 0x10, + P9 = 0x12, + P10 = 0x14, + P11 = 0x16, + P12 = 0x18, + P13 = 0x1a, + P14 = 0x1c, + P15 = 0x1e, + P16 = 0x20, + P17 = 0x22, + P18 = 0x24, + P19 = 0x26, + P20 = 0x28, + P21 = 0x2a, + P22 = 0x2c, + P23 = 0x2e, + P24 = 0x30, + P25 = 0x32, + P26 = 0x34, + P27 = 0x36, + P28 = 0x38, + P29 = 0x3a, + P30 = 0x3c, + P31 = 0x3e, + P32 = 0x40, + P33 = 0x42, + P34 = 0x44, + P35 = 0x46, + P36 = 0x48, + P37 = 0x4a, + P38 = 0x4c, + P39 = 0x4e, + P40 = 0x50, + P41 = 0x52, + P42 = 0x54, + P43 = 0x56, + P44 = 0x58, + P45 = 0x5a, + P46 = 0x5c, + P47 = 0x5e, + P48 = 0x60, + P49 = 0x62, + P50 = 0x64, + P51 = 0x66, + P52 = 0x68, + P53 = 0x6a, + P54 = 0x6c, + P55 = 0x6e, + P56 = 0x70, + P57 = 0x72, + P58 = 0x74, + P59 = 0x76, + P60 = 0x78, + P61 = 0x7a, + P62 = 0x7c, + P63 = 0x7e, + P64 = 0x80, + P65 = 0x82, + P66 = 0x84, + P67 = 0x86, + P68 = 0x88, + P69 = 0x8a, + P70 = 0x8c, + P71 = 0x8e, + P72 = 0x90, + P73 = 0x92, + P74 = 0x94, + P75 = 0x96, + P76 = 0x98, + P77 = 0x9a, + P78 = 0x9c, + P79 = 0x9e, + P80 = 0xa0, + P81 = 0xa2, + P82 = 0xa4, + P83 = 0xa6, + P84 = 0xa8, + P85 = 0xaa, + P86 = 0xac, + P87 = 0xae, + P88 = 0xb0, + P89 = 0xb2, + P90 = 0xb4, + P91 = 0xb6, + P92 = 0xb8, + P93 = 0xba, + P94 = 0xbc, + P95 = 0xbe, + P96 = 0xc0, + P97 = 0xc2, + P98 = 0xc4, + P99 = 0xc6, + P100 = 0xc8, + P101 = 0xca, + P102 = 0xcc, + P103 = 0xce, + P104 = 0xd0, + P105 = 0xd2, + P106 = 0xd4, + P107 = 0xd6, + P108 = 0xd8, + P109 = 0xda, + P110 = 0xdc, + P111 = 0xde, + P112 = 0xe0, + P113 = 0xe2, + P114 = 0xe4, + P115 = 0xe6, + P116 = 0xe8, + P117 = 0xea, + P118 = 0xec, + P119 = 0xee, + P120 = 0xf0, + P121 = 0xf2, + P122 = 0xf4, + P123 = 0xf6, + P124 = 0xf8, + P125 = 0xfa, + P126 = 0xfc, + P127 = 0xfe, +} + +/// The interrupt priority level. +/// +/// NOTE: The contents of this enum differ according to the set `prio-bits-*` Cargo feature. +#[cfg(feature = "prio-bits-8")] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +#[allow(missing_docs)] +pub enum Priority { + P0 = 0x0, + P1 = 0x1, + P2 = 0x2, + P3 = 0x3, + P4 = 0x4, + P5 = 0x5, + P6 = 0x6, + P7 = 0x7, + P8 = 0x8, + P9 = 0x9, + P10 = 0xa, + P11 = 0xb, + P12 = 0xc, + P13 = 0xd, + P14 = 0xe, + P15 = 0xf, + P16 = 0x10, + P17 = 0x11, + P18 = 0x12, + P19 = 0x13, + P20 = 0x14, + P21 = 0x15, + P22 = 0x16, + P23 = 0x17, + P24 = 0x18, + P25 = 0x19, + P26 = 0x1a, + P27 = 0x1b, + P28 = 0x1c, + P29 = 0x1d, + P30 = 0x1e, + P31 = 0x1f, + P32 = 0x20, + P33 = 0x21, + P34 = 0x22, + P35 = 0x23, + P36 = 0x24, + P37 = 0x25, + P38 = 0x26, + P39 = 0x27, + P40 = 0x28, + P41 = 0x29, + P42 = 0x2a, + P43 = 0x2b, + P44 = 0x2c, + P45 = 0x2d, + P46 = 0x2e, + P47 = 0x2f, + P48 = 0x30, + P49 = 0x31, + P50 = 0x32, + P51 = 0x33, + P52 = 0x34, + P53 = 0x35, + P54 = 0x36, + P55 = 0x37, + P56 = 0x38, + P57 = 0x39, + P58 = 0x3a, + P59 = 0x3b, + P60 = 0x3c, + P61 = 0x3d, + P62 = 0x3e, + P63 = 0x3f, + P64 = 0x40, + P65 = 0x41, + P66 = 0x42, + P67 = 0x43, + P68 = 0x44, + P69 = 0x45, + P70 = 0x46, + P71 = 0x47, + P72 = 0x48, + P73 = 0x49, + P74 = 0x4a, + P75 = 0x4b, + P76 = 0x4c, + P77 = 0x4d, + P78 = 0x4e, + P79 = 0x4f, + P80 = 0x50, + P81 = 0x51, + P82 = 0x52, + P83 = 0x53, + P84 = 0x54, + P85 = 0x55, + P86 = 0x56, + P87 = 0x57, + P88 = 0x58, + P89 = 0x59, + P90 = 0x5a, + P91 = 0x5b, + P92 = 0x5c, + P93 = 0x5d, + P94 = 0x5e, + P95 = 0x5f, + P96 = 0x60, + P97 = 0x61, + P98 = 0x62, + P99 = 0x63, + P100 = 0x64, + P101 = 0x65, + P102 = 0x66, + P103 = 0x67, + P104 = 0x68, + P105 = 0x69, + P106 = 0x6a, + P107 = 0x6b, + P108 = 0x6c, + P109 = 0x6d, + P110 = 0x6e, + P111 = 0x6f, + P112 = 0x70, + P113 = 0x71, + P114 = 0x72, + P115 = 0x73, + P116 = 0x74, + P117 = 0x75, + P118 = 0x76, + P119 = 0x77, + P120 = 0x78, + P121 = 0x79, + P122 = 0x7a, + P123 = 0x7b, + P124 = 0x7c, + P125 = 0x7d, + P126 = 0x7e, + P127 = 0x7f, + P128 = 0x80, + P129 = 0x81, + P130 = 0x82, + P131 = 0x83, + P132 = 0x84, + P133 = 0x85, + P134 = 0x86, + P135 = 0x87, + P136 = 0x88, + P137 = 0x89, + P138 = 0x8a, + P139 = 0x8b, + P140 = 0x8c, + P141 = 0x8d, + P142 = 0x8e, + P143 = 0x8f, + P144 = 0x90, + P145 = 0x91, + P146 = 0x92, + P147 = 0x93, + P148 = 0x94, + P149 = 0x95, + P150 = 0x96, + P151 = 0x97, + P152 = 0x98, + P153 = 0x99, + P154 = 0x9a, + P155 = 0x9b, + P156 = 0x9c, + P157 = 0x9d, + P158 = 0x9e, + P159 = 0x9f, + P160 = 0xa0, + P161 = 0xa1, + P162 = 0xa2, + P163 = 0xa3, + P164 = 0xa4, + P165 = 0xa5, + P166 = 0xa6, + P167 = 0xa7, + P168 = 0xa8, + P169 = 0xa9, + P170 = 0xaa, + P171 = 0xab, + P172 = 0xac, + P173 = 0xad, + P174 = 0xae, + P175 = 0xaf, + P176 = 0xb0, + P177 = 0xb1, + P178 = 0xb2, + P179 = 0xb3, + P180 = 0xb4, + P181 = 0xb5, + P182 = 0xb6, + P183 = 0xb7, + P184 = 0xb8, + P185 = 0xb9, + P186 = 0xba, + P187 = 0xbb, + P188 = 0xbc, + P189 = 0xbd, + P190 = 0xbe, + P191 = 0xbf, + P192 = 0xc0, + P193 = 0xc1, + P194 = 0xc2, + P195 = 0xc3, + P196 = 0xc4, + P197 = 0xc5, + P198 = 0xc6, + P199 = 0xc7, + P200 = 0xc8, + P201 = 0xc9, + P202 = 0xca, + P203 = 0xcb, + P204 = 0xcc, + P205 = 0xcd, + P206 = 0xce, + P207 = 0xcf, + P208 = 0xd0, + P209 = 0xd1, + P210 = 0xd2, + P211 = 0xd3, + P212 = 0xd4, + P213 = 0xd5, + P214 = 0xd6, + P215 = 0xd7, + P216 = 0xd8, + P217 = 0xd9, + P218 = 0xda, + P219 = 0xdb, + P220 = 0xdc, + P221 = 0xdd, + P222 = 0xde, + P223 = 0xdf, + P224 = 0xe0, + P225 = 0xe1, + P226 = 0xe2, + P227 = 0xe3, + P228 = 0xe4, + P229 = 0xe5, + P230 = 0xe6, + P231 = 0xe7, + P232 = 0xe8, + P233 = 0xe9, + P234 = 0xea, + P235 = 0xeb, + P236 = 0xec, + P237 = 0xed, + P238 = 0xee, + P239 = 0xef, + P240 = 0xf0, + P241 = 0xf1, + P242 = 0xf2, + P243 = 0xf3, + P244 = 0xf4, + P245 = 0xf5, + P246 = 0xf6, + P247 = 0xf7, + P248 = 0xf8, + P249 = 0xf9, + P250 = 0xfa, + P251 = 0xfb, + P252 = 0xfc, + P253 = 0xfd, + P254 = 0xfe, + P255 = 0xff, +} diff --git a/embassy-hal-internal/src/lib.rs b/embassy-hal-internal/src/lib.rs new file mode 100644 index 0000000..89f20e9 --- /dev/null +++ b/embassy-hal-internal/src/lib.rs @@ -0,0 +1,17 @@ +#![no_std] +#![allow(clippy::new_without_default)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +pub mod atomic_ring_buffer; +pub mod drop; +mod macros; +mod peripheral; +pub mod ratio; +pub use peripheral::{Peripheral, PeripheralRef}; + +#[cfg(feature = "cortex-m")] +pub mod interrupt; diff --git a/embassy-hal-internal/src/macros.rs b/embassy-hal-internal/src/macros.rs new file mode 100644 index 0000000..07cd894 --- /dev/null +++ b/embassy-hal-internal/src/macros.rs @@ -0,0 +1,135 @@ +/// Types for the peripheral singletons. +#[macro_export] +macro_rules! peripherals_definition { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// Types for the peripheral singletons. + pub mod peripherals { + $( + $(#[$cfg])? + #[allow(non_camel_case_types)] + #[doc = concat!(stringify!($name), " peripheral")] + pub struct $name { _private: () } + + $(#[$cfg])? + impl $name { + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn steal() -> Self { + Self{ _private: ()} + } + } + + $(#[$cfg])? + $crate::impl_peripheral!($name); + )* + } + }; +} + +/// Define the peripherals struct. +#[macro_export] +macro_rules! peripherals_struct { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// Struct containing all the peripheral singletons. + /// + /// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]. + #[allow(non_snake_case)] + pub struct Peripherals { + $( + #[doc = concat!(stringify!($name), " peripheral")] + $(#[$cfg])? + pub $name: peripherals::$name, + )* + } + + impl Peripherals { + ///Returns all the peripherals *once* + #[inline] + pub(crate) fn take() -> Self { + critical_section::with(Self::take_with_cs) + } + + ///Returns all the peripherals *once* + #[inline] + pub(crate) fn take_with_cs(_cs: critical_section::CriticalSection) -> Self { + #[no_mangle] + static mut _EMBASSY_DEVICE_PERIPHERALS: bool = false; + + // safety: OK because we're inside a CS. + unsafe { + if _EMBASSY_DEVICE_PERIPHERALS { + panic!("init called more than once!") + } + _EMBASSY_DEVICE_PERIPHERALS = true; + Self::steal() + } + } + } + + impl Peripherals { + /// Unsafely create an instance of this peripheral out of thin air. + /// + /// # Safety + /// + /// You must ensure that you're only using one instance of this type at a time. + #[inline] + pub unsafe fn steal() -> Self { + Self { + $( + $(#[$cfg])? + $name: peripherals::$name::steal(), + )* + } + } + } + }; +} + +/// Defining peripheral type. +#[macro_export] +macro_rules! peripherals { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + $crate::peripherals_definition!( + $( + $(#[$cfg])? + $name, + )* + ); + $crate::peripherals_struct!( + $( + $(#[$cfg])? + $name, + )* + ); + }; +} + +/// Convenience converting into reference. +#[macro_export] +macro_rules! into_ref { + ($($name:ident),*) => { + $( + let mut $name = $name.into_ref(); + )* + } +} + +/// Implement the peripheral trait. +#[macro_export] +macro_rules! impl_peripheral { + ($type:ident) => { + impl $crate::Peripheral for $type { + type P = $type; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + #[allow(clippy::needless_update)] + $type { ..*self } + } + } + }; +} diff --git a/embassy-hal-internal/src/peripheral.rs b/embassy-hal-internal/src/peripheral.rs new file mode 100644 index 0000000..0b0f133 --- /dev/null +++ b/embassy-hal-internal/src/peripheral.rs @@ -0,0 +1,177 @@ +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; + +/// An exclusive reference to a peripheral. +/// +/// This is functionally the same as a `&'a mut T`. There's a few advantages in having +/// a dedicated struct instead: +/// +/// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete +/// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte). +/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. +/// PeripheralRef stores a copy of `T` instead, so it's the same size. +/// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`, +/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic +/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes +/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization. +pub struct PeripheralRef<'a, T> { + inner: T, + _lifetime: PhantomData<&'a mut T>, +} + +impl<'a, T> PeripheralRef<'a, T> { + /// Create a new reference to a peripheral. + #[inline] + pub fn new(inner: T) -> Self { + Self { + inner, + _lifetime: PhantomData, + } + } + + /// Unsafely clone (duplicate) a peripheral singleton. + /// + /// # Safety + /// + /// This returns an owned clone of the peripheral. You must manually ensure + /// only one copy of the peripheral is in use at a time. For example, don't + /// create two SPI drivers on `SPI1`, because they will "fight" each other. + /// + /// You should strongly prefer using `reborrow()` instead. It returns a + /// `PeripheralRef` that borrows `self`, which allows the borrow checker + /// to enforce this at compile time. + pub unsafe fn clone_unchecked(&self) -> PeripheralRef<'a, T> + where + T: Peripheral

, + { + PeripheralRef::new(self.inner.clone_unchecked()) + } + + /// Reborrow into a "child" PeripheralRef. + /// + /// `self` will stay borrowed until the child PeripheralRef is dropped. + pub fn reborrow(&mut self) -> PeripheralRef<'_, T> + where + T: Peripheral

, + { + // safety: we're returning the clone inside a new PeripheralRef that borrows + // self, so user code can't use both at the same time. + PeripheralRef::new(unsafe { self.inner.clone_unchecked() }) + } + + /// Map the inner peripheral using `Into`. + /// + /// This converts from `PeripheralRef<'a, T>` to `PeripheralRef<'a, U>`, using an + /// `Into` impl to convert from `T` to `U`. + /// + /// For example, this can be useful to degrade GPIO pins: converting from PeripheralRef<'a, PB11>` to `PeripheralRef<'a, AnyPin>`. + #[inline] + pub fn map_into(self) -> PeripheralRef<'a, U> + where + T: Into, + { + PeripheralRef { + inner: self.inner.into(), + _lifetime: PhantomData, + } + } +} + +impl<'a, T> Deref for PeripheralRef<'a, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +/// Trait for any type that can be used as a peripheral of type `P`. +/// +/// This is used in driver constructors, to allow passing either owned peripherals (e.g. `TWISPI0`), +/// or borrowed peripherals (e.g. `&mut TWISPI0`). +/// +/// For example, if you have a driver with a constructor like this: +/// +/// ```ignore +/// impl<'d, T: Instance> Twim<'d, T> { +/// pub fn new( +/// twim: impl Peripheral

+ 'd, +/// irq: impl Peripheral

+ 'd, +/// sda: impl Peripheral

+ 'd, +/// scl: impl Peripheral

+ 'd, +/// config: Config, +/// ) -> Self { .. } +/// } +/// ``` +/// +/// You may call it with owned peripherals, which yields an instance that can live forever (`'static`): +/// +/// ```ignore +/// let mut twi: Twim<'static, ...> = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config); +/// ``` +/// +/// Or you may call it with borrowed peripherals, which yields an instance that can only live for as long +/// as the borrows last: +/// +/// ```ignore +/// let mut twi: Twim<'_, ...> = Twim::new(&mut p.TWISPI0, &mut irq, &mut p.P0_03, &mut p.P0_04, config); +/// ``` +/// +/// # Implementation details, for HAL authors +/// +/// When writing a HAL, the intended way to use this trait is to take `impl Peripheral

` in +/// the HAL's public API (such as driver constructors), calling `.into_ref()` to obtain a `PeripheralRef`, +/// and storing that in the driver struct. +/// +/// `.into_ref()` on an owned `T` yields a `PeripheralRef<'static, T>`. +/// `.into_ref()` on an `&'a mut T` yields a `PeripheralRef<'a, T>`. +pub trait Peripheral: Sized { + /// Peripheral singleton type + type P; + + /// Unsafely clone (duplicate) a peripheral singleton. + /// + /// # Safety + /// + /// This returns an owned clone of the peripheral. You must manually ensure + /// only one copy of the peripheral is in use at a time. For example, don't + /// create two SPI drivers on `SPI1`, because they will "fight" each other. + /// + /// You should strongly prefer using `into_ref()` instead. It returns a + /// `PeripheralRef`, which allows the borrow checker to enforce this at compile time. + unsafe fn clone_unchecked(&self) -> Self::P; + + /// Convert a value into a `PeripheralRef`. + /// + /// When called on an owned `T`, yields a `PeripheralRef<'static, T>`. + /// When called on an `&'a mut T`, yields a `PeripheralRef<'a, T>`. + #[inline] + fn into_ref<'a>(self) -> PeripheralRef<'a, Self::P> + where + Self: 'a, + { + PeripheralRef::new(unsafe { self.clone_unchecked() }) + } +} + +impl<'b, T: DerefMut> Peripheral for T +where + T::Target: Peripheral, +{ + type P = ::P; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + T::Target::clone_unchecked(self) + } +} + +impl<'b, T: Peripheral> Peripheral for PeripheralRef<'_, T> { + type P = T::P; + + #[inline] + unsafe fn clone_unchecked(&self) -> Self::P { + T::clone_unchecked(self) + } +} diff --git a/embassy-hal-internal/src/ratio.rs b/embassy-hal-internal/src/ratio.rs new file mode 100644 index 0000000..91dcfd4 --- /dev/null +++ b/embassy-hal-internal/src/ratio.rs @@ -0,0 +1,130 @@ +//! Types for dealing with rational numbers. +use core::ops::{Add, Div, Mul}; + +use num_traits::{CheckedAdd, CheckedDiv, CheckedMul}; + +/// Represents the ratio between two numbers. +#[derive(Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Ratio { + /// Numerator. + numer: T, + /// Denominator. + denom: T, +} + +impl Ratio { + /// Creates a new `Ratio`. + #[inline(always)] + pub const fn new_raw(numer: T, denom: T) -> Ratio { + Ratio { numer, denom } + } + + /// Gets an immutable reference to the numerator. + #[inline(always)] + pub const fn numer(&self) -> &T { + &self.numer + } + + /// Gets an immutable reference to the denominator. + #[inline(always)] + pub const fn denom(&self) -> &T { + &self.denom + } +} + +impl Ratio { + /// Converts to an integer, rounding towards zero. + #[inline(always)] + pub fn to_integer(&self) -> T { + unwrap!(self.numer().checked_div(self.denom())) + } +} + +impl Div for Ratio { + type Output = Self; + + #[inline(always)] + fn div(mut self, rhs: T) -> Self::Output { + self.denom = unwrap!(self.denom().checked_mul(&rhs)); + self + } +} + +impl Mul for Ratio { + type Output = Self; + + #[inline(always)] + fn mul(mut self, rhs: T) -> Self::Output { + self.numer = unwrap!(self.numer().checked_mul(&rhs)); + self + } +} + +impl Add for Ratio { + type Output = Self; + + #[inline(always)] + fn add(mut self, rhs: T) -> Self::Output { + self.numer = unwrap!(unwrap!(self.denom().checked_mul(&rhs)).checked_add(self.numer())); + self + } +} + +macro_rules! impl_from_for_float { + ($from:ident) => { + impl From> for f32 { + #[inline(always)] + fn from(r: Ratio<$from>) -> Self { + (r.numer as f32) / (r.denom as f32) + } + } + + impl From> for f64 { + #[inline(always)] + fn from(r: Ratio<$from>) -> Self { + (r.numer as f64) / (r.denom as f64) + } + } + }; +} + +impl_from_for_float!(u8); +impl_from_for_float!(u16); +impl_from_for_float!(u32); +impl_from_for_float!(u64); +impl_from_for_float!(u128); +impl_from_for_float!(i8); +impl_from_for_float!(i16); +impl_from_for_float!(i32); +impl_from_for_float!(i64); +impl_from_for_float!(i128); + +impl core::fmt::Display for Ratio { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::write!(f, "{} / {}", self.numer(), self.denom()) + } +} + +#[cfg(test)] +mod tests { + use super::Ratio; + + #[test] + fn basics() { + let mut r = Ratio::new_raw(1, 2) + 2; + assert_eq!(*r.numer(), 5); + assert_eq!(*r.denom(), 2); + assert_eq!(r.to_integer(), 2); + + r = r * 2; + assert_eq!(*r.numer(), 10); + assert_eq!(*r.denom(), 2); + assert_eq!(r.to_integer(), 5); + + r = r / 2; + assert_eq!(*r.numer(), 10); + assert_eq!(*r.denom(), 4); + assert_eq!(r.to_integer(), 2); + } +} diff --git a/embassy-net-driver-channel/CHANGELOG.md b/embassy-net-driver-channel/CHANGELOG.md new file mode 100644 index 0000000..d7af7e5 --- /dev/null +++ b/embassy-net-driver-channel/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +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 + +## 0.3.0 - 2024-08-05 + +- Add collapse_debuginfo to fmt.rs macros. +- Update embassy-sync version + +## 0.2.0 - 2023-10-18 + +- Update `embassy-net-driver` to v0.2 +- `Runner::new` now takes an `embassy_net_driver::HardwareAddress` parameter. +- `Runner::set_ethernet_address` is now `set_hardware_address`. + +## 0.1.0 - 2023-06-29 + +- First release diff --git a/embassy-net-driver-channel/Cargo.toml b/embassy-net-driver-channel/Cargo.toml new file mode 100644 index 0000000..5165621 --- /dev/null +++ b/embassy-net-driver-channel/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "embassy-net-driver-channel" +version = "0.3.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-driver-channel" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy-sync = { version = "0.6.2", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } diff --git a/embassy-net-driver-channel/README.md b/embassy-net-driver-channel/README.md new file mode 100644 index 0000000..1955624 --- /dev/null +++ b/embassy-net-driver-channel/README.md @@ -0,0 +1,86 @@ +# embassy-net-driver-channel + +This crate provides a toolkit for implementing [`embassy-net`](https://crates.io/crates/embassy-net) drivers in a +higher level way than implementing the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) trait directly. + +The `embassy-net-driver` trait is polling-based. To implement it, you must write the packet receive/transmit state machines by +hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net` +knows when to poll your driver again to make more progress. + +With `embassy-net-driver-channel` you get a "channel-like" interface instead, where you can send/receive packets +to/from embassy-net. The intended usage is to spawn a "driver task" in the background that does this, passing +packets between the hardware and the channel. + +## A note about deadlocks + +When implementing a driver using this crate, it might be tempting to write it in the most straightforward way: + +```rust,ignore +loop { + // Wait for either.. + match select( + // ... the chip signaling an interrupt, indicating a packet is available to receive, or + irq_pin.wait_for_low(), + // ... a TX buffer becoming available, i.e. embassy-net wants to send a packet + tx_chan.tx_buf(), + ).await { + Either::First(_) => { + // a packet is ready to be received! + let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue + let n = receive_packet_over_spi(buf).await; + rx_chan.rx_done(n); + } + Either::Second(buf) => { + // a packet is ready to be sent! + send_packet_over_spi(buf).await; + tx_chan.tx_done(); + } + } +} +``` + +However, this code has a latent deadlock bug. The symptom is it can hang at `rx_chan.rx_buf().await` under load. + +The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue. + +The fix is to make sure to always service the TX queue while you're waiting for space to become available in the RX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available": + +```rust,ignore +loop { + // Wait for either.. + match select( + async { + // ... the chip signaling an interrupt, indicating a packet is available to receive + irq_pin.wait_for_low().await; + // *AND* the buffer is ready... + rx_chan.rx_buf().await + }, + // ... or a TX buffer becoming available, i.e. embassy-net wants to send a packet + tx_chan.tx_buf(), + ).await { + Either::First(buf) => { + // a packet is ready to be received! + let n = receive_packet_over_spi(buf).await; + rx_chan.rx_done(n); + } + Either::Second(buf) => { + // a packet is ready to be sent! + send_packet_over_spi(buf).await; + tx_chan.tx_done(); + } + } +} +``` + +## Examples + +These `embassy-net` drivers are implemented using this crate. You can look at them for inspiration. + +- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W +- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. +- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips. +- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-net-driver-channel/src/fmt.rs b/embassy-net-driver-channel/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-net-driver-channel/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-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs new file mode 100644 index 0000000..600efd9 --- /dev/null +++ b/embassy-net-driver-channel/src/lib.rs @@ -0,0 +1,410 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// must go first! +mod fmt; + +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::task::{Context, Poll}; + +pub use embassy_net_driver as driver; +use embassy_net_driver::{Capabilities, LinkState}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_sync::zerocopy_channel; + +/// Channel state. +/// +/// Holds a buffer of packets with size MTU, for both TX and RX. +pub struct State { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl State { + /// Create a new channel state. + pub const fn new() -> Self { + Self { + rx: [const { PacketBuf::new() }; N_RX], + tx: [const { PacketBuf::new() }; N_TX], + inner: MaybeUninit::uninit(), + } + } +} + +struct StateInner<'d, const MTU: usize> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + shared: Mutex>, +} + +struct Shared { + link_state: LinkState, + waker: WakerRegistration, + hardware_address: driver::HardwareAddress, +} + +/// Channel runner. +/// +/// Holds the shared state and the lower end of channels for inbound and outbound packets. +pub struct Runner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, +} + +/// State runner. +/// +/// Holds the shared state of the channel such as link state. +#[derive(Clone, Copy)] +pub struct StateRunner<'d> { + shared: &'d Mutex>, +} + +/// RX runner. +/// +/// Holds the lower end of the channel for passing inbound packets up the stack. +pub struct RxRunner<'d, const MTU: usize> { + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, +} + +/// TX runner. +/// +/// Holds the lower end of the channel for passing outbound packets down the stack. +pub struct TxRunner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, +} + +impl<'d, const MTU: usize> Runner<'d, MTU> { + /// Split the runner into separate runners for controlling state, rx and tx. + pub fn split(self) -> (StateRunner<'d>, RxRunner<'d, MTU>, TxRunner<'d, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { rx_chan: self.rx_chan }, + TxRunner { tx_chan: self.tx_chan }, + ) + } + + /// Split the runner into separate runners for controlling state, rx and tx borrowing the underlying state. + pub fn borrow_split(&mut self) -> (StateRunner<'_>, RxRunner<'_, MTU>, TxRunner<'_, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { + rx_chan: self.rx_chan.borrow(), + }, + TxRunner { + tx_chan: self.tx_chan.borrow(), + }, + ) + } + + /// Create a state runner sharing the state channel. + pub fn state_runner(&self) -> StateRunner<'d> { + StateRunner { shared: self.shared } + } + + /// Set the link state. + pub fn set_link_state(&mut self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + /// Set the hardware address. + pub fn set_hardware_address(&mut self, address: driver::HardwareAddress) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.hardware_address = address; + s.waker.wake(); + }); + } + + /// Wait until there is space for more inbound packets and return a slice they can be copied into. + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + /// Check if there is space for more inbound packets right now. + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + /// Polling the inbound channel if there is space for packets. + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + /// Mark packet of len bytes as pushed to the inbound channel. + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } + + /// Wait until there is space for more outbound packets and return a slice they can be copied into. + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.receive().await; + &mut p.buf[..p.len] + } + + /// Check if there is space for more outbound packets right now. + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_receive()?; + Some(&mut p.buf[..p.len]) + } + + /// Polling the outbound channel if there is space for packets. + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_receive(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + /// Mark outbound packet as copied. + pub fn tx_done(&mut self) { + self.tx_chan.receive_done(); + } +} + +impl<'d> StateRunner<'d> { + /// Set link state. + pub fn set_link_state(&self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + /// Set the hardware address. + pub fn set_hardware_address(&self, address: driver::HardwareAddress) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.hardware_address = address; + s.waker.wake(); + }); + } +} + +impl<'d, const MTU: usize> RxRunner<'d, MTU> { + /// Wait until there is space for more inbound packets and return a slice they can be copied into. + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + /// Check if there is space for more inbound packets right now. + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + /// Polling the inbound channel if there is space for packets. + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + /// Mark packet of len bytes as pushed to the inbound channel. + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } +} + +impl<'d, const MTU: usize> TxRunner<'d, MTU> { + /// Wait until there is space for more outbound packets and return a slice they can be copied into. + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.receive().await; + &mut p.buf[..p.len] + } + + /// Check if there is space for more outbound packets right now. + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_receive()?; + Some(&mut p.buf[..p.len]) + } + + /// Polling the outbound channel if there is space for packets. + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_receive(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + /// Mark outbound packet as copied. + pub fn tx_done(&mut self) { + self.tx_chan.receive_done(); + } +} + +/// Create a channel. +/// +/// Returns a pair of handles for interfacing with the peripheral and the networking stack. +/// +/// The runner is interfacing with the peripheral at the lower part of the stack. +/// The device is interfacing with the networking stack on the layer above. +pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( + state: &'d mut State, + hardware_address: driver::HardwareAddress, +) -> (Runner<'d, MTU>, Device<'d, MTU>) { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + + // safety: this is a self-referential struct, however: + // - it can't move while the `'d` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let state_uninit: *mut MaybeUninit> = + (&mut state.inner as *mut MaybeUninit>).cast(); + let state = unsafe { &mut *state_uninit }.write(StateInner { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + shared: Mutex::new(RefCell::new(Shared { + link_state: LinkState::Down, + hardware_address, + waker: WakerRegistration::new(), + })), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + Runner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + shared: &state.shared, + }, + Device { + caps, + shared: &state.shared, + rx: rx_receiver, + tx: tx_sender, + }, + ) +} + +/// Represents a packet of size MTU. +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + /// Create a new packet buffer. + pub const fn new() -> Self { + Self { len: 0, buf: [0; MTU] } + } +} + +/// Channel device. +/// +/// Holds the shared state and upper end of channels for inbound and outbound packets. +pub struct Device<'d, const MTU: usize> { + rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, + caps: Capabilities, +} + +impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { + type RxToken<'a> + = RxToken<'a, MTU> + where + Self: 'a; + type TxToken<'a> + = TxToken<'a, MTU> + where + Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx.poll_receive(cx).is_ready() && self.tx.poll_send(cx).is_ready() { + Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) + } else { + None + } + } + + /// Construct a transmit token. + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.tx.poll_send(cx).is_ready() { + Some(TxToken { tx: self.tx.borrow() }) + } else { + None + } + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities { + self.caps.clone() + } + + fn hardware_address(&self) -> driver::HardwareAddress { + self.shared.lock(|s| s.borrow().hardware_address) + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.link_state + }) + } +} + +/// A rx token. +/// +/// Holds inbound receive channel and interfaces with embassy-net-driver. +pub struct RxToken<'a, const MTU: usize> { + rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.try_receive()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.receive_done(); + r + } +} + +/// A tx token. +/// +/// Holds outbound transmit channel and interfaces with embassy-net-driver. +pub struct TxToken<'a, const MTU: usize> { + tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.try_send()); + let r = f(&mut pkt.buf[..len]); + pkt.len = len; + self.tx.send_done(); + r + } +} diff --git a/embassy-net-driver/CHANGELOG.md b/embassy-net-driver/CHANGELOG.md new file mode 100644 index 0000000..165461e --- /dev/null +++ b/embassy-net-driver/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +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). + +## 0.2.0 - 2023-10-18 + +- Added support for IEEE 802.15.4 mediums. +- Added `Driver::hardware_address()`, `HardwareAddress`. +- Removed `Medium` enum. The medium is deduced out of the hardware address. +- Removed `Driver::ethernet_address()`. Replacement is `hardware_address()`. + +## 0.1.0 - 2023-06-29 + +- First release diff --git a/embassy-net-driver/Cargo.toml b/embassy-net-driver/Cargo.toml new file mode 100644 index 0000000..97e8a0d --- /dev/null +++ b/embassy-net-driver/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "embassy-net-driver" +version = "0.2.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Driver trait for the `embassy-net` async TCP/IP network stack." +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-driver" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } diff --git a/embassy-net-driver/README.md b/embassy-net-driver/README.md new file mode 100644 index 0000000..24fcaaf --- /dev/null +++ b/embassy-net-driver/README.md @@ -0,0 +1,18 @@ +# embassy-net-driver + +This crate contains the driver trait necessary for adding [`embassy-net`](https://crates.io/crates/embassy-net) support +for a new hardware platform. + +If you want to *use* `embassy-net` with already made drivers, you should depend on the main `embassy-net` crate, not on this crate. + +If you are writing a driver, you should depend only on this crate, not on the main `embassy-net` crate. +This will allow your driver to continue working for newer `embassy-net` major versions, without needing an update, +if the driver trait has not had breaking changes. + +See also [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel), which provides a higer-level API +to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via +packet queues for RX and TX. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs new file mode 100644 index 0000000..4c84771 --- /dev/null +++ b/embassy-net-driver/src/lib.rs @@ -0,0 +1,219 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use core::task::Context; + +/// Representation of an hardware address, such as an Ethernet address or an IEEE802.15.4 address. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum HardwareAddress { + /// Ethernet medium, with a A six-octet Ethernet address. + /// + /// Devices of this type send and receive Ethernet frames, + /// and interfaces using it must do neighbor discovery via ARP or NDISC. + /// + /// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode. + Ethernet([u8; 6]), + /// 6LoWPAN over IEEE802.15.4, with an eight-octet address. + Ieee802154([u8; 8]), + /// Indicates that a Driver is IP-native, and has no hardware address. + /// + /// Devices of this type send and receive IP frames, without an + /// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done. + /// + /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode. + Ip, +} + +/// Main `embassy-net` driver API. +/// +/// This is essentially an interface for sending and receiving raw network frames. +/// +/// The interface is based on _tokens_, which are types that allow to receive/transmit a +/// single packet. The `receive` and `transmit` functions only construct such tokens, the +/// real sending/receiving operation are performed when the tokens are consumed. +pub trait Driver { + /// A token to receive a single network packet. + type RxToken<'a>: RxToken + where + Self: 'a; + + /// A token to transmit a single network packet. + type TxToken<'a>: TxToken + where + Self: 'a; + + /// Construct a token pair consisting of one receive token and one transmit token. + /// + /// If there is a packet ready to be received, this function must return `Some`. + /// If there isn't, it must return `None`, and wake `cx.waker()` when a packet is ready. + /// + /// The additional transmit token makes it possible to generate a reply packet based + /// on the contents of the received packet. For example, this makes it possible to + /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes + /// need to be sent back, without heap allocation. + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; + + /// Construct a transmit token. + /// + /// If there is free space in the transmit buffer to transmit a packet, this function must return `Some`. + /// If there isn't, it must return `None`, and wake `cx.waker()` when space becomes available. + /// + /// Note that [`TxToken::consume`] is infallible, so it is not allowed to return a token + /// if there is no free space and fail later. + fn transmit(&mut self, cx: &mut Context) -> Option>; + + /// Get the link state. + /// + /// This function must return the current link state of the device, and wake `cx.waker()` when + /// the link state changes. + fn link_state(&mut self, cx: &mut Context) -> LinkState; + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities; + + /// Get the device's hardware address. + /// + /// The returned hardware address also determines the "medium" of this driver. This indicates + /// what kind of packet the sent/received bytes are, and determines some behaviors of + /// the interface. For example, ARP/NDISC address resolution is only done for Ethernet mediums. + fn hardware_address(&self) -> HardwareAddress; +} + +impl Driver for &mut T { + type RxToken<'a> + = T::RxToken<'a> + where + Self: 'a; + type TxToken<'a> + = T::TxToken<'a> + where + Self: 'a; + + fn transmit(&mut self, cx: &mut Context) -> Option> { + T::transmit(self, cx) + } + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + T::receive(self, cx) + } + fn capabilities(&self) -> Capabilities { + T::capabilities(self) + } + fn link_state(&mut self, cx: &mut Context) -> LinkState { + T::link_state(self, cx) + } + fn hardware_address(&self) -> HardwareAddress { + T::hardware_address(self) + } +} + +/// A token to receive a single network packet. +pub trait RxToken { + /// Consumes the token to receive a single network packet. + /// + /// This method receives a packet and then calls the given closure `f` with the raw + /// packet bytes as argument. + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; +} + +/// A token to transmit a single network packet. +pub trait TxToken { + /// Consumes the token to send a single network packet. + /// + /// This method constructs a transmit buffer of size `len` and calls the passed + /// closure `f` with a mutable reference to that buffer. The closure should construct + /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure + /// returns, the transmit buffer is sent out. + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; +} + +/// A description of device capabilities. +/// +/// Higher-level protocols may achieve higher throughput or lower latency if they consider +/// the bandwidth or packet size limitations. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Capabilities { + /// Maximum transmission unit. + /// + /// The network device is unable to send or receive frames larger than the value returned + /// by this function. + /// + /// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but + /// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14. + /// + /// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet + /// devices. This is a common source of confusion. + /// + /// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets. + pub max_transmission_unit: usize, + + /// Maximum burst size, in terms of MTU. + /// + /// The network device is unable to send or receive bursts large than the value returned + /// by this function. + /// + /// If `None`, there is no fixed limit on burst size, e.g. if network buffers are + /// dynamically allocated. + pub max_burst_size: Option, + + /// Checksum behavior. + /// + /// If the network device is capable of verifying or computing checksums for some protocols, + /// it can request that the stack not do so in software to improve performance. + pub checksum: ChecksumCapabilities, +} + +/// A description of checksum behavior for every supported protocol. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ChecksumCapabilities { + /// Checksum behavior for IPv4. + pub ipv4: Checksum, + /// Checksum behavior for UDP. + pub udp: Checksum, + /// Checksum behavior for TCP. + pub tcp: Checksum, + /// Checksum behavior for ICMPv4. + pub icmpv4: Checksum, + /// Checksum behavior for ICMPv6. + pub icmpv6: Checksum, +} + +/// A description of checksum behavior for a particular protocol. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Checksum { + /// Verify checksum when receiving and compute checksum when sending. + Both, + /// Verify checksum when receiving. + Rx, + /// Compute checksum before sending. + Tx, + /// Ignore checksum completely. + None, +} + +impl Default for Checksum { + fn default() -> Checksum { + Checksum::Both + } +} + +/// The link state of a network device. +#[derive(PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LinkState { + /// The link is down. + Down, + /// The link is up. + Up, +} diff --git a/embassy-net-wiznet/Cargo.toml b/embassy-net-wiznet/Cargo.toml new file mode 100644 index 0000000..2ad5a6f --- /dev/null +++ b/embassy-net-wiznet/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "embassy-net-wiznet" +version = "0.2.0" +description = "embassy-net driver for WIZnet SPI Ethernet chips" +keywords = ["embedded", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "asynchronous"] +license = "MIT OR Apache-2.0" +edition = "2021" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net-wiznet" + +[dependencies] +embedded-hal = { version = "1.0" } +embedded-hal-async = { version = "1.0" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +defmt = { version = "0.3", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-wiznet-v$VERSION/embassy-net-wiznet/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-wiznet/src/" +target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] diff --git a/embassy-net-wiznet/README.md b/embassy-net-wiznet/README.md new file mode 100644 index 0000000..b666198 --- /dev/null +++ b/embassy-net-wiznet/README.md @@ -0,0 +1,16 @@ +# WIZnet `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet SPI ethernet chips, operating in MACRAW mode. + +See [`examples`](https://github.com/embassy-rs/embassy/tree/main/examples/rp) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico)) module. + +## Supported chips + +- W5500 +- W5100S + +## Interoperability + +This crate can run on any executor. + +It supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async). diff --git a/embassy-net-wiznet/src/chip/mod.rs b/embassy-net-wiznet/src/chip/mod.rs new file mode 100644 index 0000000..2e7a9ed --- /dev/null +++ b/embassy-net-wiznet/src/chip/mod.rs @@ -0,0 +1,48 @@ +//! Wiznet W5100s and W5500 family driver. +mod w5500; +pub use w5500::W5500; +mod w5100s; +use embedded_hal_async::spi::SpiDevice; +pub use w5100s::W5100S; + +pub(crate) trait SealedChip { + type Address; + + /// The version of the chip as reported by the VERSIONR register. + /// This is used to verify that the chip is supported by the driver, + /// and that SPI communication is working. + const CHIP_VERSION: u8; + + const COMMON_MODE: Self::Address; + const COMMON_MAC: Self::Address; + const COMMON_SOCKET_INTR: Self::Address; + const COMMON_PHY_CFG: Self::Address; + const COMMON_VERSION: Self::Address; + + const SOCKET_MODE: Self::Address; + const SOCKET_COMMAND: Self::Address; + const SOCKET_RXBUF_SIZE: Self::Address; + const SOCKET_TXBUF_SIZE: Self::Address; + const SOCKET_TX_FREE_SIZE: Self::Address; + const SOCKET_TX_DATA_WRITE_PTR: Self::Address; + const SOCKET_RECVD_SIZE: Self::Address; + const SOCKET_RX_DATA_READ_PTR: Self::Address; + const SOCKET_INTR_MASK: Self::Address; + const SOCKET_INTR: Self::Address; + + const SOCKET_MODE_VALUE: u8; + + const BUF_SIZE: u16; + const AUTO_WRAP: bool; + + fn rx_addr(addr: u16) -> Self::Address; + fn tx_addr(addr: u16) -> Self::Address; + + async fn bus_read(spi: &mut SPI, address: Self::Address, data: &mut [u8]) + -> Result<(), SPI::Error>; + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error>; +} + +/// Trait for Wiznet chips. +#[allow(private_bounds)] +pub trait Chip: SealedChip {} diff --git a/embassy-net-wiznet/src/chip/w5100s.rs b/embassy-net-wiznet/src/chip/w5100s.rs new file mode 100644 index 0000000..4c4b7ab --- /dev/null +++ b/embassy-net-wiznet/src/chip/w5100s.rs @@ -0,0 +1,65 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +const SOCKET_BASE: u16 = 0x400; +const TX_BASE: u16 = 0x4000; +const RX_BASE: u16 = 0x6000; + +/// Wizard W5100S chip. +pub enum W5100S {} + +impl super::Chip for W5100S {} +impl super::SealedChip for W5100S { + type Address = u16; + + const CHIP_VERSION: u8 = 0x51; + + const COMMON_MODE: Self::Address = 0x00; + const COMMON_MAC: Self::Address = 0x09; + const COMMON_SOCKET_INTR: Self::Address = 0x16; + const COMMON_PHY_CFG: Self::Address = 0x3c; + const COMMON_VERSION: Self::Address = 0x80; + + const SOCKET_MODE: Self::Address = SOCKET_BASE + 0x00; + const SOCKET_COMMAND: Self::Address = SOCKET_BASE + 0x01; + const SOCKET_RXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1E; + const SOCKET_TXBUF_SIZE: Self::Address = SOCKET_BASE + 0x1F; + const SOCKET_TX_FREE_SIZE: Self::Address = SOCKET_BASE + 0x20; + const SOCKET_TX_DATA_WRITE_PTR: Self::Address = SOCKET_BASE + 0x24; + const SOCKET_RECVD_SIZE: Self::Address = SOCKET_BASE + 0x26; + const SOCKET_RX_DATA_READ_PTR: Self::Address = SOCKET_BASE + 0x28; + const SOCKET_INTR_MASK: Self::Address = SOCKET_BASE + 0x2C; + const SOCKET_INTR: Self::Address = SOCKET_BASE + 0x02; + + const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 6); + + const BUF_SIZE: u16 = 0x2000; + const AUTO_WRAP: bool = false; + + fn rx_addr(addr: u16) -> Self::Address { + RX_BASE + addr + } + + fn tx_addr(addr: u16) -> Self::Address { + TX_BASE + addr + } + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + spi.transaction(&mut [ + Operation::Write(&[0x0F, (address >> 8) as u8, address as u8]), + Operation::Read(data), + ]) + .await + } + + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + spi.transaction(&mut [ + Operation::Write(&[0xF0, (address >> 8) as u8, address as u8]), + Operation::Write(data), + ]) + .await + } +} diff --git a/embassy-net-wiznet/src/chip/w5500.rs b/embassy-net-wiznet/src/chip/w5500.rs new file mode 100644 index 0000000..5cfcb94 --- /dev/null +++ b/embassy-net-wiznet/src/chip/w5500.rs @@ -0,0 +1,76 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +#[repr(u8)] +pub enum RegisterBlock { + Common = 0x00, + Socket0 = 0x01, + TxBuf = 0x02, + RxBuf = 0x03, +} + +/// Wiznet W5500 chip. +pub enum W5500 {} + +impl super::Chip for W5500 {} +impl super::SealedChip for W5500 { + type Address = (RegisterBlock, u16); + + const CHIP_VERSION: u8 = 0x04; + + const COMMON_MODE: Self::Address = (RegisterBlock::Common, 0x00); + const COMMON_MAC: Self::Address = (RegisterBlock::Common, 0x09); + const COMMON_SOCKET_INTR: Self::Address = (RegisterBlock::Common, 0x18); + const COMMON_PHY_CFG: Self::Address = (RegisterBlock::Common, 0x2E); + const COMMON_VERSION: Self::Address = (RegisterBlock::Common, 0x39); + + const SOCKET_MODE: Self::Address = (RegisterBlock::Socket0, 0x00); + const SOCKET_COMMAND: Self::Address = (RegisterBlock::Socket0, 0x01); + const SOCKET_RXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1E); + const SOCKET_TXBUF_SIZE: Self::Address = (RegisterBlock::Socket0, 0x1F); + const SOCKET_TX_FREE_SIZE: Self::Address = (RegisterBlock::Socket0, 0x20); + const SOCKET_TX_DATA_WRITE_PTR: Self::Address = (RegisterBlock::Socket0, 0x24); + const SOCKET_RECVD_SIZE: Self::Address = (RegisterBlock::Socket0, 0x26); + const SOCKET_RX_DATA_READ_PTR: Self::Address = (RegisterBlock::Socket0, 0x28); + const SOCKET_INTR_MASK: Self::Address = (RegisterBlock::Socket0, 0x2C); + const SOCKET_INTR: Self::Address = (RegisterBlock::Socket0, 0x02); + + const SOCKET_MODE_VALUE: u8 = (1 << 2) | (1 << 7); + + const BUF_SIZE: u16 = 0x4000; + const AUTO_WRAP: bool = true; + + fn rx_addr(addr: u16) -> Self::Address { + (RegisterBlock::RxBuf, addr) + } + + fn tx_addr(addr: u16) -> Self::Address { + (RegisterBlock::TxBuf, addr) + } + + async fn bus_read( + spi: &mut SPI, + address: Self::Address, + data: &mut [u8], + ) -> Result<(), SPI::Error> { + let address_phase = address.1.to_be_bytes(); + let control_phase = [(address.0 as u8) << 3]; + let operations = &mut [ + Operation::Write(&address_phase), + Operation::Write(&control_phase), + Operation::TransferInPlace(data), + ]; + spi.transaction(operations).await + } + + async fn bus_write(spi: &mut SPI, address: Self::Address, data: &[u8]) -> Result<(), SPI::Error> { + let address_phase = address.1.to_be_bytes(); + let control_phase = [(address.0 as u8) << 3 | 0b0000_0100]; + let data_phase = data; + let operations = &mut [ + Operation::Write(&address_phase[..]), + Operation::Write(&control_phase), + Operation::Write(&data_phase), + ]; + spi.transaction(operations).await + } +} diff --git a/embassy-net-wiznet/src/device.rs b/embassy-net-wiznet/src/device.rs new file mode 100644 index 0000000..d2b6bb0 --- /dev/null +++ b/embassy-net-wiznet/src/device.rs @@ -0,0 +1,255 @@ +use core::marker::PhantomData; + +use embedded_hal_async::spi::SpiDevice; + +use crate::chip::Chip; + +#[repr(u8)] +enum Command { + Open = 0x01, + Send = 0x20, + Receive = 0x40, +} + +#[repr(u8)] +enum Interrupt { + Receive = 0b00100_u8, +} + +/// Wiznet chip in MACRAW mode +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(crate) struct WiznetDevice { + spi: SPI, + _phantom: PhantomData, +} + +/// Error type when initializing a new Wiznet device +pub enum InitError { + /// Error occurred when sending or receiving SPI data + SpiError(SE), + /// The chip returned a version that isn't expected or supported + InvalidChipVersion { + /// The version that is supported + expected: u8, + /// The version that was returned by the chip + actual: u8, + }, +} + +impl From for InitError { + fn from(e: SE) -> Self { + InitError::SpiError(e) + } +} + +impl core::fmt::Debug for InitError +where + SE: core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + InitError::SpiError(e) => write!(f, "SpiError({:?})", e), + InitError::InvalidChipVersion { expected, actual } => { + write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) + } + } + } +} + +#[cfg(feature = "defmt")] +impl defmt::Format for InitError +where + SE: defmt::Format, +{ + fn format(&self, f: defmt::Formatter) { + match self { + InitError::SpiError(e) => defmt::write!(f, "SpiError({})", e), + InitError::InvalidChipVersion { expected, actual } => { + defmt::write!(f, "InvalidChipVersion {{ expected: {}, actual: {} }}", expected, actual) + } + } + } +} + +impl WiznetDevice { + /// Create and initialize the driver + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result> { + let mut this = Self { + spi, + _phantom: PhantomData, + }; + + // Reset device + this.bus_write(C::COMMON_MODE, &[0x80]).await?; + + // Check the version of the chip + let mut version = [0]; + this.bus_read(C::COMMON_VERSION, &mut version).await?; + if version[0] != C::CHIP_VERSION { + #[cfg(feature = "defmt")] + defmt::error!("invalid chip version: {} (expected {})", version[0], C::CHIP_VERSION); + return Err(InitError::InvalidChipVersion { + actual: version[0], + expected: C::CHIP_VERSION, + }); + } + + // Enable interrupt pin + this.bus_write(C::COMMON_SOCKET_INTR, &[0x01]).await?; + // Enable receive interrupt + this.bus_write(C::SOCKET_INTR_MASK, &[Interrupt::Receive as u8]).await?; + + // Set MAC address + this.bus_write(C::COMMON_MAC, &mac_addr).await?; + + // Set the raw socket RX/TX buffer sizes. + let buf_kbs = (C::BUF_SIZE / 1024) as u8; + this.bus_write(C::SOCKET_TXBUF_SIZE, &[buf_kbs]).await?; + this.bus_write(C::SOCKET_RXBUF_SIZE, &[buf_kbs]).await?; + + // MACRAW mode with MAC filtering. + this.bus_write(C::SOCKET_MODE, &[C::SOCKET_MODE_VALUE]).await?; + this.command(Command::Open).await?; + + Ok(this) + } + + async fn bus_read(&mut self, address: C::Address, data: &mut [u8]) -> Result<(), SPI::Error> { + C::bus_read(&mut self.spi, address, data).await + } + + async fn bus_write(&mut self, address: C::Address, data: &[u8]) -> Result<(), SPI::Error> { + C::bus_write(&mut self.spi, address, data).await + } + + async fn reset_interrupt(&mut self, code: Interrupt) -> Result<(), SPI::Error> { + let data = [code as u8]; + self.bus_write(C::SOCKET_INTR, &data).await + } + + async fn get_tx_write_ptr(&mut self) -> Result { + let mut data = [0u8; 2]; + self.bus_read(C::SOCKET_TX_DATA_WRITE_PTR, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + async fn set_tx_write_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + self.bus_write(C::SOCKET_TX_DATA_WRITE_PTR, &data).await + } + + async fn get_rx_read_ptr(&mut self) -> Result { + let mut data = [0u8; 2]; + self.bus_read(C::SOCKET_RX_DATA_READ_PTR, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + async fn set_rx_read_ptr(&mut self, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + self.bus_write(C::SOCKET_RX_DATA_READ_PTR, &data).await + } + + async fn command(&mut self, command: Command) -> Result<(), SPI::Error> { + let data = [command as u8]; + self.bus_write(C::SOCKET_COMMAND, &data).await + } + + async fn get_rx_size(&mut self) -> Result { + loop { + // Wait until two sequential reads are equal + let mut res0 = [0u8; 2]; + self.bus_read(C::SOCKET_RECVD_SIZE, &mut res0).await?; + let mut res1 = [0u8; 2]; + self.bus_read(C::SOCKET_RECVD_SIZE, &mut res1).await?; + if res0 == res1 { + break Ok(u16::from_be_bytes(res0)); + } + } + } + + async fn get_tx_free_size(&mut self) -> Result { + let mut data = [0; 2]; + self.bus_read(C::SOCKET_TX_FREE_SIZE, &mut data).await?; + Ok(u16::from_be_bytes(data)) + } + + /// Read bytes from the RX buffer. + async fn read_bytes(&mut self, read_ptr: &mut u16, buffer: &mut [u8]) -> Result<(), SPI::Error> { + if C::AUTO_WRAP { + self.bus_read(C::rx_addr(*read_ptr), buffer).await?; + } else { + let addr = *read_ptr % C::BUF_SIZE; + if addr as usize + buffer.len() <= C::BUF_SIZE as usize { + self.bus_read(C::rx_addr(addr), buffer).await?; + } else { + let n = C::BUF_SIZE - addr; + self.bus_read(C::rx_addr(addr), &mut buffer[..n as usize]).await?; + self.bus_read(C::rx_addr(0), &mut buffer[n as usize..]).await?; + } + } + + *read_ptr = (*read_ptr).wrapping_add(buffer.len() as u16); + + Ok(()) + } + + /// Read an ethernet frame from the device. Returns the number of bytes read. + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + let rx_size = self.get_rx_size().await? as usize; + if rx_size == 0 { + return Ok(0); + } + + self.reset_interrupt(Interrupt::Receive).await?; + + let mut read_ptr = self.get_rx_read_ptr().await?; + + // First two bytes gives the size of the received ethernet frame + let expected_frame_size: usize = { + let mut frame_bytes = [0u8; 2]; + self.read_bytes(&mut read_ptr, &mut frame_bytes).await?; + u16::from_be_bytes(frame_bytes) as usize - 2 + }; + + // Read the ethernet frame + self.read_bytes(&mut read_ptr, &mut frame[..expected_frame_size]) + .await?; + + // Register RX as completed + self.set_rx_read_ptr(read_ptr).await?; + self.command(Command::Receive).await?; + + Ok(expected_frame_size) + } + + /// Write an ethernet frame to the device. Returns number of bytes written + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + while self.get_tx_free_size().await? < frame.len() as u16 {} + let write_ptr = self.get_tx_write_ptr().await?; + + if C::AUTO_WRAP { + self.bus_write(C::tx_addr(write_ptr), frame).await?; + } else { + let addr = write_ptr % C::BUF_SIZE; + if addr as usize + frame.len() <= C::BUF_SIZE as usize { + self.bus_write(C::tx_addr(addr), frame).await?; + } else { + let n = C::BUF_SIZE - addr; + self.bus_write(C::tx_addr(addr), &frame[..n as usize]).await?; + self.bus_write(C::tx_addr(0), &frame[n as usize..]).await?; + } + } + + self.set_tx_write_ptr(write_ptr.wrapping_add(frame.len() as u16)) + .await?; + self.command(Command::Send).await?; + Ok(frame.len()) + } + + pub async fn is_link_up(&mut self) -> bool { + let mut link = [0]; + self.bus_read(C::COMMON_PHY_CFG, &mut link).await.ok(); + link[0] & 1 == 1 + } +} diff --git a/embassy-net-wiznet/src/lib.rs b/embassy-net-wiznet/src/lib.rs new file mode 100644 index 0000000..3fbd4c7 --- /dev/null +++ b/embassy-net-wiznet/src/lib.rs @@ -0,0 +1,133 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +pub mod chip; +mod device; + +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_time::{Duration, Ticker, Timer}; +use embedded_hal::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; + +use crate::chip::Chip; +pub use crate::device::InitError; +use crate::device::WiznetDevice; + +// If you change this update the docs of State +const MTU: usize = 1514; + +/// Type alias for the embassy-net driver. +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +/// +/// The two generic arguments `N_RX` and `N_TX` set the size of the receive and +/// send packet queue. With a the ethernet MTU of _1514_ this takes up `N_RX + +/// NTX * 1514` bytes. While setting these both to 1 is the minimum this might +/// hurt performance as a packet can not be received while processing another. +/// +/// # Warning +/// On devices with a small amount of ram (think ~64k) watch out with the size +/// of there parameters. They will quickly use too much RAM. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the driver. +/// +/// You must call `.run()` in a background task for the driver to operate. +pub struct Runner<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> { + mac: WiznetDevice, + ch: ch::Runner<'d, MTU>, + int: INT, + _reset: RST, +} + +/// You must call this in a background task for the driver to operate. +impl<'d, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, C, SPI, INT, RST> { + /// Run the driver. + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let mut tick = Ticker::every(Duration::from_millis(500)); + loop { + match select3( + async { + self.int.wait_for_low().await.ok(); + rx_chan.rx_buf().await + }, + tx_chan.tx_buf(), + tick.next(), + ) + .await + { + Either3::First(p) => { + if let Ok(n) = self.mac.read_frame(p).await { + rx_chan.rx_done(n); + } + } + Either3::Second(p) => { + self.mac.write_frame(p).await.ok(); + tx_chan.tx_done(); + } + Either3::Third(()) => { + if self.mac.is_link_up().await { + state_chan.set_link_state(LinkState::Up); + } else { + state_chan.set_link_state(LinkState::Down); + } + } + } + } + } +} + +/// Create a Wiznet ethernet chip driver for [`embassy-net`](https://crates.io/crates/embassy-net). +/// +/// This returns two structs: +/// - a `Device` that you must pass to the `embassy-net` stack. +/// - a `Runner`. You must call `.run()` on it in a background task. +pub async fn new<'a, const N_RX: usize, const N_TX: usize, C: Chip, SPI: SpiDevice, INT: Wait, RST: OutputPin>( + mac_addr: [u8; 6], + state: &'a mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, +) -> Result<(Device<'a>, Runner<'a, C, SPI, INT, RST>), InitError> { + // Reset the chip. + reset.set_low().ok(); + // Ensure the reset is registered. + Timer::after_millis(1).await; + reset.set_high().ok(); + + // Wait for PLL lock. Some chips are slower than others. + // Slowest is w5100s which is 100ms, so let's just wait that. + Timer::after_millis(100).await; + + let mac = WiznetDevice::new(spi_dev, mac_addr).await?; + + let (runner, device) = ch::new(&mut state.ch_state, ch::driver::HardwareAddress::Ethernet(mac_addr)); + + Ok(( + device, + Runner { + ch: runner, + mac, + int, + _reset: reset, + }, + )) +} diff --git a/embassy-net/CHANGELOG.md b/embassy-net/CHANGELOG.md new file mode 100644 index 0000000..cfee5f3 --- /dev/null +++ b/embassy-net/CHANGELOG.md @@ -0,0 +1,87 @@ +# Changelog + +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 + +No unreleased changes yet... Quick, go send a PR! + +## 0.7 - 2025-02-14 + +- don't infinite loop if udp::send methods receive a buffer too large to ever be sent +- add ICMP sockets and a ping utility + +## 0.6 - 2025-01-05 + +- Make `Config` constructors `const` +- The `std` feature has been removed +- Updated `embassy-time` to v0.4 + +## 0.5 - 2024-11-28 + +- Refactor the API structure, simplifying lifetimes and generics. + - Stack is now a thin handle that implements `Copy+Clone`. Instead of passing `&Stack` around, you can now pass `Stack`. + - `Stack` and `DnsSocket` no longer need a generic parameter for the device driver. + - The `run()` method has been moved to a new `Runner` struct. + - Sockets are covariant wrt their lifetime. + - An implication of the refactor is now you need only one `StaticCell` instead of two if you need to share the network stack between tasks. +- Use standard `core::net` IP types instead of custom ones from smoltcp. +- Update to `smoltcp` v0.12. +- Add `mdns` Cargo feature. +- dns: properly handle `AddrType::Either` in `get_host_by_name()` +- dns: truncate instead of panic if the DHCP server gives us more DNS servers than the configured maximum. +- stack: add `wait_link_up()`, `wait_link_down()`, `wait_config_down()`. +- tcp: Add `recv_queue()`, `send_queue()`. +- tcp: Add `wait_read_ready()`, `wait_write_ready()`. +- tcp: allow setting timeout through `embedded-nal` client. +- tcp: fix `flush()` hanging forever if socket is closed with pending data. +- tcp: fix `flush()` not waiting for ACK of FIN. +- tcp: implement `ReadReady`, `WriteReady` traits from `embedded-io`. +- udp, raw: Add `wait_send_ready()`, `wait_recv_ready()`, `flush()`. +- udp: add `recv_from_with()`, `send_to_with()` methods, allowing for IO with one less copy. +- udp: send/recv now takes/returns full `UdpMetadata` instead of just the remote `IpEndpoint`. +- raw: add raw sockets. + + +## 0.4 - 2024-01-11 + +- Update to `embassy-time` v0.3. + +## 0.3 - 2024-01-04 + +- Added `ReadReady` and `WriteReady` impls on `TcpSocket`. +- Avoid never resolving `TcpIo::read` when the output buffer is empty. +- Update to `smoltcp` v0.11. +- Forward constants from `smoltcp` in DNS query results so changing DNS result size in `smoltcp` properly propagates. +- Removed the nightly feature. + +## 0.2.1 - 2023-10-31 + +- Re-add impl_trait_projections +- Fix: Reset DHCP socket when the link up is detected + +## 0.2.0 - 2023-10-18 + +- Re-export `smoltcp::wire::IpEndpoint` +- Add poll functions on UdpSocket +- Make dual-stack work in embassy-net +- Fix multicast support +- Allow ethernet and 802.15.4 to coexist +- Add IEEE802.15.4 address to embassy net Stack +- Use HardwareAddress in Driver +- Add async versions of smoltcp's `send` and `recv` closure based API +- add error translation to tcp errors +- Forward TCP/UDP socket capacity impls +- allow changing IP config at runtime +- allow non-'static drivers +- Remove impl_trait_projections +- update embedded-io, embedded-nal-async +- add support for dhcp hostname option +- Wake stack's task after queueing a DNS query + +## 0.1.0 - 2023-06-29 + +- First release diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml new file mode 100644 index 0000000..1e513a9 --- /dev/null +++ b/embassy-net/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "embassy-net" +version = "0.7.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Async TCP/IP network stack for embedded systems" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-net" +categories = [ + "embedded", + "no-std", + "asynchronous", + "network-programming", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/" +features = ["defmt", "tcp", "udp", "raw", "dns", "icmp", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt", "tcp", "udp", "raw", "dns", "icmp", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "multicast", "dhcpv4-hostname"] + +[features] +## Enable defmt +defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt", "heapless/defmt-03", "defmt?/ip_in_core"] + +## Trace all raw received and transmitted packets using defmt or log. +packet-trace = [] + +#! Many of the following feature flags are re-exports of smoltcp feature flags. See +#! the [smoltcp feature flag documentation](https://github.com/smoltcp-rs/smoltcp#feature-flags) +#! for more details + +## Enable ICMP support +icmp = ["smoltcp/socket-icmp"] +## Enable UDP support +udp = ["smoltcp/socket-udp"] +## Enable Raw support +raw = ["smoltcp/socket-raw"] +## Enable TCP support +tcp = ["smoltcp/socket-tcp"] +## Enable DNS support +dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] +## Enable mDNS support +mdns = ["dns", "smoltcp/socket-mdns"] +## Enable DHCPv4 support +dhcpv4 = ["proto-ipv4", "medium-ethernet", "smoltcp/socket-dhcpv4"] +## Enable DHCPv4 support with hostname +dhcpv4-hostname = ["dhcpv4"] +## Enable IPv4 support +proto-ipv4 = ["smoltcp/proto-ipv4"] +## Enable IPv6 support +proto-ipv6 = ["smoltcp/proto-ipv6"] +## Enable the Ethernet medium +medium-ethernet = ["smoltcp/medium-ethernet"] +## Enable the IP medium +medium-ip = ["smoltcp/medium-ip"] +## Enable the IEEE 802.15.4 medium +medium-ieee802154 = ["smoltcp/medium-ieee802154"] +## Enable multicast support (for both ipv4 and/or ipv6 if enabled) +multicast = ["smoltcp/multicast"] + +[dependencies] + +defmt = { version = "0.3.8", optional = true } +log = { version = "0.4.14", optional = true } + +smoltcp = { version = "0.12.0", default-features = false, features = [ + "socket", + "async", +] } + +embassy-net-driver = { version = "0.2.0", path = "../embassy-net-driver" } +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-sync = { version = "0.6.2", path = "../embassy-sync" } +embedded-io-async = { version = "0.6.1" } + +managed = { version = "0.8.0", default-features = false, features = [ "map" ] } +heapless = { version = "0.8", default-features = false } +embedded-nal-async = "0.8.0" +document-features = "0.2.7" diff --git a/embassy-net/README.md b/embassy-net/README.md new file mode 100644 index 0000000..1722ffc --- /dev/null +++ b/embassy-net/README.md @@ -0,0 +1,54 @@ +# embassy-net + +`embassy-net` is a no-std no-alloc async network stack, designed for embedded systems. + +It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated +API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and +memory management designed to work well for embedded systems, aiming for a more "Just Works" experience. + +## Features + +- IPv4, IPv6 +- Ethernet and bare-IP mediums. +- TCP, UDP, DNS, DHCPv4 +- TCP sockets implement the `embedded-io` async traits. +- Multicast + +See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and +unimplemented features of the network protocols. + +## Hardware support + +- [`esp-wifi`](https://github.com/esp-rs/esp-wifi) for WiFi support on bare-metal ESP32 chips. Maintained by Espressif. +- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W +- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. +- [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5). +- [`embassy-net-wiznet`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-wiznet) for Wiznet SPI Ethernet MAC+PHY chips (W5100S, W5500) +- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. + +## Examples + +- For usage with Embassy HALs and network chip drivers, search [here](https://github.com/embassy-rs/embassy/tree/main/examples) for `eth` or `wifi`. +- The [`esp-wifi` repo](https://github.com/esp-rs/esp-wifi) has examples for use on bare-metal ESP32 chips. +- For usage on `std` platforms, see [the `std` examples](https://github.com/embassy-rs/embassy/tree/main/examples/std/src/bin) + +## Adding support for new hardware + +To add `embassy-net` support for new hardware (i.e. a new Ethernet or WiFi chip, or +an Ethernet/WiFi MCU peripheral), you have to implement the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) +traits. + +Alternatively, [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel) provides a higher-level API +to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via +packet queues for RX and TX. + +Drivers should depend only on `embassy-net-driver` or `embassy-net-driver-channel`. Never on the main `embassy-net` crate. +This allows existing drivers to continue working for newer `embassy-net` major versions, without needing an update, if the driver +trait has not had breaking changes. + +## Interoperability + +This crate can run on any executor. + +[`embassy-time`](https://crates.io/crates/embassy-time) is used for timekeeping and timeouts. You must +link an `embassy-time` driver in your project to use this crate. diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs new file mode 100644 index 0000000..dbe7377 --- /dev/null +++ b/embassy-net/src/dns.rs @@ -0,0 +1,120 @@ +//! DNS client compatible with the `embedded-nal-async` traits. +//! +//! This exists only for compatibility with crates that use `embedded-nal-async`. +//! Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +//! not using `embedded-nal-async`. + +use heapless::Vec; +pub use smoltcp::socket::dns::{DnsQuery, Socket}; +pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; +pub use smoltcp::wire::{DnsQueryType, IpAddress}; + +use crate::Stack; + +/// Errors returned by DnsSocket. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid name + InvalidName, + /// Name too long + NameTooLong, + /// Name lookup failed + Failed, +} + +impl From for Error { + fn from(_: GetQueryResultError) -> Self { + Self::Failed + } +} + +impl From for Error { + fn from(e: StartQueryError) -> Self { + match e { + StartQueryError::NoFreeSlot => Self::Failed, + StartQueryError::InvalidName => Self::InvalidName, + StartQueryError::NameTooLong => Self::NameTooLong, + } + } +} + +/// DNS client compatible with the `embedded-nal-async` traits. +/// +/// This exists only for compatibility with crates that use `embedded-nal-async`. +/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +/// not using `embedded-nal-async`. +pub struct DnsSocket<'a> { + stack: Stack<'a>, +} + +impl<'a> DnsSocket<'a> { + /// Create a new DNS socket using the provided stack. + /// + /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. + pub fn new(stack: Stack<'a>) -> Self { + Self { stack } + } + + /// Make a query for a given name and return the corresponding IP addresses. + pub async fn query( + &self, + name: &str, + qtype: DnsQueryType, + ) -> Result, Error> { + self.stack.dns_query(name, qtype).await + } +} + +impl<'a> embedded_nal_async::Dns for DnsSocket<'a> { + type Error = Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + use core::net::IpAddr; + + use embedded_nal_async::AddrType; + + let (qtype, secondary_qtype) = match addr_type { + AddrType::IPv4 => (DnsQueryType::A, None), + AddrType::IPv6 => (DnsQueryType::Aaaa, None), + AddrType::Either => { + #[cfg(not(feature = "proto-ipv6"))] + let v6_first = false; + #[cfg(feature = "proto-ipv6")] + let v6_first = self.stack.config_v6().is_some(); + match v6_first { + true => (DnsQueryType::Aaaa, Some(DnsQueryType::A)), + false => (DnsQueryType::A, Some(DnsQueryType::Aaaa)), + } + } + }; + let mut addrs = self.query(host, qtype).await?; + if addrs.is_empty() { + if let Some(qtype) = secondary_qtype { + addrs = self.query(host, qtype).await? + } + } + if let Some(first) = addrs.get(0) { + Ok(match first { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => IpAddr::V4(*addr), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => IpAddr::V6(*addr), + }) + } else { + Err(Error::Failed) + } + } + + async fn get_host_by_address(&self, _addr: core::net::IpAddr, _result: &mut [u8]) -> Result { + todo!() + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: DnsSocket<'b>) -> DnsSocket<'a> { + x +} diff --git a/embassy-net/src/driver_util.rs b/embassy-net/src/driver_util.rs new file mode 100644 index 0000000..536f4c3 --- /dev/null +++ b/embassy-net/src/driver_util.rs @@ -0,0 +1,112 @@ +use core::task::Context; + +use embassy_net_driver::{Capabilities, Checksum, Driver, RxToken, TxToken}; +use smoltcp::phy::{self, Medium}; +use smoltcp::time::Instant; + +pub(crate) struct DriverAdapter<'d, 'c, T> +where + T: Driver, +{ + // must be Some when actually using this to rx/tx + pub cx: Option<&'d mut Context<'c>>, + pub inner: &'d mut T, + pub medium: Medium, +} + +impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> +where + T: Driver, +{ + type RxToken<'a> + = RxTokenAdapter> + where + Self: 'a; + type TxToken<'a> + = TxTokenAdapter> + where + Self: 'a; + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.inner + .receive(unwrap!(self.cx.as_deref_mut())) + .map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx))) + } + + /// Construct a transmit token. + fn transmit(&mut self, _timestamp: Instant) -> Option> { + self.inner.transmit(unwrap!(self.cx.as_deref_mut())).map(TxTokenAdapter) + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> phy::DeviceCapabilities { + fn convert(c: Checksum) -> phy::Checksum { + match c { + Checksum::Both => phy::Checksum::Both, + Checksum::Tx => phy::Checksum::Tx, + Checksum::Rx => phy::Checksum::Rx, + Checksum::None => phy::Checksum::None, + } + } + let caps: Capabilities = self.inner.capabilities(); + let mut smolcaps = phy::DeviceCapabilities::default(); + + smolcaps.max_transmission_unit = caps.max_transmission_unit; + smolcaps.max_burst_size = caps.max_burst_size; + smolcaps.medium = self.medium; + smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4); + smolcaps.checksum.tcp = convert(caps.checksum.tcp); + smolcaps.checksum.udp = convert(caps.checksum.udp); + #[cfg(feature = "proto-ipv4")] + { + smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); + } + #[cfg(feature = "proto-ipv6")] + { + smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6); + } + + smolcaps + } +} + +pub(crate) struct RxTokenAdapter(T) +where + T: RxToken; + +impl phy::RxToken for RxTokenAdapter +where + T: RxToken, +{ + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + self.0.consume(|buf| { + #[cfg(feature = "packet-trace")] + trace!("embassy device rx: {:02x}", buf); + f(buf) + }) + } +} + +pub(crate) struct TxTokenAdapter(T) +where + T: TxToken; + +impl phy::TxToken for TxTokenAdapter +where + T: TxToken, +{ + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + self.0.consume(len, |buf| { + let r = f(buf); + #[cfg(feature = "packet-trace")] + trace!("embassy device tx: {:02x}", buf); + r + }) + } +} diff --git a/embassy-net/src/fmt.rs b/embassy-net/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-net/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-net/src/icmp.rs b/embassy-net/src/icmp.rs new file mode 100644 index 0000000..22c31a5 --- /dev/null +++ b/embassy-net/src/icmp.rs @@ -0,0 +1,859 @@ +//! ICMP sockets. + +use core::future::{poll_fn, Future}; +use core::mem; +use core::task::{Context, Poll}; + +use smoltcp::iface::{Interface, SocketHandle}; +pub use smoltcp::phy::ChecksumCapabilities; +use smoltcp::socket::icmp; +pub use smoltcp::socket::icmp::{Endpoint as IcmpEndpoint, PacketMetadata}; +use smoltcp::wire::IpAddress; +#[cfg(feature = "proto-ipv4")] +pub use smoltcp::wire::{Icmpv4Message, Icmpv4Packet, Icmpv4Repr}; +#[cfg(feature = "proto-ipv6")] +pub use smoltcp::wire::{Icmpv6Message, Icmpv6Packet, Icmpv6Repr}; + +use crate::Stack; + +/// Error returned by [`IcmpSocket::bind`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + /// The socket was already open. + InvalidState, + /// The endpoint isn't specified + InvalidEndpoint, + /// No route to host. + NoRoute, +} + +/// Error returned by [`IcmpSocket::send_to`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + /// No route to host. + NoRoute, + /// Socket not bound to an outgoing port. + SocketNotBound, + /// There is not enough transmit buffer capacity to ever send this packet. + PacketTooLarge, +} + +/// Error returned by [`IcmpSocket::recv_from`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + /// Provided buffer was smaller than the received packet. + Truncated, +} + +/// An ICMP socket. +pub struct IcmpSocket<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'a> IcmpSocket<'a> { + /// Create a new ICMP socket using the provided stack and buffers. + pub fn new( + stack: Stack<'a>, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(icmp::Socket::new( + icmp::PacketBuffer::new(rx_meta, rx_buffer), + icmp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); + + Self { stack, handle } + } + + /// Bind the socket to the given endpoint. + pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> + where + T: Into, + { + let endpoint = endpoint.into(); + + if !endpoint.is_specified() { + return Err(BindError::InvalidEndpoint); + } + + match self.with_mut(|s, _| s.bind(endpoint)) { + Ok(()) => Ok(()), + Err(icmp::BindError::InvalidState) => Err(BindError::InvalidState), + Err(icmp::BindError::Unaddressable) => Err(BindError::NoRoute), + } + } + + fn with(&self, f: impl FnOnce(&icmp::Socket, &Interface) -> R) -> R { + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) + } + + fn with_mut(&self, f: impl FnOnce(&mut icmp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub fn wait_recv_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_recv_ready(cx)) + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + /// + /// Returns the number of bytes received and the remote endpoint. + pub fn recv_from<'s>( + &'s self, + buf: &'s mut [u8], + ) -> impl Future> + 's { + poll_fn(|cx| self.poll_recv_from(buf, cx)) + } + + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready` with the + /// number of bytes received and the remote endpoint. + pub fn poll_recv_from(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok((n, meta)) => Poll::Ready(Ok((n, meta))), + // No data ready + Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + Err(icmp::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Dequeue a packet received from a remote endpoint and calls the provided function with the + /// slice of the packet and the remote endpoint address and returns `Poll::Ready` with the + /// function's returned value. + /// + /// **Note**: when the size of the provided buffer is smaller than the size of the payload, + /// the packet is dropped and a `RecvError::Truncated` error is returned. + pub async fn recv_from_with(&self, f: F) -> Result + where + F: FnOnce((&[u8], IpAddress)) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| match s.recv() { + Ok(x) => Poll::Ready(Ok(unwrap!(f.take())(x))), + Err(icmp::RecvError::Exhausted) => { + cx.waker().wake_by_ref(); + Poll::Pending + } + Err(icmp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + }) + }) + .await + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub fn wait_send_ready(&self) -> impl Future + '_ { + poll_fn(|cx| self.poll_send_ready(cx)) + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram to the specified remote endpoint. + /// + /// This method will wait until the datagram has been sent. + /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)` + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError> + where + T: Into, + { + let remote_endpoint: IpAddress = remote_endpoint.into(); + poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await + } + + /// Send a datagram to the specified remote endpoint. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))` + /// + /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`. + pub fn poll_send_to(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll> + where + T: Into, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len()); + if send_capacity_too_small { + return Poll::Ready(Err(SendError::PacketTooLarge)); + } + + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint.into()) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(Ok(())), + Err(icmp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(icmp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.is_open() { + Poll::Ready(Err(SendError::NoRoute)) + } else { + Poll::Ready(Err(SendError::SocketNotBound)) + } + } + }) + } + + /// Enqueue a packet to be sent to a given remote address with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into, + F: FnOnce(&mut [u8]) -> R, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the icmp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < size); + if send_capacity_too_small { + return Err(SendError::PacketTooLarge); + } + + let mut f = Some(f); + let remote_endpoint = remote_endpoint.into(); + poll_fn(move |cx| { + self.with_mut(|s, _| match s.send(size, remote_endpoint) { + Ok(buf) => Poll::Ready(Ok({ unwrap!(f.take())(buf) })), + Err(icmp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(icmp::SendError::Unaddressable) => Poll::Ready(Err(SendError::NoRoute)), + }) + }) + .await + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } + + /// Check whether the socket is open. + pub fn is_open(&self) -> bool { + self.with(|s, _| s.is_open()) + } + + /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. + pub fn may_send(&self) -> bool { + self.with(|s, _| s.can_send()) + } + + /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. + pub fn may_recv(&self) -> bool { + self.with(|s, _| s.can_recv()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_recv_capacity(&self) -> usize { + self.with(|s, _| s.packet_recv_capacity()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_send_capacity(&self) -> usize { + self.with(|s, _| s.packet_send_capacity()) + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn payload_recv_capacity(&self) -> usize { + self.with(|s, _| s.payload_recv_capacity()) + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn payload_send_capacity(&self) -> usize { + self.with(|s, _| s.payload_send_capacity()) + } + + /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets. + pub fn hop_limit(&self) -> Option { + self.with(|s, _| s.hop_limit()) + } + + /// Set the hop limit field in the IP header of sent packets. + pub fn set_hop_limit(&mut self, hop_limit: Option) { + self.with_mut(|s, _| s.set_hop_limit(hop_limit)) + } +} + +impl Drop for IcmpSocket<'_> { + fn drop(&mut self) { + self.stack.with_mut(|i| i.sockets.remove(self.handle)); + } +} + +pub mod ping { + //! Ping utilities. + //! + //! This module allows for an easy ICMP Echo message interface used to + //! ping devices with an [ICMP Socket](IcmpSocket). + //! + //! ## Usage + //! + //! ``` + //! use core::net::Ipv4Addr; + //! use core::str::FromStr; + //! + //! use embassy_net::icmp::ping::{PingManager, PingParams}; + //! use embassy_net::icmp::PacketMetadata; + //! + //! let mut rx_buffer = [0; 256]; + //! let mut tx_buffer = [0; 256]; + //! let mut rx_meta = [PacketMetadata::EMPTY]; + //! let mut tx_meta = [PacketMetadata::EMPTY]; + //! + //! let mut ping_manager = PingManager::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + //! let addr = "192.168.8.1"; + //! let mut ping_params = PingParams::new(Ipv4Addr::from_str(addr).unwrap()); + //! ping_params.set_payload(b"Hello, router!"); + //! match ping_manager.ping(&ping_params).await { + //! Ok(time) => info!("Ping time of {}: {}ms", addr, time.as_millis()), + //! Err(ping_error) => warn!("{:?}", ping_error), + //! }; + //! ``` + + use core::net::IpAddr; + #[cfg(feature = "proto-ipv6")] + use core::net::Ipv6Addr; + + use embassy_time::{Duration, Instant, Timer, WithTimeout}; + #[cfg(feature = "proto-ipv6")] + use smoltcp::wire::IpAddress; + #[cfg(feature = "proto-ipv6")] + use smoltcp::wire::Ipv6Address; + + use super::*; + + /// Error returned by [`ping()`](PingManager::ping). + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PingError { + /// The target did not respond. + /// + /// The packet was sent but the Reply packet has not been recieved + /// in the timeout set by [`set_timeout()`](PingParams::set_timeout). + DestinationHostUnreachable, + /// The target has not been specified. + InvalidTargetAddress, + /// The source has not been specified (Ipv6 only). + #[cfg(feature = "proto-ipv6")] + InvalidSourceAddress, + /// The socket could not queue the packet in the buffer. + SocketSendTimeout, + /// Container error for [`icmp::BindError`]. + SocketBindError(BindError), + /// Container error for [`icmp::SendError`]. + SocketSendError(SendError), + /// Container error for [`icmp::RecvError`]. + SocketRecvError(RecvError), + } + + /// Manages ICMP ping operations. + /// + /// This struct provides functionality to send ICMP echo requests (pings) to a specified target + /// and measure the round-trip time for the requests. It supports both IPv4 and IPv6, depending + /// on the enabled features. + /// + /// # Fields + /// + /// * `stack` - The network stack instance used for managing network operations. + /// * `rx_meta` - Metadata buffer for receiving packets. + /// * `rx_buffer` - Buffer for receiving packets. + /// * `tx_meta` - Metadata buffer for transmitting packets. + /// * `tx_buffer` - Buffer for transmitting packets. + /// * `ident` - Identifier for the ICMP echo requests. + /// + /// # Methods + /// + /// * [`new`](PingManager::new) - Creates a new instance of `PingManager` with the specified stack and buffers. + /// * [`ping`](PingManager::ping) - Sends ICMP echo requests to the specified target and returns the average round-trip time. + pub struct PingManager<'d> { + stack: Stack<'d>, + rx_meta: &'d mut [PacketMetadata], + rx_buffer: &'d mut [u8], + tx_meta: &'d mut [PacketMetadata], + tx_buffer: &'d mut [u8], + ident: u16, + } + + impl<'d> PingManager<'d> { + /// Creates a new instance of [`PingManager`] with a [`Stack`] instance + /// and the buffers used for RX and TX. + /// + /// **note**: This does not yet creates the ICMP socket. + pub fn new( + stack: Stack<'d>, + rx_meta: &'d mut [PacketMetadata], + rx_buffer: &'d mut [u8], + tx_meta: &'d mut [PacketMetadata], + tx_buffer: &'d mut [u8], + ) -> Self { + Self { + stack, + rx_meta, + rx_buffer, + tx_meta, + tx_buffer, + ident: 0, + } + } + + /// Sends ICMP echo requests to the specified target and returns the average round-trip time. + /// + /// # Arguments + /// + /// * `params` - Parameters for configuring the ping operation. + /// + /// # Returns + /// + /// * `Ok(Duration)` - The average round-trip time for the ping requests. + /// * `Err(PingError)` - An error occurred during the ping operation. + pub async fn ping<'a>(&mut self, params: &PingParams<'a>) -> Result { + // Input validation + if params.target().is_none() { + return Err(PingError::InvalidTargetAddress); + } + #[cfg(feature = "proto-ipv6")] + if params.target().unwrap().is_ipv6() && params.source().is_none() { + return Err(PingError::InvalidSourceAddress); + } + // Increment the ident (wrapping u16) to respect standards + self.ident = self.ident.wrapping_add(1u16); + // Used to calculate the average duration + let mut total_duration = Duration::default(); + let mut num_of_durations = 0u16; + // Increment the sequence number as per standards + for seq_no in 0..params.count() { + // Make sure each ping takes at least 1 second to respect standards + let rate_limit_start = Instant::now(); + + // make a single ping + // - shorts out errors + // - select the ip version + let ping_duration = match params.target.unwrap() { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(_) => self.single_ping_v4(params, seq_no).await?, + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(_) => self.single_ping_v6(params, seq_no).await?, + }; + + // safely add up the durations of each ping + if let Some(dur) = total_duration.checked_add(ping_duration) { + total_duration = dur; + num_of_durations += 1; + } + + // 1 sec min per ping + let rate_limit_end = rate_limit_start.elapsed(); + if rate_limit_end <= params.rate_limit { + Timer::after(params.rate_limit.checked_sub(rate_limit_end).unwrap()).await; + } + } + // calculate and return the average duration + Ok(total_duration.checked_div(num_of_durations as u32).unwrap()) + } + + #[cfg(feature = "proto-ipv4")] + fn create_repr_ipv4<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv4Repr<'b> { + Icmpv4Repr::EchoRequest { + ident: self.ident, + seq_no, + data: params.payload, + } + } + + #[cfg(feature = "proto-ipv6")] + fn create_repr_ipv6<'b>(&self, params: &PingParams<'b>, seq_no: u16) -> Icmpv6Repr<'b> { + Icmpv6Repr::EchoRequest { + ident: self.ident, + seq_no, + data: params.payload, + } + } + + #[cfg(feature = "proto-ipv4")] + async fn single_ping_v4(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result { + let ping_repr = self.create_repr_ipv4(params, seq_no); + + // Create the socket and set hop limit and bind it to the endpoint with the ident + let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer); + socket.set_hop_limit(params.hop_limit); + if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) { + return Err(PingError::SocketBindError(e)); + } + + // Helper func to fill the buffer when sending the ICMP packet + fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv4Repr<'_>) -> Instant { + let mut icmp_packet = Icmpv4Packet::new_unchecked(buf); + ping_repr.emit(&mut icmp_packet, &ChecksumCapabilities::default()); + Instant::now() + } + + // Send with timeout the ICMP packet filling it with the helper function + let send_result = socket + .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| { + fill_packet_buffer(buf, ping_repr) + }) + .with_timeout(Duration::from_millis(100)) + .await; + // Filter and translate potential errors from sending the packet + let now = match send_result { + Ok(send_result) => match send_result { + Ok(i) => i, + Err(e) => return Err(PingError::SocketSendError(e)), + }, + Err(_) => return Err(PingError::SocketSendTimeout), + }; + + // Helper function for the recieve helper function to validate the echo reply + fn filter_pong(buf: &[u8], seq_no: u16) -> bool { + let pong_packet = match Icmpv4Packet::new_checked(buf) { + Ok(pak) => pak, + Err(_) => return false, + }; + pong_packet.echo_seq_no() == seq_no + } + + // Helper function to recieve and return the correct echo reply when it finds it + async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { + while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await { + Ok(b) => !b, + Err(e) => return Err(PingError::SocketRecvError(e)), + } {} + Ok(()) + } + + // Calls the recieve helper function with a timeout + match recv_pong(&socket, seq_no).with_timeout(params.timeout).await { + Ok(res) => res?, + Err(_) => return Err(PingError::DestinationHostUnreachable), + } + + // Return the round trip duration + Ok(now.elapsed()) + } + + #[cfg(feature = "proto-ipv6")] + async fn single_ping_v6(&mut self, params: &PingParams<'_>, seq_no: u16) -> Result { + let ping_repr = self.create_repr_ipv6(params, seq_no); + + // Create the socket and set hop limit and bind it to the endpoint with the ident + let mut socket = IcmpSocket::new(self.stack, self.rx_meta, self.rx_buffer, self.tx_meta, self.tx_buffer); + socket.set_hop_limit(params.hop_limit); + if let Err(e) = socket.bind(IcmpEndpoint::Ident(self.ident)) { + return Err(PingError::SocketBindError(e)); + } + + // Helper func to fill the buffer when sending the ICMP packet + fn fill_packet_buffer(buf: &mut [u8], ping_repr: Icmpv6Repr<'_>, params: &PingParams<'_>) -> Instant { + let mut icmp_packet = Icmpv6Packet::new_unchecked(buf); + let target = match params.target().unwrap() { + IpAddr::V4(_) => unreachable!(), + IpAddr::V6(addr) => addr, + }; + ping_repr.emit( + ¶ms.source().unwrap(), + &target, + &mut icmp_packet, + &ChecksumCapabilities::default(), + ); + Instant::now() + } + + // Send with timeout the ICMP packet filling it with the helper function + let send_result = socket + .send_to_with(ping_repr.buffer_len(), params.target.unwrap(), |buf| { + fill_packet_buffer(buf, ping_repr, params) + }) + .with_timeout(Duration::from_millis(100)) + .await; + let now = match send_result { + Ok(send_result) => match send_result { + Ok(i) => i, + Err(e) => return Err(PingError::SocketSendError(e)), + }, + Err(_) => return Err(PingError::SocketSendTimeout), + }; + + // Helper function for the recieve helper function to validate the echo reply + fn filter_pong(buf: &[u8], seq_no: u16) -> bool { + let pong_packet = match Icmpv6Packet::new_checked(buf) { + Ok(pak) => pak, + Err(_) => return false, + }; + pong_packet.echo_seq_no() == seq_no + } + + // Helper function to recieve and return the correct echo reply when it finds it + async fn recv_pong(socket: &IcmpSocket<'_>, seq_no: u16) -> Result<(), PingError> { + while match socket.recv_from_with(|(buf, _)| filter_pong(buf, seq_no)).await { + Ok(b) => !b, + Err(e) => return Err(PingError::SocketRecvError(e)), + } {} + Ok(()) + } + + // Calls the recieve helper function with a timeout + match recv_pong(&socket, seq_no).with_timeout(params.timeout).await { + Ok(res) => res?, + Err(_) => return Err(PingError::DestinationHostUnreachable), + } + + // Return the round trip duration + Ok(now.elapsed()) + } + } + + /// Parameters for configuring the ping operation. + /// + /// This struct provides various configuration options for performing ICMP ping operations, + /// including the target IP address, payload data, hop limit, number of pings, and timeout duration. + /// + /// # Fields + /// + /// * `target` - The target IP address for the ping operation. + /// * `source` - The source IP address for the ping operation (IPv6 only). + /// * `payload` - The data to be sent in the payload field of the ping. + /// * `hop_limit` - The hop limit to be used by the socket. + /// * `count` - The number of pings to be sent in one ping operation. + /// * `timeout` - The timeout duration before returning a [`PingError::DestinationHostUnreachable`] error. + /// * `rate_limit` - The minimum time per echo request. + pub struct PingParams<'a> { + target: Option, + #[cfg(feature = "proto-ipv6")] + source: Option, + payload: &'a [u8], + hop_limit: Option, + count: u16, + timeout: Duration, + rate_limit: Duration, + } + + impl Default for PingParams<'_> { + fn default() -> Self { + Self { + target: None, + #[cfg(feature = "proto-ipv6")] + source: None, + payload: b"embassy-net", + hop_limit: None, + count: 4, + timeout: Duration::from_secs(4), + rate_limit: Duration::from_secs(1), + } + } + } + + impl<'a> PingParams<'a> { + /// Creates a new instance of [`PingParams`] with the specified target IP address. + pub fn new>(target: T) -> Self { + Self { + target: Some(PingParams::ip_addr_to_smoltcp(target)), + #[cfg(feature = "proto-ipv6")] + source: None, + payload: b"embassy-net", + hop_limit: None, + count: 4, + timeout: Duration::from_secs(4), + rate_limit: Duration::from_secs(1), + } + } + + fn ip_addr_to_smoltcp>(ip_addr: T) -> IpAddress { + match ip_addr.into() { + #[cfg(feature = "proto-ipv4")] + IpAddr::V4(v4) => IpAddress::Ipv4(v4), + #[cfg(not(feature = "proto-ipv4"))] + IpAddr::V4(_) => unreachable!(), + #[cfg(feature = "proto-ipv6")] + IpAddr::V6(v6) => IpAddress::Ipv6(v6), + #[cfg(not(feature = "proto-ipv6"))] + IpAddr::V6(_) => unreachable!(), + } + } + + /// Sets the target IP address for the ping. + pub fn set_target>(&mut self, target: T) -> &mut Self { + self.target = Some(PingParams::ip_addr_to_smoltcp(target)); + self + } + + /// Retrieves the target IP address for the ping. + pub fn target(&self) -> Option { + self.target.map(|t| t.into()) + } + + /// Sets the source IP address for the ping (IPv6 only). + #[cfg(feature = "proto-ipv6")] + pub fn set_source>(&mut self, source: T) -> &mut Self { + self.source = Some(source.into()); + self + } + + /// Retrieves the source IP address for the ping (IPv6 only). + #[cfg(feature = "proto-ipv6")] + pub fn source(&self) -> Option { + self.source + } + + /// Sets the data used in the payload field of the ping with the provided slice. + pub fn set_payload(&mut self, payload: &'a [u8]) -> &mut Self { + self.payload = payload; + self + } + + /// Gives a reference to the slice of data that's going to be sent in the payload field + /// of the ping. + pub fn payload(&self) -> &'a [u8] { + self.payload + } + + /// Sets the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit). + /// + /// **Note**: A hop limit of [`Some(0)`](Some()) is equivalent to a hop limit of [`None`]. + pub fn set_hop_limit(&mut self, hop_limit: Option) -> &mut Self { + let mut hop_limit = hop_limit; + if hop_limit.is_some_and(|x| x == 0) { + hop_limit = None + } + self.hop_limit = hop_limit; + self + } + + /// Retrieves the hop limit that will be used by the socket with [`set_hop_limit()`](IcmpSocket::set_hop_limit). + pub fn hop_limit(&self) -> Option { + self.hop_limit + } + + /// Sets the count used for specifying the number of pings done on one + /// [`ping()`](PingManager::ping) call. + /// + /// **Note**: A count of 0 will be set as 1. + pub fn set_count(&mut self, count: u16) -> &mut Self { + let mut count = count; + if count == 0 { + count = 1; + } + self.count = count; + self + } + + /// Retrieve the count used for specifying the number of pings done on one + /// [`ping()`](PingManager::ping) call. + pub fn count(&self) -> u16 { + self.count + } + + /// Sets the timeout used before returning [`PingError::DestinationHostUnreachable`] + /// when waiting for the Echo Reply icmp packet. + pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self { + self.timeout = timeout; + self + } + + /// Retrieve the timeout used before returning [`PingError::DestinationHostUnreachable`] + /// when waiting for the Echo Reply icmp packet. + pub fn timeout(&self) -> Duration { + self.timeout + } + + /// Sets the `rate_limit`: minimum time per echo request. + pub fn set_rate_limit(&mut self, rate_limit: Duration) -> &mut Self { + self.rate_limit = rate_limit; + self + } + + /// Retrieve the rate_limit. + pub fn rate_limit(&self) -> Duration { + self.rate_limit + } + } +} diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs new file mode 100644 index 0000000..693a39e --- /dev/null +++ b/embassy-net/src/lib.rs @@ -0,0 +1,892 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +#[cfg(not(any(feature = "proto-ipv4", feature = "proto-ipv6")))] +compile_error!("You must enable at least one of the following features: proto-ipv4, proto-ipv6"); + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +#[cfg(feature = "dns")] +pub mod dns; +mod driver_util; +#[cfg(feature = "icmp")] +pub mod icmp; +#[cfg(feature = "raw")] +pub mod raw; +#[cfg(feature = "tcp")] +pub mod tcp; +mod time; +#[cfg(feature = "udp")] +pub mod udp; + +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; +use core::pin::pin; +use core::task::{Context, Poll}; + +pub use embassy_net_driver as driver; +use embassy_net_driver::{Driver, LinkState}; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_time::{Instant, Timer}; +use heapless::Vec; +#[cfg(feature = "dns")] +pub use smoltcp::config::DNS_MAX_SERVER_COUNT; +#[cfg(feature = "multicast")] +pub use smoltcp::iface::MulticastError; +#[cfg(any(feature = "dns", feature = "dhcpv4"))] +use smoltcp::iface::SocketHandle; +use smoltcp::iface::{Interface, SocketSet, SocketStorage}; +use smoltcp::phy::Medium; +#[cfg(feature = "dhcpv4")] +use smoltcp::socket::dhcpv4::{self, RetryConfig}; +#[cfg(feature = "medium-ethernet")] +pub use smoltcp::wire::EthernetAddress; +#[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154", feature = "medium-ip"))] +pub use smoltcp::wire::HardwareAddress; +#[cfg(any(feature = "udp", feature = "tcp"))] +pub use smoltcp::wire::IpListenEndpoint; +#[cfg(feature = "medium-ieee802154")] +pub use smoltcp::wire::{Ieee802154Address, Ieee802154Frame}; +pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint}; +#[cfg(feature = "proto-ipv4")] +pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; +#[cfg(feature = "proto-ipv6")] +pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; + +use crate::driver_util::DriverAdapter; +use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; + +const LOCAL_PORT_MIN: u16 = 1025; +const LOCAL_PORT_MAX: u16 = 65535; +#[cfg(feature = "dns")] +const MAX_QUERIES: usize = 4; +#[cfg(feature = "dhcpv4-hostname")] +const MAX_HOSTNAME_LEN: usize = 32; + +/// Memory resources needed for a network stack. +pub struct StackResources { + sockets: MaybeUninit<[SocketStorage<'static>; SOCK]>, + inner: MaybeUninit>, + #[cfg(feature = "dns")] + queries: MaybeUninit<[Option; MAX_QUERIES]>, + #[cfg(feature = "dhcpv4-hostname")] + hostname: HostnameResources, +} + +#[cfg(feature = "dhcpv4-hostname")] +struct HostnameResources { + option: MaybeUninit>, + data: MaybeUninit<[u8; MAX_HOSTNAME_LEN]>, +} + +impl StackResources { + /// Create a new set of stack resources. + pub const fn new() -> Self { + Self { + sockets: MaybeUninit::uninit(), + inner: MaybeUninit::uninit(), + #[cfg(feature = "dns")] + queries: MaybeUninit::uninit(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: HostnameResources { + option: MaybeUninit::uninit(), + data: MaybeUninit::uninit(), + }, + } + } +} + +/// Static IP address configuration. +#[cfg(feature = "proto-ipv4")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV4 { + /// IP address and subnet mask. + pub address: Ipv4Cidr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: Vec, +} + +/// Static IPv6 address configuration +#[cfg(feature = "proto-ipv6")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV6 { + /// IP address and subnet mask. + pub address: Ipv6Cidr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: Vec, +} + +/// DHCP configuration. +#[cfg(feature = "dhcpv4")] +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub struct DhcpConfig { + /// Maximum lease duration. + /// + /// If not set, the lease duration specified by the server will be used. + /// If set, the lease duration will be capped at this value. + pub max_lease_duration: Option, + /// Retry configuration. + pub retry_config: RetryConfig, + /// Ignore NAKs from DHCP servers. + /// + /// This is not compliant with the DHCP RFCs, since theoretically we must stop using the assigned IP when receiving a NAK. This can increase reliability on broken networks with buggy routers or rogue DHCP servers, however. + pub ignore_naks: bool, + /// Server port. This is almost always 67. Do not change unless you know what you're doing. + pub server_port: u16, + /// Client port. This is almost always 68. Do not change unless you know what you're doing. + pub client_port: u16, + /// Our hostname. This will be sent to the DHCP server as Option 12. + #[cfg(feature = "dhcpv4-hostname")] + pub hostname: Option>, +} + +#[cfg(feature = "dhcpv4")] +impl Default for DhcpConfig { + fn default() -> Self { + Self { + max_lease_duration: Default::default(), + retry_config: Default::default(), + ignore_naks: Default::default(), + server_port: smoltcp::wire::DHCP_SERVER_PORT, + client_port: smoltcp::wire::DHCP_CLIENT_PORT, + #[cfg(feature = "dhcpv4-hostname")] + hostname: None, + } + } +} + +/// Network stack configuration. +#[derive(Debug, Clone, Default)] +#[non_exhaustive] +pub struct Config { + /// IPv4 configuration + #[cfg(feature = "proto-ipv4")] + pub ipv4: ConfigV4, + /// IPv6 configuration + #[cfg(feature = "proto-ipv6")] + pub ipv6: ConfigV6, +} + +impl Config { + /// IPv4 configuration with static addressing. + #[cfg(feature = "proto-ipv4")] + pub const fn ipv4_static(config: StaticConfigV4) -> Self { + Self { + ipv4: ConfigV4::Static(config), + #[cfg(feature = "proto-ipv6")] + ipv6: ConfigV6::None, + } + } + + /// IPv6 configuration with static addressing. + #[cfg(feature = "proto-ipv6")] + pub const fn ipv6_static(config: StaticConfigV6) -> Self { + Self { + #[cfg(feature = "proto-ipv4")] + ipv4: ConfigV4::None, + ipv6: ConfigV6::Static(config), + } + } + + /// IPv4 configuration with dynamic addressing. + /// + /// # Example + /// ```rust + /// # use embassy_net::Config; + /// let _cfg = Config::dhcpv4(Default::default()); + /// ``` + #[cfg(feature = "dhcpv4")] + pub const fn dhcpv4(config: DhcpConfig) -> Self { + Self { + ipv4: ConfigV4::Dhcp(config), + #[cfg(feature = "proto-ipv6")] + ipv6: ConfigV6::None, + } + } +} + +/// Network stack IPv4 configuration. +#[cfg(feature = "proto-ipv4")] +#[derive(Debug, Clone, Default)] +pub enum ConfigV4 { + /// Do not configure IPv4. + #[default] + None, + /// Use a static IPv4 address configuration. + Static(StaticConfigV4), + /// Use DHCP to obtain an IP address configuration. + #[cfg(feature = "dhcpv4")] + Dhcp(DhcpConfig), +} + +/// Network stack IPv6 configuration. +#[cfg(feature = "proto-ipv6")] +#[derive(Debug, Clone, Default)] +pub enum ConfigV6 { + /// Do not configure IPv6. + #[default] + None, + /// Use a static IPv6 address configuration. + Static(StaticConfigV6), +} + +/// Network stack runner. +/// +/// You must call [`Runner::run()`] in a background task for the network stack to work. +pub struct Runner<'d, D: Driver> { + driver: D, + stack: Stack<'d>, +} + +/// Network stack handle +/// +/// Use this to create sockets. It's `Copy`, so you can pass +/// it by value instead of by reference. +#[derive(Copy, Clone)] +pub struct Stack<'d> { + inner: &'d RefCell, +} + +pub(crate) struct Inner { + pub(crate) sockets: SocketSet<'static>, // Lifetime type-erased. + pub(crate) iface: Interface, + /// Waker used for triggering polls. + pub(crate) waker: WakerRegistration, + /// Waker used for waiting for link up or config up. + state_waker: WakerRegistration, + hardware_address: HardwareAddress, + next_local_port: u16, + link_up: bool, + #[cfg(feature = "proto-ipv4")] + static_v4: Option, + #[cfg(feature = "proto-ipv6")] + static_v6: Option, + #[cfg(feature = "dhcpv4")] + dhcp_socket: Option, + #[cfg(feature = "dns")] + dns_socket: SocketHandle, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration, + #[cfg(feature = "dhcpv4-hostname")] + hostname: *mut HostnameResources, +} + +fn _assert_covariant<'a, 'b: 'a>(x: Stack<'b>) -> Stack<'a> { + x +} + +/// Create a new network stack. +pub fn new<'d, D: Driver, const SOCK: usize>( + mut driver: D, + config: Config, + resources: &'d mut StackResources, + random_seed: u64, +) -> (Stack<'d>, Runner<'d, D>) { + let (hardware_address, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + let mut iface_cfg = smoltcp::iface::Config::new(hardware_address); + iface_cfg.random_seed = random_seed; + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut driver, + cx: None, + medium, + }, + instant_to_smoltcp(Instant::now()), + ); + + unsafe fn transmute_slice(x: &mut [T]) -> &'static mut [T] { + core::mem::transmute(x) + } + + let sockets = resources.sockets.write([SocketStorage::EMPTY; SOCK]); + #[allow(unused_mut)] + let mut sockets: SocketSet<'static> = SocketSet::new(unsafe { transmute_slice(sockets) }); + + let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + #[cfg(feature = "dns")] + let dns_socket = sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(unsafe { + transmute_slice(resources.queries.write([const { None }; MAX_QUERIES])) + }), + )); + + let mut inner = Inner { + sockets, + iface, + waker: WakerRegistration::new(), + state_waker: WakerRegistration::new(), + next_local_port, + hardware_address, + link_up: false, + #[cfg(feature = "proto-ipv4")] + static_v4: None, + #[cfg(feature = "proto-ipv6")] + static_v6: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + #[cfg(feature = "dhcpv4-hostname")] + hostname: &mut resources.hostname, + }; + + #[cfg(feature = "proto-ipv4")] + inner.set_config_v4(config.ipv4); + #[cfg(feature = "proto-ipv6")] + inner.set_config_v6(config.ipv6); + inner.apply_static_config(); + + let inner = &*resources.inner.write(RefCell::new(inner)); + let stack = Stack { inner }; + (stack, Runner { driver, stack }) +} + +fn to_smoltcp_hardware_address(addr: driver::HardwareAddress) -> (HardwareAddress, Medium) { + match addr { + #[cfg(feature = "medium-ethernet")] + driver::HardwareAddress::Ethernet(eth) => (HardwareAddress::Ethernet(EthernetAddress(eth)), Medium::Ethernet), + #[cfg(feature = "medium-ieee802154")] + driver::HardwareAddress::Ieee802154(ieee) => ( + HardwareAddress::Ieee802154(Ieee802154Address::Extended(ieee)), + Medium::Ieee802154, + ), + #[cfg(feature = "medium-ip")] + driver::HardwareAddress::Ip => (HardwareAddress::Ip, Medium::Ip), + + #[allow(unreachable_patterns)] + _ => panic!( + "Unsupported medium {:?}. Make sure to enable the right medium feature in embassy-net's Cargo features.", + addr + ), + } +} + +impl<'d> Stack<'d> { + fn with(&self, f: impl FnOnce(&Inner) -> R) -> R { + f(&self.inner.borrow()) + } + + fn with_mut(&self, f: impl FnOnce(&mut Inner) -> R) -> R { + f(&mut self.inner.borrow_mut()) + } + + /// Get the hardware address of the network interface. + pub fn hardware_address(&self) -> HardwareAddress { + self.with(|i| i.hardware_address) + } + + /// Check whether the link is up. + pub fn is_link_up(&self) -> bool { + self.with(|i| i.link_up) + } + + /// Check whether the network stack has a valid IP configuration. + /// This is true if the network stack has a static IP configuration or if DHCP has completed + pub fn is_config_up(&self) -> bool { + let v4_up; + let v6_up; + + #[cfg(feature = "proto-ipv4")] + { + v4_up = self.config_v4().is_some(); + } + #[cfg(not(feature = "proto-ipv4"))] + { + v4_up = false; + } + + #[cfg(feature = "proto-ipv6")] + { + v6_up = self.config_v6().is_some(); + } + #[cfg(not(feature = "proto-ipv6"))] + { + v6_up = false; + } + + v4_up || v6_up + } + + /// Wait for the network device to obtain a link signal. + pub async fn wait_link_up(&self) { + self.wait(|| self.is_link_up()).await + } + + /// Wait for the network device to lose link signal. + pub async fn wait_link_down(&self) { + self.wait(|| !self.is_link_up()).await + } + + /// Wait for the network stack to obtain a valid IP configuration. + /// + /// ## Notes: + /// - Ensure [`Runner::run`] has been started before using this function. + /// + /// - This function may never return (e.g. if no configuration is obtained through DHCP). + /// The caller is supposed to handle a timeout for this case. + /// + /// ## Example + /// ```ignore + /// let config = embassy_net::Config::dhcpv4(Default::default()); + /// // Init network stack + /// // NOTE: DHCP and DNS need one socket slot if enabled. This is why we're + /// // provisioning space for 3 sockets here: one for DHCP, one for DNS, and one for your code (e.g. TCP). + /// // If you use more sockets you must increase this. If you don't enable DHCP or DNS you can decrease it. + /// static RESOURCES: StaticCell> = StaticCell::new(); + /// let (stack, runner) = embassy_net::new( + /// driver, + /// config, + /// RESOURCES.init(embassy_net::StackResources::new()), + /// seed + /// ); + /// // Launch network task that runs `runner.run().await` + /// spawner.spawn(net_task(runner)).unwrap(); + /// // Wait for DHCP config + /// stack.wait_config_up().await; + /// // use the network stack + /// // ... + /// ``` + pub async fn wait_config_up(&self) { + self.wait(|| self.is_config_up()).await + } + + /// Wait for the network stack to lose a valid IP configuration. + pub async fn wait_config_down(&self) { + self.wait(|| !self.is_config_up()).await + } + + fn wait<'a>(&'a self, mut predicate: impl FnMut() -> bool + 'a) -> impl Future + 'a { + poll_fn(move |cx| { + if predicate() { + Poll::Ready(()) + } else { + // If the config is not up, we register a waker that is woken up + // when a config is applied (static or DHCP). + trace!("Waiting for config up"); + + self.with_mut(|i| { + i.state_waker.register(cx.waker()); + }); + + Poll::Pending + } + }) + } + + /// Get the current IPv4 configuration. + /// + /// If using DHCP, this will be None if DHCP hasn't been able to + /// acquire an IP address, or Some if it has. + #[cfg(feature = "proto-ipv4")] + pub fn config_v4(&self) -> Option { + self.with(|i| i.static_v4.clone()) + } + + /// Get the current IPv6 configuration. + #[cfg(feature = "proto-ipv6")] + pub fn config_v6(&self) -> Option { + self.with(|i| i.static_v6.clone()) + } + + /// Set the IPv4 configuration. + #[cfg(feature = "proto-ipv4")] + pub fn set_config_v4(&self, config: ConfigV4) { + self.with_mut(|i| { + i.set_config_v4(config); + i.apply_static_config(); + }) + } + + /// Set the IPv6 configuration. + #[cfg(feature = "proto-ipv6")] + pub fn set_config_v6(&self, config: ConfigV6) { + self.with_mut(|i| { + i.set_config_v6(config); + i.apply_static_config(); + }) + } + + /// Make a query for a given name and return the corresponding IP addresses. + #[cfg(feature = "dns")] + pub async fn dns_query( + &self, + name: &str, + qtype: dns::DnsQueryType, + ) -> Result, dns::Error> { + // For A and AAAA queries we try detect whether `name` is just an IP address + match qtype { + #[cfg(feature = "proto-ipv4")] + dns::DnsQueryType::A => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv4) { + return Ok([ip].into_iter().collect()); + } + } + #[cfg(feature = "proto-ipv6")] + dns::DnsQueryType::Aaaa => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv6) { + return Ok([ip].into_iter().collect()); + } + } + _ => {} + } + + let query = poll_fn(|cx| { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.start_query(i.iface.context(), name, qtype) { + Ok(handle) => { + i.waker.wake(); + Poll::Ready(Ok(handle)) + } + Err(dns::StartQueryError::NoFreeSlot) => { + i.dns_waker.register(cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e)), + } + }) + }) + .await?; + + #[must_use = "to delay the drop handler invocation to the end of the scope"] + struct OnDrop { + f: core::mem::MaybeUninit, + } + + impl OnDrop { + fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } + } + + fn defuse(self) { + core::mem::forget(self) + } + } + + impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } + } + + let drop = OnDrop::new(|| { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + socket.cancel_query(query); + i.waker.wake(); + i.dns_waker.wake(); + }) + }); + + let res = poll_fn(|cx| { + self.with_mut(|i| { + let socket = i.sockets.get_mut::(i.dns_socket); + match socket.get_query_result(query) { + Ok(addrs) => { + i.dns_waker.wake(); + Poll::Ready(Ok(addrs)) + } + Err(dns::GetQueryResultError::Pending) => { + socket.register_query_waker(query, cx.waker()); + Poll::Pending + } + Err(e) => { + i.dns_waker.wake(); + Poll::Ready(Err(e.into())) + } + } + }) + }) + .await; + + drop.defuse(); + + res + } +} + +#[cfg(feature = "multicast")] +impl<'d> Stack<'d> { + /// Join a multicast group. + pub fn join_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.join_multicast_group(addr)) + } + + /// Leave a multicast group. + pub fn leave_multicast_group(&self, addr: impl Into) -> Result<(), MulticastError> { + self.with_mut(|i| i.iface.leave_multicast_group(addr)) + } + + /// Get whether the network stack has joined the given multicast group. + pub fn has_multicast_group(&self, addr: impl Into) -> bool { + self.with(|i| i.iface.has_multicast_group(addr)) + } +} + +impl Inner { + #[allow(clippy::absurd_extreme_comparisons)] + pub fn get_local_port(&mut self) -> u16 { + let res = self.next_local_port; + self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; + res + } + + #[cfg(feature = "proto-ipv4")] + pub fn set_config_v4(&mut self, config: ConfigV4) { + // Handle static config. + self.static_v4 = match config.clone() { + ConfigV4::None => None, + #[cfg(feature = "dhcpv4")] + ConfigV4::Dhcp(_) => None, + ConfigV4::Static(c) => Some(c), + }; + + // Handle DHCP config. + #[cfg(feature = "dhcpv4")] + match config { + ConfigV4::Dhcp(c) => { + // Create the socket if it doesn't exist. + if self.dhcp_socket.is_none() { + let socket = smoltcp::socket::dhcpv4::Socket::new(); + let handle = self.sockets.add(socket); + self.dhcp_socket = Some(handle); + } + + // Configure it + let socket = self.sockets.get_mut::(unwrap!(self.dhcp_socket)); + socket.set_ignore_naks(c.ignore_naks); + socket.set_max_lease_duration(c.max_lease_duration.map(crate::time::duration_to_smoltcp)); + socket.set_ports(c.server_port, c.client_port); + socket.set_retry_config(c.retry_config); + + socket.set_outgoing_options(&[]); + #[cfg(feature = "dhcpv4-hostname")] + if let Some(h) = c.hostname { + // safety: + // - we just did set_outgoing_options([]) so we know the socket is no longer holding a reference. + // - we know this pointer lives for as long as the stack exists, because `new()` borrows + // the resources for `'d`. Therefore it's OK to pass a reference to this to smoltcp. + let hostname = unsafe { &mut *self.hostname }; + + // create data + let data = hostname.data.write([0; MAX_HOSTNAME_LEN]); + data[..h.len()].copy_from_slice(h.as_bytes()); + let data: &[u8] = &data[..h.len()]; + + // set the option. + let option = hostname.option.write(smoltcp::wire::DhcpOption { data, kind: 12 }); + socket.set_outgoing_options(core::slice::from_ref(option)); + } + + socket.reset(); + } + _ => { + // Remove DHCP socket if any. + if let Some(socket) = self.dhcp_socket { + self.sockets.remove(socket); + self.dhcp_socket = None; + } + } + } + } + + #[cfg(feature = "proto-ipv6")] + pub fn set_config_v6(&mut self, config: ConfigV6) { + self.static_v6 = match config { + ConfigV6::None => None, + ConfigV6::Static(c) => Some(c), + }; + } + + fn apply_static_config(&mut self) { + let mut addrs = Vec::new(); + #[cfg(feature = "dns")] + let mut dns_servers: Vec<_, 6> = Vec::new(); + #[cfg(feature = "proto-ipv4")] + let mut gateway_v4 = None; + #[cfg(feature = "proto-ipv6")] + let mut gateway_v6 = None; + + #[cfg(feature = "proto-ipv4")] + if let Some(config) = &self.static_v4 { + debug!("IPv4: UP"); + debug!(" IP address: {:?}", config.address); + debug!(" Default gateway: {:?}", config.gateway); + + unwrap!(addrs.push(IpCidr::Ipv4(config.address)).ok()); + gateway_v4 = config.gateway; + #[cfg(feature = "dns")] + for s in &config.dns_servers { + debug!(" DNS server: {:?}", s); + unwrap!(dns_servers.push(s.clone().into()).ok()); + } + } else { + info!("IPv4: DOWN"); + } + + #[cfg(feature = "proto-ipv6")] + if let Some(config) = &self.static_v6 { + debug!("IPv6: UP"); + debug!(" IP address: {:?}", config.address); + debug!(" Default gateway: {:?}", config.gateway); + + unwrap!(addrs.push(IpCidr::Ipv6(config.address)).ok()); + gateway_v6 = config.gateway.into(); + #[cfg(feature = "dns")] + for s in &config.dns_servers { + debug!(" DNS server: {:?}", s); + unwrap!(dns_servers.push(s.clone().into()).ok()); + } + } else { + info!("IPv6: DOWN"); + } + + // Apply addresses + self.iface.update_ip_addrs(|a| *a = addrs); + + // Apply gateways + #[cfg(feature = "proto-ipv4")] + if let Some(gateway) = gateway_v4 { + unwrap!(self.iface.routes_mut().add_default_ipv4_route(gateway)); + } else { + self.iface.routes_mut().remove_default_ipv4_route(); + } + #[cfg(feature = "proto-ipv6")] + if let Some(gateway) = gateway_v6 { + unwrap!(self.iface.routes_mut().add_default_ipv6_route(gateway)); + } else { + self.iface.routes_mut().remove_default_ipv6_route(); + } + + // Apply DNS servers + #[cfg(feature = "dns")] + if !dns_servers.is_empty() { + let count = if dns_servers.len() > DNS_MAX_SERVER_COUNT { + warn!("Number of DNS servers exceeds DNS_MAX_SERVER_COUNT, truncating list."); + DNS_MAX_SERVER_COUNT + } else { + dns_servers.len() + }; + self.sockets + .get_mut::(self.dns_socket) + .update_servers(&dns_servers[..count]); + } + + self.state_waker.wake(); + } + + fn poll(&mut self, cx: &mut Context<'_>, driver: &mut D) { + self.waker.register(cx.waker()); + + let (_hardware_addr, medium) = to_smoltcp_hardware_address(driver.hardware_address()); + + #[cfg(any(feature = "medium-ethernet", feature = "medium-ieee802154"))] + { + let do_set = match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => true, + #[cfg(feature = "medium-ieee802154")] + Medium::Ieee802154 => true, + #[allow(unreachable_patterns)] + _ => false, + }; + if do_set { + self.iface.set_hardware_addr(_hardware_addr); + } + } + + let timestamp = instant_to_smoltcp(Instant::now()); + let mut smoldev = DriverAdapter { + cx: Some(cx), + inner: driver, + medium, + }; + self.iface.poll(timestamp, &mut smoldev, &mut self.sockets); + + // Update link up + let old_link_up = self.link_up; + self.link_up = driver.link_state(cx) == LinkState::Up; + + // Print when changed + if old_link_up != self.link_up { + info!("link_up = {:?}", self.link_up); + self.state_waker.wake(); + } + + #[cfg(feature = "dhcpv4")] + if let Some(dhcp_handle) = self.dhcp_socket { + let socket = self.sockets.get_mut::(dhcp_handle); + + let configure = if self.link_up { + if old_link_up != self.link_up { + socket.reset(); + } + match socket.poll() { + None => false, + Some(dhcpv4::Event::Deconfigured) => { + self.static_v4 = None; + true + } + Some(dhcpv4::Event::Configured(config)) => { + self.static_v4 = Some(StaticConfigV4 { + address: config.address, + gateway: config.router, + dns_servers: config.dns_servers, + }); + true + } + } + } else if old_link_up { + socket.reset(); + self.static_v4 = None; + true + } else { + false + }; + if configure { + self.apply_static_config() + } + } + + if let Some(poll_at) = self.iface.poll_at(timestamp, &mut self.sockets) { + let t = pin!(Timer::at(instant_from_smoltcp(poll_at))); + if t.poll(cx).is_ready() { + cx.waker().wake_by_ref(); + } + } + } +} + +impl<'d, D: Driver> Runner<'d, D> { + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. + pub async fn run(&mut self) -> ! { + poll_fn(|cx| { + self.stack.with_mut(|i| i.poll(cx, &mut self.driver)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } +} diff --git a/embassy-net/src/raw.rs b/embassy-net/src/raw.rs new file mode 100644 index 0000000..c9f753f --- /dev/null +++ b/embassy-net/src/raw.rs @@ -0,0 +1,190 @@ +//! Raw sockets. + +use core::future::{poll_fn, Future}; +use core::mem; +use core::task::{Context, Poll}; + +use embassy_net_driver::Driver; +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::raw; +pub use smoltcp::socket::raw::PacketMetadata; +pub use smoltcp::wire::{IpProtocol, IpVersion}; + +use crate::Stack; + +/// Error returned by [`RawSocket::recv`] and [`RawSocket::send`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + /// Provided buffer was smaller than the received packet. + Truncated, +} + +/// An Raw socket. +pub struct RawSocket<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'a> RawSocket<'a> { + /// Create a new Raw socket using the provided stack and buffers. + pub fn new( + stack: Stack<'a>, + ip_version: IpVersion, + ip_protocol: IpProtocol, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(raw::Socket::new( + ip_version, + ip_protocol, + raw::PacketBuffer::new(rx_meta, rx_buffer), + raw::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); + + Self { stack, handle } + } + + fn with_mut(&self, f: impl FnOnce(&mut raw::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub fn wait_recv_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_recv_ready(cx)) + } + + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + pub async fn recv(&self, buf: &mut [u8]) -> Result { + poll_fn(move |cx| self.poll_recv(buf, cx)).await + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + pub fn poll_recv(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok(n) => Poll::Ready(Ok(n)), + // No data ready + Err(raw::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + Err(raw::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub fn wait_send_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_send_ready(cx)) + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram. + /// + /// This method will wait until the datagram has been sent.` + pub fn send<'s>(&'s self, buf: &'s [u8]) -> impl Future + 's { + poll_fn(|cx| self.poll_send(buf, cx)) + } + + /// Send a datagram. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + pub fn poll_send(&self, buf: &[u8], cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| match s.send_slice(buf) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(()), + Err(raw::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } +} + +impl Drop for RawSocket<'_> { + fn drop(&mut self) { + self.stack.with_mut(|i| i.sockets.remove(self.handle)); + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: RawSocket<'b>) -> RawSocket<'a> { + x +} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs new file mode 100644 index 0000000..d0230b5 --- /dev/null +++ b/embassy-net/src/tcp.rs @@ -0,0 +1,920 @@ +//! TCP sockets. +//! +//! # Listening +//! +//! `embassy-net` does not have a `TcpListener`. Instead, individual `TcpSocket`s can be put into +//! listening mode by calling [`TcpSocket::accept`]. +//! +//! Incoming connections when no socket is listening are rejected. To accept many incoming +//! connections, create many sockets and put them all into listening mode. + +use core::future::{poll_fn, Future}; +use core::mem; +use core::task::{Context, Poll}; + +use embassy_time::Duration; +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::tcp; +pub use smoltcp::socket::tcp::State; +use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; + +use crate::time::duration_to_smoltcp; +use crate::Stack; + +/// Error returned by TcpSocket read/write functions. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The connection was reset. + /// + /// This can happen on receiving a RST packet, or on timeout. + ConnectionReset, +} + +/// Error returned by [`TcpSocket::connect`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConnectError { + /// The socket is already connected or listening. + InvalidState, + /// The remote host rejected the connection with a RST packet. + ConnectionReset, + /// Connect timed out. + TimedOut, + /// No route to host. + NoRoute, +} + +/// Error returned by [`TcpSocket::accept`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AcceptError { + /// The socket is already connected or listening. + InvalidState, + /// Invalid listen port + InvalidPort, + /// The remote host rejected the connection with a RST packet. + ConnectionReset, +} + +/// A TCP socket. +pub struct TcpSocket<'a> { + io: TcpIo<'a>, +} + +/// The reader half of a TCP socket. +pub struct TcpReader<'a> { + io: TcpIo<'a>, +} + +/// The writer half of a TCP socket. +pub struct TcpWriter<'a> { + io: TcpIo<'a>, +} + +impl<'a> TcpReader<'a> { + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [`may_recv()`](TcpSocket::may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub fn wait_read_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_read_ready(cx)) + } + + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. + /// + /// # Note + /// A return value of Ok(0) means that we have read all data and the remote + /// side has closed our receive half of the socket. The remote can no longer + /// send bytes. + /// + /// The send half of the socket is still open. If you want to reconnect using + /// the socket you split this reader off the send half needs to be closed using + /// [`abort()`](TcpSocket::abort). + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// If no data is available, it waits until there is at least one byte available. + pub async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.read_with(f).await + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn recv_capacity(&self) -> usize { + self.io.recv_capacity() + } + + /// Return the amount of octets queued in the receive buffer. This value can be larger than + /// the slice read by the next `recv` or `peek` call because it includes all queued octets, + /// and not only the octets that may be returned as a contiguous slice. + pub fn recv_queue(&self) -> usize { + self.io.recv_queue() + } +} + +impl<'a> TcpWriter<'a> { + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [`may_send()`](TcpSocket::may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub fn wait_write_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_write_ready(cx)) + } + + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. + pub fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { + self.io.write(buf) + } + + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. For a connection + /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. + pub fn flush(&mut self) -> impl Future> + '_ { + self.io.flush() + } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// If the socket is not ready to accept data, it waits until it is. + pub async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.write_with(f).await + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn send_capacity(&self) -> usize { + self.io.send_capacity() + } + + /// Return the amount of octets queued in the transmit buffer. + pub fn send_queue(&self) -> usize { + self.io.send_queue() + } +} + +impl<'a> TcpSocket<'a> { + /// Create a new TCP socket on the given stack, with the given buffers. + pub fn new(stack: Stack<'a>, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let handle = stack.with_mut(|i| { + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(tcp::Socket::new( + tcp::SocketBuffer::new(rx_buffer), + tcp::SocketBuffer::new(tx_buffer), + )) + }); + + Self { + io: TcpIo { stack, handle }, + } + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn recv_capacity(&self) -> usize { + self.io.recv_capacity() + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn send_capacity(&self) -> usize { + self.io.send_capacity() + } + + /// Return the amount of octets queued in the transmit buffer. + pub fn send_queue(&self) -> usize { + self.io.send_queue() + } + + /// Return the amount of octets queued in the receive buffer. This value can be larger than + /// the slice read by the next `recv` or `peek` call because it includes all queued octets, + /// and not only the octets that may be returned as a contiguous slice. + pub fn recv_queue(&self) -> usize { + self.io.recv_queue() + } + + /// Call `f` with the largest contiguous slice of octets in the transmit buffer, + /// and enqueue the amount of elements returned by `f`. + /// + /// If the socket is not ready to accept data, it waits until it is. + pub async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.write_with(f).await + } + + /// Call `f` with the largest contiguous slice of octets in the receive buffer, + /// and dequeue the amount of elements returned by `f`. + /// + /// If no data is available, it waits until there is at least one byte available. + pub async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + self.io.read_with(f).await + } + + /// Split the socket into reader and a writer halves. + pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { + (TcpReader { io: self.io }, TcpWriter { io: self.io }) + } + + /// Connect to a remote host. + pub async fn connect(&mut self, remote_endpoint: T) -> Result<(), ConnectError> + where + T: Into, + { + let local_port = self.io.stack.with_mut(|i| i.get_local_port()); + + match { + self.io + .with_mut(|s, i| s.connect(i.context(), remote_endpoint, local_port)) + } { + Ok(()) => {} + Err(tcp::ConnectError::InvalidState) => return Err(ConnectError::InvalidState), + Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute), + } + + poll_fn(|cx| { + self.io.with_mut(|s, _| match s.state() { + tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)), + tcp::State::Listen => unreachable!(), + tcp::State::SynSent | tcp::State::SynReceived => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + /// Accept a connection from a remote host. + /// + /// This function puts the socket in listening mode, and waits until a connection is received. + pub async fn accept(&mut self, local_endpoint: T) -> Result<(), AcceptError> + where + T: Into, + { + match self.io.with_mut(|s, _| s.listen(local_endpoint)) { + Ok(()) => {} + Err(tcp::ListenError::InvalidState) => return Err(AcceptError::InvalidState), + Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort), + } + + poll_fn(|cx| { + self.io.with_mut(|s, _| match s.state() { + tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + _ => Poll::Ready(Ok(())), + }) + }) + .await + } + + /// Wait until the socket becomes readable. + /// + /// A socket becomes readable when the receive half of the full-duplex connection is open + /// (see [may_recv](#method.may_recv)), and there is some pending data in the receive buffer. + /// + /// This is the equivalent of [read](#method.read), without buffering any data. + pub fn wait_read_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_read_ready(cx)) + } + + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. + /// + /// A return value of Ok(0) means that the socket was closed and is longer + /// able to receive any data. + pub fn read<'s>(&'s mut self, buf: &'s mut [u8]) -> impl Future> + 's { + self.io.read(buf) + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + /// + /// This is the equivalent of [write](#method.write), without sending any data. + pub fn wait_write_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.io.poll_write_ready(cx)) + } + + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. + pub fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { + self.io.write(buf) + } + + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. For a connection + /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. + pub fn flush(&mut self) -> impl Future> + '_ { + self.io.flush() + } + + /// Set the timeout for the socket. + /// + /// If the timeout is set, the socket will be closed if no data is received for the + /// specified duration. + /// + /// # Note: + /// Set a keep alive interval ([`set_keep_alive`] to prevent timeouts when + /// the remote could still respond. + pub fn set_timeout(&mut self, duration: Option) { + self.io + .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) + } + + /// Set the keep-alive interval for the socket. + /// + /// If the keep-alive interval is set, the socket will send keep-alive packets after + /// the specified duration of inactivity. + /// + /// If not set, the socket will not send keep-alive packets. + /// + /// By setting a [`timeout`](Self::timeout) larger then the keep alive you + /// can detect a remote endpoint that no longer answers. + pub fn set_keep_alive(&mut self, interval: Option) { + self.io + .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) + } + + /// Set the hop limit field in the IP header of sent packets. + pub fn set_hop_limit(&mut self, hop_limit: Option) { + self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) + } + + /// Get the local endpoint of the socket. + /// + /// Returns `None` if the socket is not bound (listening) or not connected. + pub fn local_endpoint(&self) -> Option { + self.io.with(|s, _| s.local_endpoint()) + } + + /// Get the remote endpoint of the socket. + /// + /// Returns `None` if the socket is not connected. + pub fn remote_endpoint(&self) -> Option { + self.io.with(|s, _| s.remote_endpoint()) + } + + /// Get the state of the socket. + pub fn state(&self) -> State { + self.io.with(|s, _| s.state()) + } + + /// Close the write half of the socket. + /// + /// This closes only the write half of the socket. The read half side remains open, the + /// socket can still receive data. + /// + /// Data that has been written to the socket and not yet sent (or not yet ACKed) will still + /// still sent. The last segment of the pending to send data is sent with the FIN flag set. + pub fn close(&mut self) { + self.io.with_mut(|s, _| s.close()) + } + + /// Forcibly close the socket. + /// + /// This instantly closes both the read and write halves of the socket. Any pending data + /// that has not been sent will be lost. + /// + /// Note that the TCP RST packet is not sent immediately - if the `TcpSocket` is dropped too soon + /// the remote host may not know the connection has been closed. + /// `abort()` callers should wait for a [`flush()`](TcpSocket::flush) call to complete before + /// dropping or reusing the socket. + pub fn abort(&mut self) { + self.io.with_mut(|s, _| s.abort()) + } + + /// Return whether the transmit half of the full-duplex connection is open. + /// + /// This function returns true if it's possible to send data and have it arrive + /// to the remote endpoint. However, it does not make any guarantees about the state + /// of the transmit buffer, and even if it returns true, [write](#method.write) may + /// not be able to enqueue any octets. + /// + /// In terms of the TCP state machine, the socket must be in the `ESTABLISHED` or + /// `CLOSE-WAIT` state. + pub fn may_send(&self) -> bool { + self.io.with(|s, _| s.may_send()) + } + + /// Check whether the transmit half of the full-duplex connection is open + /// (see [may_send](#method.may_send)), and the transmit buffer is not full. + pub fn can_send(&self) -> bool { + self.io.with(|s, _| s.can_send()) + } + + /// return whether the receive half of the full-duplex connection is open. + /// This function returns true if it’s possible to receive data from the remote endpoint. + /// It will return true while there is data in the receive buffer, and if there isn’t, + /// as long as the remote endpoint has not closed the connection. + pub fn may_recv(&self) -> bool { + self.io.with(|s, _| s.may_recv()) + } + + /// Get whether the socket is ready to receive data, i.e. whether there is some pending data in the receive buffer. + pub fn can_recv(&self) -> bool { + self.io.with(|s, _| s.can_recv()) + } +} + +impl<'a> Drop for TcpSocket<'a> { + fn drop(&mut self) { + self.io.stack.with_mut(|i| i.sockets.remove(self.io.handle)); + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: TcpSocket<'b>) -> TcpSocket<'a> { + x +} +fn _assert_covariant_reader<'a, 'b: 'a>(x: TcpReader<'b>) -> TcpReader<'a> { + x +} +fn _assert_covariant_writer<'a, 'b: 'a>(x: TcpWriter<'b>) -> TcpWriter<'a> { + x +} + +// ======================= + +#[derive(Copy, Clone)] +struct TcpIo<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'d> TcpIo<'d> { + fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) + } + + fn with_mut(&self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + fn read<'s>(&'s mut self, buf: &'s mut [u8]) -> impl Future> + 's { + poll_fn(|cx| { + // CAUTION: smoltcp semantics around EOF are different to what you'd expect + // from posix-like IO, so we have to tweak things here. + self.with_mut(|s, _| match s.recv_slice(buf) { + // Reading into empty buffer + Ok(0) if buf.is_empty() => { + // embedded_io_async::Read's contract is to not block if buf is empty. While + // this function is not a direct implementor of the trait method, we still don't + // want our future to never resolve. + Poll::Ready(Ok(0)) + } + // No data ready + Ok(0) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + // Data ready! + Ok(n) => Poll::Ready(Ok(n)), + // EOF + Err(tcp::RecvError::Finished) => Poll::Ready(Ok(0)), + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::RecvError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + } + + fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + fn write<'s>(&'s mut self, buf: &'s [u8]) -> impl Future> + 's { + poll_fn(|cx| { + self.with_mut(|s, _| match s.send_slice(buf) { + // Not ready to send (no space in the tx buffer) + Ok(0) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + // Some data sent + Ok(n) => Poll::Ready(Ok(n)), + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::SendError::InvalidState) => Poll::Ready(Err(Error::ConnectionReset)), + }) + }) + } + + async fn write_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + if !s.can_send() { + if s.may_send() { + // socket buffer is full wait until it has atleast one byte free + s.register_send_waker(cx.waker()); + Poll::Pending + } else { + // if we can't transmit because the transmit half of the duplex connection is closed then return an error + Poll::Ready(Err(Error::ConnectionReset)) + } + } else { + Poll::Ready(match s.send(unwrap!(f.take())) { + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::SendError::InvalidState) => Err(Error::ConnectionReset), + Ok(r) => Ok(r), + }) + } + }) + }) + .await + } + + async fn read_with(&mut self, f: F) -> Result + where + F: FnOnce(&mut [u8]) -> (usize, R), + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + if !s.can_recv() { + if s.may_recv() { + // socket buffer is empty wait until it has atleast one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } else { + // if we can't receive because the receive half of the duplex connection is closed then return an error + Poll::Ready(Err(Error::ConnectionReset)) + } + } else { + Poll::Ready(match s.recv(unwrap!(f.take())) { + // Connection reset. TODO: this can also be timeouts etc, investigate. + Err(tcp::RecvError::Finished) | Err(tcp::RecvError::InvalidState) => { + Err(Error::ConnectionReset) + } + Ok(r) => Ok(r), + }) + } + }) + }) + .await + } + + fn flush(&mut self) -> impl Future> + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + let data_pending = (s.send_queue() > 0) && s.state() != tcp::State::Closed; + let fin_pending = matches!( + s.state(), + tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck + ); + let rst_pending = s.state() == tcp::State::Closed && s.remote_endpoint().is_some(); + + // If there are outstanding send operations, register for wake up and wait + // smoltcp issues wake-ups when octets are dequeued from the send buffer + if data_pending || fin_pending || rst_pending { + s.register_send_waker(cx.waker()); + Poll::Pending + // No outstanding sends, socket is flushed + } else { + Poll::Ready(Ok(())) + } + }) + }) + } + + fn recv_capacity(&self) -> usize { + self.with(|s, _| s.recv_capacity()) + } + + fn send_capacity(&self) -> usize { + self.with(|s, _| s.send_capacity()) + } + + fn send_queue(&self) -> usize { + self.with(|s, _| s.send_queue()) + } + + fn recv_queue(&self) -> usize { + self.with(|s, _| s.recv_queue()) + } +} + +mod embedded_io_impls { + use super::*; + + impl embedded_io_async::Error for ConnectError { + fn kind(&self) -> embedded_io_async::ErrorKind { + match self { + ConnectError::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset, + ConnectError::TimedOut => embedded_io_async::ErrorKind::TimedOut, + ConnectError::NoRoute => embedded_io_async::ErrorKind::NotConnected, + ConnectError::InvalidState => embedded_io_async::ErrorKind::Other, + } + } + } + + impl embedded_io_async::Error for Error { + fn kind(&self) -> embedded_io_async::ErrorKind { + match self { + Error::ConnectionReset => embedded_io_async::ErrorKind::ConnectionReset, + } + } + } + + impl<'d> embedded_io_async::ErrorType for TcpSocket<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Read for TcpSocket<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io_async::ReadReady for TcpSocket<'d> { + fn read_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_recv() || !s.may_recv())) + } + } + + impl<'d> embedded_io_async::Write for TcpSocket<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } + + impl<'d> embedded_io_async::WriteReady for TcpSocket<'d> { + fn write_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_send())) + } + } + + impl<'d> embedded_io_async::ErrorType for TcpReader<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Read for TcpReader<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io_async::ReadReady for TcpReader<'d> { + fn read_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_recv() || !s.may_recv())) + } + } + + impl<'d> embedded_io_async::ErrorType for TcpWriter<'d> { + type Error = Error; + } + + impl<'d> embedded_io_async::Write for TcpWriter<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } + + impl<'d> embedded_io_async::WriteReady for TcpWriter<'d> { + fn write_ready(&mut self) -> Result { + Ok(self.io.with(|s, _| s.can_send())) + } + } +} + +/// TCP client compatible with `embedded-nal-async` traits. +pub mod client { + use core::cell::{Cell, UnsafeCell}; + use core::mem::MaybeUninit; + use core::net::IpAddr; + use core::ptr::NonNull; + + use super::*; + + /// TCP client connection pool compatible with `embedded-nal-async` traits. + /// + /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. + pub struct TcpClient<'d, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + stack: Stack<'d>, + state: &'d TcpClientState, + socket_timeout: Option, + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, N, TX_SZ, RX_SZ> { + /// Create a new `TcpClient`. + pub fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Self { + Self { + stack, + state, + socket_timeout: None, + } + } + + /// Set the timeout for each socket created by this `TcpClient`. + /// + /// If the timeout is set, the socket will be closed if no data is received for the + /// specified duration. + pub fn set_timeout(&mut self, timeout: Option) { + self.socket_timeout = timeout; + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + for TcpClient<'d, N, TX_SZ, RX_SZ> + { + type Error = Error; + type Connection<'m> + = TcpConnection<'m, N, TX_SZ, RX_SZ> + where + Self: 'm; + + async fn connect<'a>(&'a self, remote: core::net::SocketAddr) -> Result, Self::Error> { + let addr: crate::IpAddress = match remote.ip() { + #[cfg(feature = "proto-ipv4")] + IpAddr::V4(addr) => crate::IpAddress::Ipv4(addr), + #[cfg(not(feature = "proto-ipv4"))] + IpAddr::V4(_) => panic!("ipv4 support not enabled"), + #[cfg(feature = "proto-ipv6")] + IpAddr::V6(addr) => crate::IpAddress::Ipv6(addr), + #[cfg(not(feature = "proto-ipv6"))] + IpAddr::V6(_) => panic!("ipv6 support not enabled"), + }; + let remote_endpoint = (addr, remote.port()); + let mut socket = TcpConnection::new(self.stack, self.state)?; + socket.socket.set_timeout(self.socket_timeout); + socket + .socket + .connect(remote_endpoint) + .await + .map_err(|_| Error::ConnectionReset)?; + Ok(socket) + } + } + + /// Opened TCP connection in a [`TcpClient`]. + pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { + socket: TcpSocket<'d>, + state: &'d TcpClientState, + bufs: NonNull<([u8; TX_SZ], [u8; RX_SZ])>, + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> { + fn new(stack: Stack<'d>, state: &'d TcpClientState) -> Result { + let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; + Ok(Self { + socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, + state, + bufs, + }) + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> Drop for TcpConnection<'d, N, TX_SZ, RX_SZ> { + fn drop(&mut self) { + unsafe { + self.socket.close(); + self.state.pool.free(self.bufs); + } + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::ErrorType + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + type Error = Error; + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Read + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.socket.read(buf).await + } + } + + impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io_async::Write + for TcpConnection<'d, N, TX_SZ, RX_SZ> + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.socket.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.socket.flush().await + } + } + + /// State for TcpClient + pub struct TcpClientState { + pool: Pool<([u8; TX_SZ], [u8; RX_SZ]), N>, + } + + impl TcpClientState { + /// Create a new `TcpClientState`. + pub const fn new() -> Self { + Self { pool: Pool::new() } + } + } + + struct Pool { + used: [Cell; N], + data: [UnsafeCell>; N], + } + + impl Pool { + const VALUE: Cell = Cell::new(false); + const UNINIT: UnsafeCell> = UnsafeCell::new(MaybeUninit::uninit()); + + const fn new() -> Self { + Self { + used: [Self::VALUE; N], + data: [Self::UNINIT; N], + } + } + } + + impl Pool { + fn alloc(&self) -> Option> { + for n in 0..N { + // this can't race because Pool is not Sync. + if !self.used[n].get() { + self.used[n].set(true); + let p = self.data[n].get() as *mut T; + return Some(unsafe { NonNull::new_unchecked(p) }); + } + } + None + } + + /// safety: p must be a pointer obtained from self.alloc that hasn't been freed yet. + unsafe fn free(&self, p: NonNull) { + let origin = self.data.as_ptr() as *mut T; + let n = p.as_ptr().offset_from(origin); + assert!(n >= 0); + assert!((n as usize) < N); + self.used[n as usize].set(false); + } + } +} diff --git a/embassy-net/src/time.rs b/embassy-net/src/time.rs new file mode 100644 index 0000000..b98d40f --- /dev/null +++ b/embassy-net/src/time.rs @@ -0,0 +1,20 @@ +#![allow(unused)] + +use embassy_time::{Duration, Instant}; +use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; + +pub(crate) fn instant_to_smoltcp(instant: Instant) -> SmolInstant { + SmolInstant::from_micros(instant.as_micros() as i64) +} + +pub(crate) fn instant_from_smoltcp(instant: SmolInstant) -> Instant { + Instant::from_micros(instant.total_micros() as u64) +} + +pub(crate) fn duration_to_smoltcp(duration: Duration) -> SmolDuration { + SmolDuration::from_micros(duration.as_micros()) +} + +pub(crate) fn duration_from_smoltcp(duration: SmolDuration) -> Duration { + Duration::from_micros(duration.total_micros()) +} diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs new file mode 100644 index 0000000..63c2f4c --- /dev/null +++ b/embassy-net/src/udp.rs @@ -0,0 +1,396 @@ +//! UDP sockets. + +use core::future::{poll_fn, Future}; +use core::mem; +use core::task::{Context, Poll}; + +use smoltcp::iface::{Interface, SocketHandle}; +use smoltcp::socket::udp; +pub use smoltcp::socket::udp::{PacketMetadata, UdpMetadata}; +use smoltcp::wire::IpListenEndpoint; + +use crate::Stack; + +/// Error returned by [`UdpSocket::bind`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BindError { + /// The socket was already open. + InvalidState, + /// No route to host. + NoRoute, +} + +/// Error returned by [`UdpSocket::send_to`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SendError { + /// No route to host. + NoRoute, + /// Socket not bound to an outgoing port. + SocketNotBound, + /// There is not enough transmit buffer capacity to ever send this packet. + PacketTooLarge, +} + +/// Error returned by [`UdpSocket::recv_from`]. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RecvError { + /// Provided buffer was smaller than the received packet. + Truncated, +} + +/// An UDP socket. +pub struct UdpSocket<'a> { + stack: Stack<'a>, + handle: SocketHandle, +} + +impl<'a> UdpSocket<'a> { + /// Create a new UDP socket using the provided stack and buffers. + pub fn new( + stack: Stack<'a>, + rx_meta: &'a mut [PacketMetadata], + rx_buffer: &'a mut [u8], + tx_meta: &'a mut [PacketMetadata], + tx_buffer: &'a mut [u8], + ) -> Self { + let handle = stack.with_mut(|i| { + let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; + let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; + let tx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(tx_meta) }; + let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; + i.sockets.add(udp::Socket::new( + udp::PacketBuffer::new(rx_meta, rx_buffer), + udp::PacketBuffer::new(tx_meta, tx_buffer), + )) + }); + + Self { stack, handle } + } + + /// Bind the socket to a local endpoint. + pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> + where + T: Into, + { + let mut endpoint = endpoint.into(); + + if endpoint.port == 0 { + // If user didn't specify port allocate a dynamic port. + endpoint.port = self.stack.with_mut(|i| i.get_local_port()); + } + + match self.with_mut(|s, _| s.bind(endpoint)) { + Ok(()) => Ok(()), + Err(udp::BindError::InvalidState) => Err(BindError::InvalidState), + Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute), + } + } + + fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { + self.stack.with(|i| { + let socket = i.sockets.get::(self.handle); + f(socket, &i.iface) + }) + } + + fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { + self.stack.with_mut(|i| { + let socket = i.sockets.get_mut::(self.handle); + let res = f(socket, &mut i.iface); + i.waker.wake(); + res + }) + } + + /// Wait until the socket becomes readable. + /// + /// A socket is readable when a packet has been received, or when there are queued packets in + /// the buffer. + pub fn wait_recv_ready(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_recv_ready(cx)) + } + + /// Wait until a datagram can be read. + /// + /// When no datagram is readable, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready`. + pub fn poll_recv_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_recv() { + Poll::Ready(()) + } else { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + /// + /// Returns the number of bytes received and the remote endpoint. + pub fn recv_from<'s>( + &'s self, + buf: &'s mut [u8], + ) -> impl Future> + 's { + poll_fn(|cx| self.poll_recv_from(buf, cx)) + } + + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready` with the + /// number of bytes received and the remote endpoint. + pub fn poll_recv_from( + &self, + buf: &mut [u8], + cx: &mut Context<'_>, + ) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok((n, meta)) => Poll::Ready(Ok((n, meta))), + // No data ready + Err(udp::RecvError::Truncated) => Poll::Ready(Err(RecvError::Truncated)), + Err(udp::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Receive a datagram with a zero-copy function. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will call the provided function + /// with the number of bytes received and the remote endpoint and return + /// `Poll::Ready` with the function's returned value. + pub async fn recv_from_with(&mut self, f: F) -> R + where + F: FnOnce(&[u8], UdpMetadata) -> R, + { + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.recv() { + Ok((buffer, endpoint)) => Poll::Ready(unwrap!(f.take())(buffer, endpoint)), + Err(udp::RecvError::Truncated) => unreachable!(), + Err(udp::RecvError::Exhausted) => { + // socket buffer is empty wait until at least one byte has arrived + s.register_recv_waker(cx.waker()); + Poll::Pending + } + } + }) + }) + .await + } + + /// Wait until the socket becomes writable. + /// + /// A socket becomes writable when there is space in the buffer, from initial memory or after + /// dispatching datagrams on a full buffer. + pub fn wait_send_ready(&self) -> impl Future + '_ { + poll_fn(|cx| self.poll_send_ready(cx)) + } + + /// Wait until a datagram can be sent. + /// + /// When no datagram can be sent (i.e. the buffer is full), this method will return + /// `Poll::Pending` and register the current task to be notified when + /// space is freed in the buffer after a datagram has been dispatched. + /// + /// When a datagram can be sent, this method will return `Poll::Ready`. + pub fn poll_send_ready(&self, cx: &mut Context<'_>) -> Poll<()> { + self.with_mut(|s, _| { + if s.can_send() { + Poll::Ready(()) + } else { + // socket buffer is full wait until a datagram has been dispatched + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram to the specified remote endpoint. + /// + /// This method will wait until the datagram has been sent. + /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Err(SendError::PacketTooLarge)` + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), SendError> + where + T: Into, + { + let remote_endpoint: UdpMetadata = remote_endpoint.into(); + poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await + } + + /// Send a datagram to the specified remote endpoint. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + /// + /// If the socket's send buffer is too small to fit `buf`, this method will return `Poll::Ready(Err(SendError::PacketTooLarge))` + /// + /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`. + pub fn poll_send_to(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll> + where + T: Into, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the udp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < buf.len()); + if send_capacity_too_small { + return Poll::Ready(Err(SendError::PacketTooLarge)); + } + + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(Ok(())), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + }) + } + + /// Send a datagram to the specified remote endpoint with a zero-copy function. + /// + /// This method will wait until the buffer can fit the requested size before + /// calling the function to fill its contents. + /// + /// If the socket's send buffer is too small to fit `size`, this method will return `Err(SendError::PacketTooLarge)` + /// + /// When the remote endpoint is not reachable, this method will return `Err(SendError::NoRoute)` + pub async fn send_to_with(&mut self, size: usize, remote_endpoint: T, f: F) -> Result + where + T: Into + Copy, + F: FnOnce(&mut [u8]) -> R, + { + // Don't need to wake waker in `with_mut` if the buffer will never fit the udp tx_buffer. + let send_capacity_too_small = self.with(|s, _| s.payload_send_capacity() < size); + if send_capacity_too_small { + return Err(SendError::PacketTooLarge); + } + + let mut f = Some(f); + poll_fn(move |cx| { + self.with_mut(|s, _| { + match s.send(size, remote_endpoint) { + Ok(buffer) => Poll::Ready(Ok(unwrap!(f.take())(buffer))), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => { + // If no sender/outgoing port is specified, there is not really "no route" + if s.endpoint().port == 0 { + Poll::Ready(Err(SendError::SocketNotBound)) + } else { + Poll::Ready(Err(SendError::NoRoute)) + } + } + } + }) + }) + .await + } + + /// Flush the socket. + /// + /// This method will wait until the socket is flushed. + pub fn flush(&mut self) -> impl Future + '_ { + poll_fn(|cx| { + self.with_mut(|s, _| { + if s.send_queue() == 0 { + Poll::Ready(()) + } else { + s.register_send_waker(cx.waker()); + Poll::Pending + } + }) + }) + } + + /// Returns the local endpoint of the socket. + pub fn endpoint(&self) -> IpListenEndpoint { + self.with(|s, _| s.endpoint()) + } + + /// Returns whether the socket is open. + + pub fn is_open(&self) -> bool { + self.with(|s, _| s.is_open()) + } + + /// Close the socket. + pub fn close(&mut self) { + self.with_mut(|s, _| s.close()) + } + + /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. + pub fn may_send(&self) -> bool { + self.with(|s, _| s.can_send()) + } + + /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. + pub fn may_recv(&self) -> bool { + self.with(|s, _| s.can_recv()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_recv_capacity(&self) -> usize { + self.with(|s, _| s.packet_recv_capacity()) + } + + /// Return the maximum number packets the socket can receive. + pub fn packet_send_capacity(&self) -> usize { + self.with(|s, _| s.packet_send_capacity()) + } + + /// Return the maximum number of bytes inside the recv buffer. + pub fn payload_recv_capacity(&self) -> usize { + self.with(|s, _| s.payload_recv_capacity()) + } + + /// Return the maximum number of bytes inside the transmit buffer. + pub fn payload_send_capacity(&self) -> usize { + self.with(|s, _| s.payload_send_capacity()) + } + + /// Set the hop limit field in the IP header of sent packets. + pub fn set_hop_limit(&mut self, hop_limit: Option) { + self.with_mut(|s, _| s.set_hop_limit(hop_limit)) + } +} + +impl Drop for UdpSocket<'_> { + fn drop(&mut self) { + self.stack.with_mut(|i| i.sockets.remove(self.handle)); + } +} + +fn _assert_covariant<'a, 'b: 'a>(x: UdpSocket<'b>) -> UdpSocket<'a> { + x +} diff --git a/embassy-rp/CHANGELOG.md b/embassy-rp/CHANGELOG.md new file mode 100644 index 0000000..8ad3b0b --- /dev/null +++ b/embassy-rp/CHANGELOG.md @@ -0,0 +1,63 @@ +# 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 + +## 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)) + +## 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/Cargo.toml b/embassy-rp/Cargo.toml new file mode 100644 index 0000000..4e5ef28 --- /dev/null +++ b/embassy-rp/Cargo.toml @@ -0,0 +1,176 @@ +[package] +name = "embassy-rp" +version = "0.4.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Embassy Hardware Abstraction Layer (HAL) for the Raspberry Pi RP2040 microcontroller" +keywords = ["embedded", "async", "raspberry-pi", "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_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). This brings in the [`cortex-m-rt`](https://docs.rs/cortex-m-rt) crate, which adds startup code and minimal runtime initialization. +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"] + +## 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: +## ``` +## #[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; +## +## #[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.6.2", path = "../embassy-sync" } +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true } +embassy-time = { version = "0.4.0", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-internal = { version = "0.2.0", path = "../embassy-hal-internal", features = ["cortex-m", "prio-bits-2"] } +embassy-embedded-hal = { version = "0.3.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } +atomic-polyfill = "1.0.1" +defmt = { version = "0.3", 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" } +rand_core = "0.6.4" +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" } + +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.7.0", path = "../embassy-executor", features = ["arch-std", "executor-thread"] } +static_cell = { version = "2" } diff --git a/embassy-rp/LICENSE-APACHE b/embassy-rp/LICENSE-APACHE new file mode 100644 index 0000000..48be126 --- /dev/null +++ b/embassy-rp/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/LICENSE-MIT b/embassy-rp/LICENSE-MIT new file mode 100644 index 0000000..f1cfbd5 --- /dev/null +++ b/embassy-rp/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/README.md b/embassy-rp/README.md new file mode 100644 index 0000000..16b1893 --- /dev/null +++ b/embassy-rp/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 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/build.rs b/embassy-rp/build.rs new file mode 100644 index 0000000..a8d3876 --- /dev/null +++ b/embassy-rp/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/funcsel.txt b/embassy-rp/funcsel.txt new file mode 100644 index 0000000..4dc8939 --- /dev/null +++ b/embassy-rp/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/link-rp.x.in b/embassy-rp/link-rp.x.in new file mode 100644 index 0000000..1839dda --- /dev/null +++ b/embassy-rp/link-rp.x.in @@ -0,0 +1,8 @@ + +SECTIONS { + /* ### Boot loader */ + .boot2 ORIGIN(BOOT2) : + { + KEEP(*(.boot2)); + } > BOOT2 +} diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs new file mode 100644 index 0000000..8defb52 --- /dev/null +++ b/embassy-rp/src/adc.rs @@ -0,0 +1,469 @@ +//! ADC driver. +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::{self, AnyPin, Pull, SealedPin as GpioPin}; +use crate::interrupt::typelevel::Binding; +use crate::interrupt::InterruptExt; +use crate::pac::dma::vals::TreqSel; +use crate::peripherals::{ADC, ADC_TEMP_SENSOR}; +use crate::{dma, interrupt, pac, peripherals, Peripheral, RegExt}; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +/// ADC config. +#[non_exhaustive] +#[derive(Default)] +pub struct Config {} + +enum Source<'p> { + Pin(PeripheralRef<'p, AnyPin>), + TempSensor(PeripheralRef<'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: impl Peripheral

+ 'p, pull: Pull) -> Self { + into_ref!(pin); + 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.map_into())) + } + + /// Create a new ADC channel for the internal temperature sensor. + pub fn new_temp_sensor(s: impl Peripheral

+ 'p) -> Self { + let r = pac::ADC; + r.cs().write_set(|w| w.set_ts_en(true)); + Self(Source::TempSensor(s.into_ref())) + } + + 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: impl Peripheral

+ 'd, + _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(); + r.inte().write(|w| w.set_fifo(true)); + compiler_fence(Ordering::SeqCst); + + poll_fn(move |cx| { + WAKER.register(cx.waker()); + 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: impl Peripheral

, + ) -> 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: impl Peripheral

, + ) -> 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: impl Peripheral

, + ) { + // 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: impl Peripheral

, + ) -> 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: impl Peripheral

, + ) { + // 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: impl Peripheral

+ 'd, _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/src/block.rs b/embassy-rp/src/block.rs new file mode 100644 index 0000000..a3e1ad9 --- /dev/null +++ b/embassy-rp/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 unpartition 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 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/src/bootsel.rs b/embassy-rp/src/bootsel.rs new file mode 100644 index 0000000..d24ce7b --- /dev/null +++ b/embassy-rp/src/bootsel.rs @@ -0,0 +1,83 @@ +//! 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::flash::in_ram; + +impl crate::peripherals::BOOTSEL { + /// Polls the BOOTSEL button. Returns true if the button is pressed. + /// + /// Polling 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_pressed(&mut self) -> 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)] + #[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/src/clocks.rs b/embassy-rp/src/clocks.rs new file mode 100644 index 0000000..2ac2c92 --- /dev/null +++ b/embassy-rp/src/clocks.rs @@ -0,0 +1,1196 @@ +//! Clock configuration for the RP2040 + +#[cfg(feature = "rp2040")] +use core::arch::asm; +use core::marker::PhantomData; +#[cfg(feature = "rp2040")] +use core::sync::atomic::AtomicU16; +use core::sync::atomic::{AtomicU32, Ordering}; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use pac::clocks::vals::*; + +use crate::gpio::{AnyPin, SealedPin}; +#[cfg(feature = "rp2040")] +use crate::pac::common::{Reg, RW}; +use crate::{pac, reset, Peripheral}; + +// 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. +struct Clocks { + xosc: AtomicU32, + sys: AtomicU32, + reference: AtomicU32, + pll_sys: AtomicU32, + pll_usb: AtomicU32, + usb: AtomicU32, + adc: AtomicU32, + // 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), + // 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 _, + // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +/// 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, + // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, + // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, +} + +impl ClockConfig { + /// Clock configuration derived from external crystal. + 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, + }), + // 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, + }), + // gpin0: None, + // gpin1: None, + } + } + + // pub fn bind_gpin(&mut self, gpin: Gpin<'static, P>, hz: u32) { + // match P::NR { + // 0 => self.gpin0 = Some((hz, gpin.map_into())), + // 1 => self.gpin1 = Some((hz, gpin.map_into())), + // _ => unreachable!(), + // } + // // pin is now provisionally bound. if the config is applied it must be forgotten, + // // or Gpin::drop will deconfigure the clock input. + // } +} + +/// ROSC freq range. +#[repr(u16)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +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. +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, +} + +/// 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)] +pub enum RefClkSrc { + /// XOSC. + Xosc, + /// ROSC. + Rosc, + /// PLL USB. + PllUsb, + // Gpin0, + // Gpin1, +} + +/// SYS clock source. +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SysClkSrc { + /// REF. + Ref, + /// PLL SYS. + PllSys, + /// PLL USB. + PllUsb, + /// ROSC. + Rosc, + /// XOSC. + Xosc, + // 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)] +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 _, + // 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)] +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 _, + // 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(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 _, + // 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, +} + +/// 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); + + // 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); + + let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { + Some(config) => { + // start XOSC + // datasheet mentions support for clock inputs into XIN, but doesn't go into + // how this is achieved. pico-sdk doesn't support this at all. + start_xosc(config.hz, config.delay_multiplier); + + let pll_sys_freq = match config.sys_pll { + Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), + None => 0, + }; + let pll_usb_freq = match config.usb_pll { + Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), + 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), + // 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), + // 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, + // 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, + // 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, + // 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, + // 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) +} + +// 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) +} + +/// 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) +} + +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() {} +} + +#[inline(always)] +fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { + let ref_freq = input_freq / config.refdiv as u32; + assert!(config.fbdiv >= 16 && config.fbdiv <= 320); + assert!(config.post_div1 >= 1 && config.post_div1 <= 7); + assert!(config.post_div2 >= 1 && config.post_div2 <= 7); + assert!(config.refdiv >= 1 && config.refdiv <= 63); + assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); + let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); + assert!(vco_freq >= 750_000_000 && vco_freq <= 1_800_000_000); + + // Load VCO-related dividers before starting VCO + p.cs().write(|w| w.set_refdiv(config.refdiv as _)); + p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); + + // Turn on PLL + let pwr = p.pwr().write(|w| { + w.set_dsmpd(true); // "nothing is achieved by setting this low" + w.set_pd(false); + w.set_vcopd(false); + w.set_postdivpd(true); + *w + }); + + // Wait for PLL to lock + while !p.cs().read().lock() {} + + // Set post-dividers + p.prim().write(|w| { + w.set_postdiv1(config.post_div1); + w.set_postdiv2(config.post_div2); + }); + + // Turn on post divider + p.pwr().write(|w| { + *w = pwr; + w.set_postdivpd(false); + }); + + 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: PeripheralRef<'d, AnyPin>, + _phantom: PhantomData, +} + +impl<'d, T: GpinPin> Gpin<'d, T> { + /// Create new gpin driver. + pub fn new(gpin: impl Peripheral

+ 'd) -> Self { + into_ref!(gpin); + + gpin.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[cfg(feature = "_rp235x")] + gpin.pad_ctrl().write(|w| { + w.set_iso(false); + }); + + Gpin { + gpin: gpin.map_into(), + _phantom: PhantomData, + } + } + + // fn map_into(self) -> Gpin<'d, AnyPin> { + // unsafe { core::mem::transmute(self) } + // } +} + +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 _, + // 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: PeripheralRef<'d, T>, +} + +impl<'d, T: GpoutPin> Gpout<'d, T> { + /// Create new general purpose clock output. + pub fn new(gpout: impl Peripheral

+ 'd) -> Self { + into_ref!(gpout); + + gpout.gpio().ctrl().write(|w| w.set_funcsel(0x08)); + #[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(), + // 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_RTC => clk_rtc_freq() as _, + 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 { + 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 + } +} + +impl rand_core::RngCore for RoscRng { + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + Ok(self.fill_bytes(dest)) + } + + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill_with(Self::next_u8) + } +} +/// 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", feature = "rp2040"))] +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)); + } + } +} diff --git a/embassy-rp/src/critical_section_impl.rs b/embassy-rp/src/critical_section_impl.rs new file mode 100644 index 0000000..d233e6f --- /dev/null +++ b/embassy-rp/src/critical_section_impl.rs @@ -0,0 +1,137 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +use crate::pac; + +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 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(crate) type Spinlock31 = Spinlock<31>; +pub trait SpinlockValid {} +impl SpinlockValid for Spinlock<31> {} diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs new file mode 100644 index 0000000..2edcfdf --- /dev/null +++ b/embassy-rp/src/dma.rs @@ -0,0 +1,315 @@ +//! Direct Memory Access (DMA) +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; +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: impl Peripheral

+ 'a, + 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: impl Peripheral

+ 'a, + 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: impl Peripheral

+ 'a, + 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: impl Peripheral

+ 'a, + 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: impl Peripheral

+ 'a, + from: *const u32, + to: *mut u32, + len: usize, + data_size: DataSize, + incr_read: bool, + incr_write: bool, + dreq: vals::TreqSel, +) -> Transfer<'a, C> { + into_ref!(ch); + + 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: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub(crate) fn new(channel: impl Peripheral

+ 'a) -> Self { + into_ref!(channel); + + 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: Peripheral

+ 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 _) + } + + /// Convert into type-erased [AnyChannel]. + fn degrade(self) -> AnyChannel { + AnyChannel { number: self.number() } + } +} + +/// 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 { + crate::dma::Channel::degrade(val) + } + } + }; +} + +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/src/flash.rs b/embassy-rp/src/flash.rs new file mode 100644 index 0000000..fbc8b35 --- /dev/null +++ b/embassy-rp/src/flash.rs @@ -0,0 +1,989 @@ +//! Flash driver. +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embedded_storage::nor_flash::{ + check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, + ReadNorFlash, +}; + +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 BOOTROM_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: impl Peripheral

+ 'd) -> 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: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd) -> Self { + into_ref!(dma); + Self { + dma: Some(dma.map_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(), + 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(BOOTROM_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(BOOTROM_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(BOOTROM_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)] + #[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)] + #[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)] + #[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 {} +/// 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/src/float/add_sub.rs b/embassy-rp/src/float/add_sub.rs new file mode 100644 index 0000000..673544c --- /dev/null +++ b/embassy-rp/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/src/float/cmp.rs b/embassy-rp/src/float/cmp.rs new file mode 100644 index 0000000..e540e39 --- /dev/null +++ b/embassy-rp/src/float/cmp.rs @@ -0,0 +1,201 @@ +// 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/src/float/conv.rs b/embassy-rp/src/float/conv.rs new file mode 100644 index 0000000..021826e --- /dev/null +++ b/embassy-rp/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/src/float/div.rs b/embassy-rp/src/float/div.rs new file mode 100644 index 0000000..87d1e38 --- /dev/null +++ b/embassy-rp/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/src/float/functions.rs b/embassy-rp/src/float/functions.rs new file mode 100644 index 0000000..de29ce3 --- /dev/null +++ b/embassy-rp/src/float/functions.rs @@ -0,0 +1,239 @@ +// 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/src/float/mod.rs b/embassy-rp/src/float/mod.rs new file mode 100644 index 0000000..3ad6f1c --- /dev/null +++ b/embassy-rp/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/src/float/mul.rs b/embassy-rp/src/float/mul.rs new file mode 100644 index 0000000..ceb0210 --- /dev/null +++ b/embassy-rp/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/src/fmt.rs b/embassy-rp/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-rp/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/src/gpio.rs b/embassy-rp/src/gpio.rs new file mode 100644 index 0000000..111e033 --- /dev/null +++ b/embassy-rp/src/gpio.rs @@ -0,0 +1,1389 @@ +//! 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::{impl_peripheral, into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::InterruptExt; +use crate::pac::common::{Reg, RW}; +use crate::pac::SIO; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +#[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)] +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)] +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)] +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)] +pub enum SlewRate { + /// Fast slew rate. + Fast, + /// Slow slew rate. + Slow, +} + +/// A GPIO bank with up to 32 pins. +#[derive(Debug, Eq, PartialEq)] +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. +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: impl Peripheral

+ 'd, 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() + } + + /// 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: PeripheralRef<'d, AnyPin>, +} + +impl<'d> InputFuture<'d> { + fn new(pin: PeripheralRef<'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 EGDE_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. +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: impl Peripheral

+ 'd, 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) + } + + /// 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. +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: impl Peripheral

+ 'd, 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. +pub struct Flex<'d> { + pin: PeripheralRef<'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: impl Peripheral

+ 'd) -> Self { + into_ref!(pin); + + 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.map_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()) + } + + /// 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 _); + }); + 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. +pub struct DormantWake<'w> { + pin: PeripheralRef<'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: Peripheral

+ Into + SealedPin + Sized + 'static { + /// Degrade to a generic pin struct + fn degrade(self) -> AnyPin { + AnyPin { + pin_bank: self.pin_bank(), + } + } + + /// 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 +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) -> Self { + 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 { + crate::gpio::Pin::degrade(val) + } + } + }; +} + +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/src/i2c.rs b/embassy-rp/src/i2c.rs new file mode 100644 index 0000000..3a2ee66 --- /dev/null +++ b/embassy-rp/src/i2c.rs @@ -0,0 +1,919 @@ +#![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::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::i2c; + +use crate::gpio::AnyPin; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, pac, peripherals, Peripheral}; + +/// 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, +} + +impl Default for Config { + fn default() -> Self { + Self { frequency: 100_000 } + } +} + +/// Size of I2C FIFO. +pub const FIFO_SIZE: u8 = 16; + +/// I2C driver. +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: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(scl, sda); + Self::new_inner(peri, scl.map_into(), sda.map_into(), config) + } +} + +impl<'d, T: Instance> I2c<'d, T, Async> { + /// Create a new driver instance in async mode. + pub fn new_async( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + _irq: impl Binding>, + config: Config, + ) -> Self { + into_ref!(scl, sda); + + let i2c = Self::new_inner(peri, scl.map_into(), sda.map_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 the waker is set (to eg enable the required interrupts). + 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| { + let r = f(self); + + if r.is_pending() { + T::waker().register(cx.waker()); + 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) +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(true); + w.set_pde(false); + }); +} + +impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + _irq: impl Binding>, + config: Config, + ) -> Self { + into_ref!(_peri, scl, sda); + + assert!(config.addr != 0); + + // Configure SCL & SDA pins + set_up_i2c_pin(&scl); + set_up_i2c_pin(&sda); + + 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 the waker is set (to eg enable the required interrupts). + #[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| { + let r = f(self); + + if r.is_pending() { + T::waker().register(cx.waker()); + 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 recieving 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/src/intrinsics.rs b/embassy-rp/src/intrinsics.rs new file mode 100644 index 0000000..69d5d92 --- /dev/null +++ b/embassy-rp/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 { + #[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 { + #[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")] + 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/src/lib.rs b/embassy-rp/src/lib.rs new file mode 100644 index 0000000..de60af8 --- /dev/null +++ b/embassy-rp/src/lib.rs @@ -0,0 +1,759 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![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; +pub mod pwm; +mod reset; +pub mod rom_data; +#[cfg(feature = "rp2040")] +pub mod rtc; +pub mod spi; +#[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::{into_ref, Peripheral, PeripheralRef}; +#[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!(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 { + ($vis:vis struct $name:ident { + $( + $(#[cfg($cond_irq:meta)])? + $irq:ident => $( + $(#[cfg($cond_handler:meta)])? + $handler:ty + ),*; + )* + }) => { + #[derive(Copy, Clone)] + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + $(#[cfg($cond_irq)])? + unsafe extern "C" fn $irq() { + $( + $(#[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, + + 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)] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$loader; + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[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)] + #[link_section = ".start_block"] + #[used] + static IMAGE_DEF: crate::block::ImageDef = crate::block::ImageDef::$imagedef(); + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[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<(), ()> { + 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<(), ()> { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Fail if MPU is already configured + if core.MPU.ctrl.read() != 0 { + return Err(()); + } + + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write(stack_bottom as u32 & !0xff); // set address + core.MPU.rlar.write(1); // enable region + } + 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 +} + +#[cfg(feature = "rt")] +#[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/src/multicore.rs b/embassy-rp/src/multicore.rs new file mode 100644 index 0000000..1450505 --- /dev/null +++ b/embassy-rp/src/multicore.rs @@ -0,0 +1,348 @@ +//! 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; +//! +//! 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 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::{compiler_fence, AtomicBool, Ordering}; + +use crate::interrupt::InterruptExt; +use crate::peripherals::CORE1; +use crate::{gpio, install_stack_guard, interrupt, pac}; + +const PAUSE_TOKEN: u32 = 0xDEADBEEF; +const RESUME_TOKEN: u32 = !0xDEADBEEF; +static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); + +#[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] +#[link_section = ".data.ram_func"] +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] +#[link_section = ".data.ram_func"] +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: 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/src/otp.rs b/embassy-rp/src/otp.rs new file mode 100644 index 0000000..6091f71 --- /dev/null +++ b/embassy-rp/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/src/pio/instr.rs b/embassy-rp/src/pio/instr.rs new file mode 100644 index 0000000..b15d507 --- /dev/null +++ b/embassy-rp/src/pio/instr.rs @@ -0,0 +1,103 @@ +//! 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. + 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. + 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. + 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. + 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/src/pio/mod.rs b/embassy-rp/src/pio/mod.rs new file mode 100644 index 0000000..fd09d4b --- /dev/null +++ b/embassy-rp/src/pio/mod.rs @@ -0,0 +1,1479 @@ +//! PIO driver. +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use atomic_polyfill::{AtomicU64, AtomicU8}; +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::types::extra::U8; +use fixed::FixedU32; +use pio::{Program, SideSet, Wrap}; + +use crate::dma::{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::{pac, peripherals, RegExt}; + +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 accesible 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, +} + +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: PeripheralRef<'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); + }); + } + + /// 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) + } + + /// Prepare DMA transfer from RX FIFO. + pub fn dma_pull<'a, C: Channel, W: Word>( + &'a mut self, + ch: PeripheralRef<'a, C>, + data: &'a mut [W], + bswap: bool, + ) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + 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| { + // Set RX DREQ for this statemachine + w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8 + 4)); + 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) + } +} + +/// 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 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) + } + + /// Prepare a DMA transfer to TX FIFO. + pub fn dma_push<'a, C: Channel, W: Word>( + &'a mut self, + ch: PeripheralRef<'a, C>, + data: &'a [W], + bswap: bool, + ) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + 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| { + // Set TX DREQ for this statemachine + w.set_treq_sel(crate::pac::dma::vals::TreqSel::from(pio_no * 8 + SM as u8)); + 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) + } +} + +/// 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, + }); + #[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); + } + + /// 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()); + 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()); + 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: impl Peripheral

+ 'd) -> Pin<'d, PIO> { + into_ref!(pin); + + // 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 + PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); + Pin { + pin: pin.into_ref().map_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: impl Peripheral

+ 'd, _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, + } + } +} + +/// 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(); + if state.users.fetch_sub(1, Ordering::AcqRel) == 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 + 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/src/pio_programs/hd44780.rs b/embassy-rp/src/pio_programs/hd44780.rs new file mode 100644 index 0000000..6997b91 --- /dev/null +++ b/embassy-rp/src/pio_programs/hd44780.rs @@ -0,0 +1,203 @@ +//! [HD44780 display driver](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, Irq, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, + StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +/// 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: PeripheralRef<'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>, + dma: impl Peripheral

+ 'l, + rs: impl PioPin, + rw: impl PioPin, + e: impl PioPin, + db4: impl PioPin, + db5: impl PioPin, + db6: impl PioPin, + db7: impl PioPin, + word_prg: &PioHD44780CommandWordProgram<'l, P>, + seq_prg: &PioHD44780CommandSequenceProgram<'l, P>, + ) -> PioHD44780<'l, P, S> { + into_ref!(dma); + + 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]); + cfg.clock_divider = 125u8.into(); + 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]); + cfg.clock_divider = 8u8.into(); // ~64ns/insn + 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.map_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/src/pio_programs/i2s.rs b/embassy-rp/src/pio_programs/i2s.rs new file mode 100644 index 0000000..17e3214 --- /dev/null +++ b/embassy-rp/src/pio_programs/i2s.rs @@ -0,0 +1,95 @@ +//! Pio backed I2s output + +use fixed::traits::ToFixed; + +use crate::dma::{AnyChannel, Channel, Transfer}; +use crate::pio::{ + Common, Config, Direction, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +/// This struct represents an i2s output driver program +pub struct PioI2sOutProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioI2sOutProgram<'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 2", + " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins 1 side 0b10", + " set x, 14 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<'a, P: Instance, const S: usize> { + dma: PeripheralRef<'a, AnyChannel>, + sm: StateMachine<'a, P, S>, +} + +impl<'a, P: Instance, const S: usize> PioI2sOut<'a, P, S> { + /// Configure a state machine to output I2s + pub fn new( + common: &mut Common<'a, P>, + mut sm: StateMachine<'a, P, S>, + dma: impl Peripheral

+ 'a, + data_pin: impl PioPin, + bit_clock_pin: impl PioPin, + lr_clock_pin: impl PioPin, + sample_rate: u32, + bit_depth: u32, + channels: u32, + program: &PioI2sOutProgram<'a, P>, + ) -> Self { + into_ref!(dma); + + 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 * channels; + 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]); + + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + /// Return an in-prograss dma transfer future. Awaiting it will guarentee 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/src/pio_programs/mod.rs b/embassy-rp/src/pio_programs/mod.rs new file mode 100644 index 0000000..7453782 --- /dev/null +++ b/embassy-rp/src/pio_programs/mod.rs @@ -0,0 +1,10 @@ +//! Pre-built pio programs for common interfaces + +pub mod hd44780; +pub mod i2s; +pub mod onewire; +pub mod pwm; +pub mod rotary_encoder; +pub mod stepper; +pub mod uart; +pub mod ws2812; diff --git a/embassy-rp/src/pio_programs/onewire.rs b/embassy-rp/src/pio_programs/onewire.rs new file mode 100644 index 0000000..040333e --- /dev/null +++ b/embassy-rp/src/pio_programs/onewire.rs @@ -0,0 +1,109 @@ +//! OneWire pio driver + +use crate::pio::{Common, Config, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine}; + +/// This struct represents an onewire driver program +pub struct PioOneWireProgram<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +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#" + .wrap_target + again: + pull block + mov x, osr + jmp !x, read + write: + set pindirs, 1 + set pins, 0 + loop1: + jmp x--,loop1 + set pindirs, 0 [31] + wait 1 pin 0 [31] + pull block + mov x, osr + bytes1: + pull block + set y, 7 + set pindirs, 1 + bit1: + set pins, 0 [1] + out pins,1 [31] + set pins, 1 [20] + jmp y--,bit1 + jmp x--,bytes1 + set pindirs, 0 [31] + jmp again + read: + pull block + mov x, osr + bytes2: + set y, 7 + bit2: + set pindirs, 1 + set pins, 0 [1] + set pindirs, 0 [5] + in pins,1 [10] + jmp y--,bit2 + jmp x--,bytes2 + .wrap + "#, + ); + let prg = common.load_program(&prg.program); + + Self { prg } + } +} + +/// Pio backed OneWire driver +pub struct PioOneWire<'d, PIO: Instance, const SM: usize> { + sm: StateMachine<'d, PIO, SM>, +} + +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: impl PioPin, + program: &PioOneWireProgram<'d, PIO>, + ) -> Self { + let pin = common.make_pio_pin(pin); + let mut cfg = Config::default(); + cfg.use_program(&program.prg, &[]); + cfg.set_out_pins(&[&pin]); + cfg.set_in_pins(&[&pin]); + cfg.set_set_pins(&[&pin]); + cfg.shift_in = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Right, + threshold: 8, + }; + cfg.clock_divider = 255_u8.into(); + sm.set_config(&cfg); + sm.set_enable(true); + Self { sm } + } + + /// Write bytes over the wire + pub async fn write_bytes(&mut self, bytes: &[u8]) { + self.sm.tx().wait_push(250).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes { + self.sm.tx().wait_push(*b as u32).await; + } + } + + /// Read bytes from the wire + pub async fn read_bytes(&mut self, bytes: &mut [u8]) { + self.sm.tx().wait_push(0).await; + self.sm.tx().wait_push(bytes.len() as u32 - 1).await; + for b in bytes.iter_mut() { + *b = (self.sm.rx().wait_pull().await >> 24) as u8; + } + } +} diff --git a/embassy-rp/src/pio_programs/pwm.rs b/embassy-rp/src/pio_programs/pwm.rs new file mode 100644 index 0000000..01ffe01 --- /dev/null +++ b/embassy-rp/src/pio_programs/pwm.rs @@ -0,0 +1,121 @@ +//! PIO backed PWM driver + +use core::time::Duration; + +use pio::InstructionOperands; + +use crate::clocks; +use crate::gpio::Level; +use crate::pio::{Common, Config, Direction, Instance, LoadedProgram, Pin, PioPin, StateMachine}; + +/// 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: 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 } + } + + /// Enable's 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/src/pio_programs/rotary_encoder.rs b/embassy-rp/src/pio_programs/rotary_encoder.rs new file mode 100644 index 0000000..f2fb02a --- /dev/null +++ b/embassy-rp/src/pio_programs/rotary_encoder.rs @@ -0,0 +1,75 @@ +//! PIO backed quadrature encoder + +use fixed::traits::ToFixed; + +use crate::gpio::Pull; +use crate::pio::{ + Common, Config, Direction as PioDirection, FifoJoin, Instance, LoadedProgram, PioPin, ShiftDirection, StateMachine, +}; + +/// 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: impl PioPin, + pin_b: 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; + cfg.clock_divider = 10_000.to_fixed(); + 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/src/pio_programs/stepper.rs b/embassy-rp/src/pio_programs/stepper.rs new file mode 100644 index 0000000..c8f7416 --- /dev/null +++ b/embassy-rp/src/pio_programs/stepper.rs @@ -0,0 +1,147 @@ +//! Pio Stepper Driver for 5-wire steppers + +use core::mem::{self, MaybeUninit}; + +use fixed::traits::ToFixed; +use fixed::types::extra::U8; +use fixed::FixedU32; + +use crate::pio::{Common, Config, Direction, Instance, Irq, LoadedProgram, PioPin, StateMachine}; + +/// 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: impl PioPin, + pin1: impl PioPin, + pin2: impl PioPin, + pin3: 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 = (125_000_000 / (100 * 136)).to_fixed(); + 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: FixedU32 = (125_000_000 / (freq * 136)).to_fixed(); + assert!(clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(clock_divider >= 1, "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/src/pio_programs/uart.rs b/embassy-rp/src/pio_programs/uart.rs new file mode 100644 index 0000000..641daca --- /dev/null +++ b/embassy-rp/src/pio_programs/uart.rs @@ -0,0 +1,185 @@ +//! Pio backed uart drivers + +use core::convert::Infallible; + +use embedded_io_async::{ErrorType, Read, Write}; +use fixed::traits::ToFixed; + +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<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioUartTxProgram<'a, PIO> { + /// Load the uart tx program into the given pio + pub fn new(common: &mut Common<'a, 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<'a, PIO: Instance, const SM: usize> { + sm_tx: StateMachine<'a, PIO, SM>, +} + +impl<'a, PIO: Instance, const SM: usize> PioUartTx<'a, PIO, SM> { + /// Configure a pio state machine to use the loaded tx program. + pub fn new( + baud: u32, + common: &mut Common<'a, PIO>, + mut sm_tx: StateMachine<'a, PIO, SM>, + tx_pin: impl PioPin, + program: &PioUartTxProgram<'a, 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<'a, PIO: Instance> { + prg: LoadedProgram<'a, PIO>, +} + +impl<'a, PIO: Instance> PioUartRxProgram<'a, PIO> { + /// Load the uart rx program into the given pio + pub fn new(common: &mut Common<'a, 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 reciever +pub struct PioUartRx<'a, PIO: Instance, const SM: usize> { + sm_rx: StateMachine<'a, PIO, SM>, +} + +impl<'a, PIO: Instance, const SM: usize> PioUartRx<'a, PIO, SM> { + /// Configure a pio state machine to use the loaded rx program. + pub fn new( + baud: u32, + common: &mut Common<'a, PIO>, + mut sm_rx: StateMachine<'a, PIO, SM>, + rx_pin: impl PioPin, + program: &PioUartRxProgram<'a, 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/src/pio_programs/ws2812.rs b/embassy-rp/src/pio_programs/ws2812.rs new file mode 100644 index 0000000..2462a64 --- /dev/null +++ b/embassy-rp/src/pio_programs/ws2812.rs @@ -0,0 +1,118 @@ +//! [ws2812](https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +use embassy_time::Timer; +use fixed::types::U24F8; +use smart_leds::RGB8; + +use crate::clocks::clk_sys_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::pio::{ + Common, Config, FifoJoin, Instance, LoadedProgram, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use crate::{into_ref, Peripheral, PeripheralRef}; + +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; + +/// 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 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> { + dma: PeripheralRef<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> PioWs2812<'d, P, S, N> { + /// Configure a pio state machine to use the loaded ws2812 program. + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: impl Peripheral

+ 'd, + pin: impl PioPin, + program: &PioWs2812Program<'d, P>, + ) -> Self { + into_ref!(dma); + + // 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.map_into(), + sm, + } + } + + /// 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 { + let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words, false).await; + + Timer::after_micros(55).await; + } +} diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs new file mode 100644 index 0000000..4fb8ade --- /dev/null +++ b/embassy-rp/src/pwm.rs @@ -0,0 +1,611 @@ +//! Pulse Width Modulation (PWM) + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +pub use embedded_hal_1::pwm::SetDutyCycle; +use embedded_hal_1::pwm::{Error, ErrorKind, ErrorType}; +use fixed::traits::ToFixed; +use fixed::FixedU16; +use pac::pwm::regs::{ChDiv, Intr}; +use pac::pwm::vals::Divmode; + +use crate::gpio::{AnyPin, Pin as GpioPin, Pull, SealedPin as _}; +use crate::{pac, peripherals, RegExt}; + +/// 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); + 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: impl Peripheral

+ 'd, config: Config) -> Self { + into_ref!(slice); + 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: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(slice, a); + Self::new_inner( + slice.number(), + Some(a.map_into()), + None, + Pull::None, + config, + Divmode::DIV, + ) + } + + /// Create PWM driver with a single 'b' pin as output. + #[inline] + pub fn new_output_b( + slice: impl Peripheral

+ 'd, + b: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(slice, b); + Self::new_inner( + slice.number(), + None, + Some(b.map_into()), + Pull::None, + config, + Divmode::DIV, + ) + } + + /// Create PWM driver with a 'a' and 'b' pins as output. + #[inline] + pub fn new_output_ab( + slice: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(slice, a, b); + Self::new_inner( + slice.number(), + Some(a.map_into()), + Some(b.map_into()), + Pull::None, + config, + Divmode::DIV, + ) + } + + /// Create PWM driver with a single 'b' as input pin. + #[inline] + pub fn new_input( + slice: impl Peripheral

+ 'd, + b: impl Peripheral

> + 'd, + b_pull: Pull, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(slice, b); + Self::new_inner(slice.number(), None, Some(b.map_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: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + b_pull: Pull, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(slice, a, b); + Self::new_inner( + slice.number(), + Some(a.map_into()), + Some(b.map_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(PeripheralRef<'d, AnyPin>), + B(PeripheralRef<'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)); + } + if let Some(pin) = &self.pin_b { + pin.gpio().ctrl().write(|w| w.set_funcsel(31)); + } + } +} + +trait SealedSlice {} + +/// PWM Slice. +#[allow(private_bounds)] +pub trait Slice: Peripheral

+ 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/src/relocate.rs b/embassy-rp/src/relocate.rs new file mode 100644 index 0000000..3448781 --- /dev/null +++ b/embassy-rp/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 { + 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/src/reset.rs b/embassy-rp/src/reset.rs new file mode 100644 index 0000000..4b9e424 --- /dev/null +++ b/embassy-rp/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/src/rom_data/mod.rs b/embassy-rp/src/rom_data/mod.rs new file mode 100644 index 0000000..e5fcf8e --- /dev/null +++ b/embassy-rp/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/src/rom_data/rp2040.rs b/embassy-rp/src/rom_data/rp2040.rs new file mode 100644 index 0000000..5a74edd --- /dev/null +++ b/embassy-rp/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 _; + +/// Retrive 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/src/rom_data/rp235x.rs b/embassy-rp/src/rom_data/rp235x.rs new file mode 100644 index 0000000..b16fee8 --- /dev/null +++ b/embassy-rp/src/rom_data/rp235x.rs @@ -0,0 +1,752 @@ +//! 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 +} diff --git a/embassy-rp/src/rtc/datetime_chrono.rs b/embassy-rp/src/rtc/datetime_chrono.rs new file mode 100644 index 0000000..2818e46 --- /dev/null +++ b/embassy-rp/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/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs new file mode 100644 index 0000000..5de00e6 --- /dev/null +++ b/embassy-rp/src/rtc/datetime_no_deps.rs @@ -0,0 +1,128 @@ +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)] +#[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/src/rtc/filter.rs b/embassy-rp/src/rtc/filter.rs new file mode 100644 index 0000000..d4a3bab --- /dev/null +++ b/embassy-rp/src/rtc/filter.rs @@ -0,0 +1,100 @@ +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(Default)] +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/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs new file mode 100644 index 0000000..2ce7ac6 --- /dev/null +++ b/embassy-rp/src/rtc/mod.rs @@ -0,0 +1,204 @@ +//! RTC driver. +mod filter; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; + +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; + +/// A reference to the real time clock of the system +pub struct Rtc<'d, T: Instance> { + inner: PeripheralRef<'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: impl Peripheral

+ 'd) -> Self { + into_ref!(inner); + + // Set the RTC divider + inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); + + Self { inner } + } + + /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable 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(); + } +} + +/// 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 {} + +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/src/spi.rs b/embassy-rp/src/spi.rs new file mode 100644 index 0000000..c48b5c5 --- /dev/null +++ b/embassy-rp/src/spi.rs @@ -0,0 +1,728 @@ +//! Serial Peripheral Interface +use core::marker::PhantomData; + +use embassy_embedded_hal::SetConfig; +use embassy_futures::join::join; +use embassy_hal_internal::{into_ref, PeripheralRef}; +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, Peripheral}; + +/// 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: PeripheralRef<'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: impl Peripheral

+ 'd, + clk: Option>, + mosi: Option>, + miso: Option>, + cs: Option>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + into_ref!(inner); + + 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 reenabled after the modifications + /// are applied. + fn apply_config(inner: &PeripheralRef<'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: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + mosi: impl Peripheral

+ 'd> + 'd, + miso: impl Peripheral

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, mosi, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + Some(miso.map_into()), + None, + None, + None, + config, + ) + } + + /// Create an SPI driver in blocking mode supporting writes only. + pub fn new_blocking_txonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + mosi: impl Peripheral

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, mosi); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + None, + None, + None, + None, + config, + ) + } + + /// Create an SPI driver in blocking mode supporting reads only. + pub fn new_blocking_rxonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + miso: impl Peripheral

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + None, + Some(miso.map_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: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + mosi: impl Peripheral

+ 'd> + 'd, + miso: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx_dma, rx_dma, clk, mosi, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + Some(miso.map_into()), + None, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), + config, + ) + } + + /// Create an SPI driver in async mode supporting DMA write operations only. + pub fn new_txonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + mosi: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx_dma, clk, mosi); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + None, + None, + Some(tx_dma.map_into()), + None, + config, + ) + } + + /// Create an SPI driver in async mode supporting DMA read operations only. + pub fn new_rxonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + miso: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx_dma, rx_dma, clk, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + None, + Some(miso.map_into()), + None, + Some(tx_dma.map_into()), + Some(rx_dma.map_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(); + 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(); + 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(); + 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(); + 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(); + // 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(&mut tx_ch, 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 {} + +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/src/time_driver.rs b/embassy-rp/src/time_driver.rs new file mode 100644 index 0000000..d598287 --- /dev/null +++ b/embassy-rp/src/time_driver.rs @@ -0,0 +1,144 @@ +//! Timer driver. +use core::cell::{Cell, RefCell}; + +use critical_section::CriticalSection; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex; +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/src/trng.rs b/embassy-rp/src/trng.rs new file mode 100644 index 0000000..9f2f33c --- /dev/null +++ b/embassy-rp/src/trng.rs @@ -0,0 +1,405 @@ +//! 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::Peripheral; +use embassy_sync::waitqueue::AtomicWaker; +use rand_core::Error; + +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 { + /// 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. +/// +/// --- +/// +/// 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 { + disable_autocorrelation_test: true, + disable_crngt_test: true, + disable_von_neumann_balancer: true, + 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>, +} + +/// 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: impl Peripheral

+ 'd, + _irq: impl Binding> + 'd, + config: Config, + ) -> 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(config.inverter_chain_length.clone().into()); + }); + + let sample_count_register = regs.sample_cnt1(); + sample_count_register.write(|w| { + *w = config.sample_count; + }); + + let debug_control_register = regs.trng_debug_control(); + debug_control_register.write(|w| { + w.set_auto_correlate_bypass(config.disable_autocorrelation_test); + w.set_trng_crngt_bypass(config.disable_crngt_test); + w.set_vnc_bypass(config.disable_von_neumann_balancer) + }); + + Trng { phantom: PhantomData } + } + + 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 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)); + } 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 trng_valid_register.read().ehr_valid().not() { + panic!("RNG not busy, but ehr is not valid!") + } + 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::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<(), Error> { + self.blocking_fill_bytes(dest); + Ok(()) + } +} +/// 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(); + // Clear ehr bit + regs.rng_icr().write(|w| { + w.set_ehr_valid(true); + }); + if isr.ehr_valid() { + T::waker().wake(); + } else { + // 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 + if isr.autocorr_err() { + warn!("TRNG Autocorrect error! Resetting TRNG"); + regs.trng_sw_reset().write(|w| { + w.set_trng_sw_reset(true); + }); + } + } + } +} diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs new file mode 100644 index 0000000..152a432 --- /dev/null +++ b/embassy-rp/src/uart/buffered.rs @@ -0,0 +1,839 @@ +//! Buffered UART driver. +use core::future::Future; +use core::slice; + +use atomic_polyfill::AtomicU8; +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<'d, T: Instance> { + pub(crate) rx: BufferedUartRx<'d, T>, + pub(crate) tx: BufferedUartTx<'d, T>, +} + +/// Buffered UART RX handle. +pub struct BufferedUartRx<'d, T: Instance> { + pub(crate) phantom: PhantomData<&'d mut T>, +} + +/// Buffered UART TX handle. +pub struct BufferedUartTx<'d, T: Instance> { + pub(crate) phantom: PhantomData<&'d mut T>, +} + +pub(crate) fn init_buffers<'d, T: Instance + 'd>( + _irq: impl Binding>, + tx_buffer: Option<&'d mut [u8]>, + rx_buffer: Option<&'d mut [u8]>, +) { + let state = T::buffered_state(); + + 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. + let regs = T::regs(); + regs.uartimsc().write(|w| { + w.set_rxim(true); + w.set_rtim(true); + w.set_txim(true); + }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; +} + +impl<'d, T: Instance> BufferedUart<'d, T> { + /// Create a buffered UART instance. + pub fn new( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, rx); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), Some(rx.map_into()), None, None, config); + init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + + Self { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } + + /// Create a buffered UART instance with flow control. + pub fn new_with_rtscts( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts); + + super::Uart::<'d, T, Async>::init( + Some(tx.map_into()), + Some(rx.map_into()), + Some(rts.map_into()), + Some(cts.map_into()), + config, + ); + init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + + Self { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } + + /// 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(&mut self, baudrate: u32) { + super::Uart::<'d, T, Async>::set_baudrate_inner(baudrate); + } + + /// Split into separate RX and TX handles. + pub fn split(self) -> (BufferedUartTx<'d, T>, BufferedUartRx<'d, T>) { + (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<'d, T>, &mut BufferedUartRx<'d, T>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'d, T: Instance> BufferedUartRx<'d, T> { + /// Create a new buffered UART RX. + pub fn new( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + rx: impl Peripheral

> + 'd, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(rx); + + super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), None, None, config); + init_buffers::(irq, None, Some(rx_buffer)); + + Self { phantom: PhantomData } + } + + /// Create a new buffered UART RX with flow control. + pub fn new_with_rts( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(rx, rts); + + super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), Some(rts.map_into()), None, config); + init_buffers::(irq, None, Some(rx_buffer)); + + Self { phantom: PhantomData } + } + + fn read<'a>(buf: &'a mut [u8]) -> impl Future> + 'a + where + T: 'd, + { + poll_fn(move |cx| { + if let Poll::Ready(r) = Self::try_read(buf) { + return Poll::Ready(r); + } + T::buffered_state().rx_waker.register(cx.waker()); + Poll::Pending + }) + } + + fn get_rx_error() -> Option { + let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed); + 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(buf: &mut [u8]) -> Poll> + where + T: 'd, + { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let state = T::buffered_state(); + 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() { + 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. + let regs = T::regs(); + 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(buf) { + Poll::Ready(res) => return res, + Poll::Pending => continue, + } + } + } + + fn fill_buf<'a>() -> impl Future> + where + T: 'd, + { + poll_fn(move |cx| { + let state = T::buffered_state(); + 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() { + 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(amt: usize) { + let state = T::buffered_state(); + 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. + let regs = T::regs(); + 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() -> Result { + let state = T::buffered_state(); + Ok(!state.rx_buf.is_empty()) + } +} + +impl<'d, T: Instance> BufferedUartTx<'d, T> { + /// Create a new buffered UART TX. + pub fn new( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, None, config); + init_buffers::(irq, Some(tx_buffer), None); + + Self { phantom: PhantomData } + } + + /// Create a new buffered UART TX with flow control. + pub fn new_with_cts( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, cts); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, Some(cts.map_into()), config); + init_buffers::(irq, Some(tx_buffer), None); + + Self { phantom: PhantomData } + } + + fn write(buf: &[u8]) -> impl Future> + '_ { + poll_fn(move |cx| { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let state = T::buffered_state(); + 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. + T::Interrupt::pend(); + Poll::Ready(Ok(n)) + }) + } + + fn flush() -> impl Future> { + poll_fn(move |cx| { + let state = T::buffered_state(); + 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 state = T::buffered_state(); + 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 { + // 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. + T::Interrupt::pend(); + return Ok(n); + } + } + } + + /// Flush UART TX blocking execution until done. + pub fn blocking_flush(&mut self) -> Result<(), Error> { + loop { + let state = T::buffered_state(); + if state.tx_buf.is_empty() { + return Ok(()); + } + } + } + + /// Check if UART is busy. + pub fn busy(&self) -> bool { + T::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 = T::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().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<'d, T: Instance> Drop for BufferedUartRx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { state.rx_buf.deinit() } + + // TX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if !state.tx_buf.is_available() { + T::Interrupt::disable(); + } + } +} + +impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { state.tx_buf.deinit() } + + // RX is inactive if the buffer is not available. + // We can now unregister the interrupt handler + if !state.rx_buf.is_available() { + T::Interrupt::disable(); + } + } +} + +/// Interrupt handler. +pub struct BufferedInterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + let r = T::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 { + s.rx_error.fetch_or((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<'d, T: Instance> embedded_io_async::ErrorType for BufferedUart<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartRx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io_async::ErrorType for BufferedUartTx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUart<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + BufferedUartRx::<'d, T>::read(buf).await + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::Read for BufferedUartRx<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(buf).await + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUart<'d, T> { + fn read_ready(&mut self) -> Result { + BufferedUartRx::<'d, T>::read_ready() + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::ReadReady for BufferedUartRx<'d, T> { + fn read_ready(&mut self) -> Result { + Self::read_ready() + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUart<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + BufferedUartRx::<'d, T>::fill_buf().await + } + + fn consume(&mut self, amt: usize) { + BufferedUartRx::<'d, T>::consume(amt) + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::BufRead for BufferedUartRx<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Self::consume(amt) + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUart<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + BufferedUartTx::<'d, T>::write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + BufferedUartTx::<'d, T>::flush().await + } +} + +impl<'d, T: Instance + 'd> embedded_io_async::Write for BufferedUartTx<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush().await + } +} + +impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUart<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.blocking_read(buf) + } +} + +impl<'d, T: Instance + 'd> embedded_io::Read for BufferedUartRx<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read(buf) + } +} + +impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUart<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl<'d, T: Instance + 'd> embedded_io::Write for BufferedUartTx<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + let r = T::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, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { + 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<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { + 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<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartRx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUartTx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_nb::serial::ErrorType for BufferedUart<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { + fn read(&mut self) -> nb::Result { + embedded_hal_02::serial::Read::read(self) + } +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { + 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<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { + 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/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs new file mode 100644 index 0000000..8d12aee --- /dev/null +++ b/embassy-rp/src/uart/mod.rs @@ -0,0 +1,1509 @@ +//! UART driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use atomic_polyfill::{AtomicU16, Ordering}; +use embassy_futures::select::{select, Either}; +use embassy_hal_internal::{into_ref, PeripheralRef}; +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}; +use crate::pac::io::vals::{Inover, Outover}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +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, T: Instance, M: Mode> { + tx: UartTx<'d, T, M>, + rx: UartRx<'d, T, M>, +} + +/// UART TX driver. +pub struct UartTx<'d, T: Instance, M: Mode> { + tx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +/// UART RX driver. +pub struct UartRx<'d, T: Instance, M: Mode> { + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, tx_dma); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(Some(tx_dma.map_into())) + } + + fn new_inner(tx_dma: Option>) -> Self { + Self { + tx_dma, + phantom: PhantomData, + } + } + + /// Transmit the provided buffer blocking execution until done. + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = T::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> { + let r = T::regs(); + while !r.uartfr().read().txfe() {} + Ok(()) + } + + /// Check if UART is busy transmitting. + pub fn busy(&self) -> bool { + T::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 = T::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, T: Instance> UartTx<'d, T, Blocking> { + /// Create a new UART TX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(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<'d, T> { + buffered::init_buffers::(irq, Some(tx_buffer), None); + + BufferedUartTx { phantom: PhantomData } + } +} + +impl<'d, T: Instance> UartTx<'d, T, 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(); + let transfer = unsafe { + T::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, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ.into()) + }; + transfer.await; + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { + /// Create a new DMA-enabled UART which can only receive data + pub fn new( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rx, rx_dma); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(true, Some(rx_dma.map_into())) + } + + fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + debug_assert_eq!(has_irq, rx_dma.is_some()); + if has_irq { + // disable all error interrupts initially + T::regs().uartimsc().write(|w| w.0 = 0); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + } + Self { + 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 = T::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, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { + fn drop(&mut self) { + if self.rx_dma.is_some() { + T::Interrupt::disable(); + // clear dma flags. irq handlers use these to disambiguate among themselves. + T::regs().uartdmacr().write_clear(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + reg.set_dmaonerr(true); + }); + } + } +} + +impl<'d, T: Instance> UartRx<'d, T, Blocking> { + /// Create a new UART RX instance for blocking mode operations. + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(rx); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(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<'d, T> { + buffered::init_buffers::(irq, None, Some(rx_buffer)); + + BufferedUartRx { phantom: PhantomData } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let uart = T::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, T: Instance> UartRx<'d, T, 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. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::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(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::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, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ.into()) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 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(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) 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 line break. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line 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 + line 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 + line 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 + 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 line 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 line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n > min_count` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// If a line 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 + line 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 + line 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 + 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. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::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 mut ch = self.rx_dma.as_mut().unwrap(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::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( + &mut ch, + T::regs().uartdr().as_ptr() as *const _, + sbuffer, + T::RX_DREQ.into(), + ) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 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(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) 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 = T::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, T: Instance> Uart<'d, T, Blocking> { + /// Create a new UART without hardware flow control + pub fn new_blocking( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + None, + None, + false, + None, + None, + config, + ) + } + + /// Create a new UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts_blocking( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + Some(rts.map_into()), + Some(cts.map_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<'d, T> { + buffered::init_buffers::(irq, Some(tx_buffer), Some(rx_buffer)); + + BufferedUart { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } +} + +impl<'d, T: Instance> Uart<'d, T, Async> { + /// Create a new DMA enabled UART without hardware flow control + pub fn new( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx, tx_dma, rx_dma); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + None, + None, + true, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), + config, + ) + } + + /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts, tx_dma, rx_dma); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + Some(rts.map_into()), + Some(cts.map_into()), + true, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), + config, + ) + } +} + +impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { + fn new_inner( + _uart: impl Peripheral

+ 'd, + mut tx: PeripheralRef<'d, AnyPin>, + mut rx: PeripheralRef<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, + has_irq: bool, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + Self::init( + 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(tx_dma), + rx: UartRx::new_inner(has_irq, rx_dma), + } + } + + fn init( + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) { + let r = T::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(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(0b000); + 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(f: impl FnOnce(&mut crate::pac::uart::regs::UartlcrH) -> R) -> R { + let r = T::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(baudrate); + } + + fn set_baudrate_inner(baudrate: u32) { + let r = T::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(|_| {}); + } +} + +impl<'d, T: Instance, M: Mode> Uart<'d, T, 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, T, M>, UartRx<'d, T, 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, T, M>, &mut UartRx<'d, T, M>) { + (&mut self.tx, &mut self.rx) + } +} + +impl<'d, T: Instance> Uart<'d, T, 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, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { + type Error = Error; + fn read(&mut self) -> Result> { + let r = T::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, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + let r = T::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 = T::regs(); + if !r.uartfr().read().txfe() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, 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, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, 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, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, 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, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartRx<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for UartTx<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::ErrorType for Uart<'d, T, M> { + type Error = Error; +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { + fn read(&mut self) -> nb::Result { + let r = T::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, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, 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, T: Instance> embedded_io::ErrorType for UartTx<'d, T, Blocking> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Write for UartTx<'d, T, 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, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } +} + +impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, 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, T: Instance> embedded_io::ErrorType for Uart<'d, T, Blocking> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Write for Uart<'d, T, 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() + } +} + +trait SealedMode {} + +trait SealedInstance { + const TX_DREQ: pac::dma::vals::TreqSel; + const RX_DREQ: pac::dma::vals::TreqSel; + + fn regs() -> pac::uart::Uart; + + 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 { + /// 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 { + const TX_DREQ: pac::dma::vals::TreqSel = $tx_dreq; + const RX_DREQ: pac::dma::vals::TreqSel = $rx_dreq; + + fn regs() -> pac::uart::Uart { + pac::$inst + } + + 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/src/usb.rs b/embassy-rp/src/usb.rs new file mode 100644 index 0000000..26cb90d --- /dev/null +++ b/embassy-rp/src/usb.rs @@ -0,0 +1,827 @@ +//! USB driver. +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +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::{interrupt, pac, peripherals, Peripheral, RegExt}; + +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 + '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: impl Peripheral

+ 'd, _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, + 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 = 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, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, 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/src/watchdog.rs b/embassy-rp/src/watchdog.rs new file mode 100644 index 0000000..5539366 --- /dev/null +++ b/embassy-rp/src/watchdog.rs @@ -0,0 +1,165 @@ +//! 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::pac; +use crate::peripherals::WATCHDOG; + +/// 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: 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) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.as_micros(); + if delay_us > (MAX_PERIOD / 2) as u64 { + panic!("Period cannot exceed {} microseconds", MAX_PERIOD / 2); + } + 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 + self.load_value = delay_us * 2; + + 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/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md new file mode 100644 index 0000000..2049e0f --- /dev/null +++ b/embassy-sync/CHANGELOG.md @@ -0,0 +1,66 @@ +# Changelog + +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 + +## 0.6.2 - 2025-01-15 + +- Add dynamic dispatch variant of `Pipe`. + +## 0.6.1 - 2024-11-22 + +- Add `LazyLock` sync primitive. +- Add `Watch` sync primitive. +- Add `clear`, `len`, `is_empty` and `is_full` functions to `zerocopy_channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `channel::{Sender, Receiver}`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `priority_channel::{Sender, Receiver}`. +- Add `GenericAtomicWaker` utility. + +## 0.6.0 - 2024-05-29 + +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `Channel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PriorityChannel`. +- Add `capacity`, `free_capacity`, `clear`, `len`, `is_empty` and `is_full` functions to `PubSubChannel`. +- Made `PubSubBehavior` sealed + - If you called `.publish_immediate(...)` on the queue directly before, then now call `.immediate_publisher().publish_immediate(...)` +- Add `OnceLock` sync primitive. +- Add constructor for `DynamicChannel` +- Add ready_to_receive functions to `Channel` and `Receiver`. + +## 0.5.0 - 2023-12-04 + +- Add a `PriorityChannel`. +- Remove `nightly` and `unstable-traits` features in preparation for 1.75. +- Upgrade `heapless` to 0.8. +- Upgrade `static-cell` to 2.0. + +## 0.4.0 - 2023-10-31 + +- Re-add `impl_trait_projections` +- switch to `embedded-io 0.6` + +## 0.3.0 - 2023-09-14 + +- switch to `embedded-io 0.5` +- add api for polling channels with context +- standardise fn names on channels +- add zero-copy channel + +## 0.2.0 - 2023-04-13 + +- pubsub: Fix messages not getting popped when the last subscriber that needed them gets dropped. +- pubsub: Move instead of clone messages when the last subscriber pops them. +- pubsub: Pop messages which count is 0 after unsubscribe. +- Update `embedded-io` from `0.3` to `0.4` (uses `async fn` in traits). +- impl `Default` for `WakerRegistration` +- impl `Default` for `Signal` +- Remove unnecessary uses of `atomic-polyfill` +- Add `#[must_use]` to all futures. + +## 0.1.0 - 2022-08-26 + +- First release diff --git a/embassy-sync/Cargo.toml b/embassy-sync/Cargo.toml new file mode 100644 index 0000000..4430f19 --- /dev/null +++ b/embassy-sync/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "embassy-sync" +version = "0.6.2" +edition = "2021" +description = "no-std, no-alloc synchronization primitives with async support" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-sync" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-sync-v$VERSION/embassy-sync/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-sync/src/" +target = "thumbv7em-none-eabi" + +[features] +std = ["critical-section/std"] +turbowakers = [] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +futures-sink = { version = "0.3", default-features = false, features = [] } +futures-util = { version = "0.3.17", default-features = false } +critical-section = "1.1" +heapless = "0.8" +cfg-if = "1.0.0" +embedded-io-async = { version = "0.6.1" } + +[dev-dependencies] +futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } +futures-test = "0.3.17" +futures-timer = "3.0.2" +futures-util = { version = "0.3.17", features = [ "channel", "sink" ] } + +# Enable critical-section implementation for std, for tests +critical-section = { version = "1.1", features = ["std"] } +static_cell = { version = "2" } diff --git a/embassy-sync/README.md b/embassy-sync/README.md new file mode 100644 index 0000000..6871bca --- /dev/null +++ b/embassy-sync/README.md @@ -0,0 +1,21 @@ +# embassy-sync + +An [Embassy](https://embassy.dev) project. + +Synchronization primitives and data structures with async support: + +- [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. +- [`PriorityChannel`](priority_channel::PriorityChannel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. Higher priority items are shifted to the front of the channel. +- [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. +- [`Signal`](signal::Signal) - Signalling latest value to a single consumer. +- [`Watch`](watch::Watch) - Signalling latest value to multiple consumers. +- [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. +- [`Pipe`](pipe::Pipe) - Byte stream implementing `embedded_io` traits. +- [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. +- [`AtomicWaker`](waitqueue::AtomicWaker) - A variant of `WakerRegistration` accessible using a non-mut API. +- [`MultiWakerRegistration`](waitqueue::MultiWakerRegistration) - Utility registering and waking multiple `Waker`'s. +- [`LazyLock`](lazy_lock::LazyLock) - A value which is initialized on the first access + +## Interoperability + +Futures from this crate can run on any executor. diff --git a/embassy-sync/build.rs b/embassy-sync/build.rs new file mode 100644 index 0000000..ecd2c0c --- /dev/null +++ b/embassy-sync/build.rs @@ -0,0 +1,7 @@ +#[path = "./build_common.rs"] +mod common; + +fn main() { + let mut cfgs = common::CfgSet::new(); + common::set_target_cfgs(&mut cfgs); +} diff --git a/embassy-sync/build_common.rs b/embassy-sync/build_common.rs new file mode 100644 index 0000000..4f24e6d --- /dev/null +++ b/embassy-sync/build_common.rs @@ -0,0 +1,94 @@ +// NOTE: this file is copy-pasted between several Embassy crates, because there is no +// straightforward way to share this code: +// - it cannot be placed into the root of the repo and linked from each build.rs using `#[path = +// "../build_common.rs"]`, because `cargo publish` requires that all files published with a crate +// reside in the crate's directory, +// - it cannot be symlinked from `embassy-xxx/build_common.rs` to `../build_common.rs`, because +// symlinks don't work on Windows. + +use std::collections::HashSet; +use std::env; + +/// Helper for emitting cargo instruction for enabling configs (`cargo:rustc-cfg=X`) and declaring +/// them (`cargo:rust-check-cfg=cfg(X)`). +#[derive(Debug)] +pub struct CfgSet { + enabled: HashSet, + declared: HashSet, +} + +impl CfgSet { + pub fn new() -> Self { + Self { + enabled: HashSet::new(), + declared: HashSet::new(), + } + } + + /// Enable a config, which can then be used in `#[cfg(...)]` for conditional compilation. + /// + /// All configs that can potentially be enabled should be unconditionally declared using + /// [`Self::declare()`]. + pub fn enable(&mut self, cfg: impl AsRef) { + if self.enabled.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-cfg={}", cfg.as_ref()); + } + } + + pub fn enable_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.enable(cfg.as_ref()); + } + } + + /// Declare a valid config for conditional compilation, without enabling it. + /// + /// This enables rustc to check that the configs in `#[cfg(...)]` attributes are valid. + pub fn declare(&mut self, cfg: impl AsRef) { + if self.declared.insert(cfg.as_ref().to_owned()) { + println!("cargo:rustc-check-cfg=cfg({})", cfg.as_ref()); + } + } + + pub fn declare_all(&mut self, cfgs: &[impl AsRef]) { + for cfg in cfgs.iter() { + self.declare(cfg.as_ref()); + } + } + + pub fn set(&mut self, cfg: impl Into, enable: bool) { + let cfg = cfg.into(); + if enable { + self.enable(cfg.clone()); + } + self.declare(cfg); + } +} + +/// Sets configs that describe the target platform. +pub fn set_target_cfgs(cfgs: &mut CfgSet) { + let target = env::var("TARGET").unwrap(); + + if target.starts_with("thumbv6m-") { + cfgs.enable_all(&["cortex_m", "armv6m"]); + } else if target.starts_with("thumbv7m-") { + cfgs.enable_all(&["cortex_m", "armv7m"]); + } else if target.starts_with("thumbv7em-") { + cfgs.enable_all(&["cortex_m", "armv7m", "armv7em"]); + } else if target.starts_with("thumbv8m.base") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_base"]); + } else if target.starts_with("thumbv8m.main") { + cfgs.enable_all(&["cortex_m", "armv8m", "armv8m_main"]); + } + cfgs.declare_all(&[ + "cortex_m", + "armv6m", + "armv7m", + "armv7em", + "armv8m", + "armv8m_base", + "armv8m_main", + ]); + + cfgs.set("has_fpu", target.ends_with("-eabihf")); +} diff --git a/embassy-sync/src/blocking_mutex/mod.rs b/embassy-sync/src/blocking_mutex/mod.rs new file mode 100644 index 0000000..beafdb4 --- /dev/null +++ b/embassy-sync/src/blocking_mutex/mod.rs @@ -0,0 +1,190 @@ +//! Blocking mutex. +//! +//! This module provides a blocking mutex that can be used to synchronize data. +pub mod raw; + +use core::cell::UnsafeCell; + +use self::raw::RawMutex; + +/// Blocking mutex (not async) +/// +/// Provides a blocking mutual exclusion primitive backed by an implementation of [`raw::RawMutex`]. +/// +/// Which implementation you select depends on the context in which you're using the mutex, and you can choose which kind +/// of interior mutability fits your use case. +/// +/// Use [`CriticalSectionMutex`] when data can be shared between threads and interrupts. +/// +/// Use [`NoopMutex`] when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeMutex`] when data is shared between tasks running on the same executor but you want a global singleton. +/// +/// In all cases, the blocking mutex is intended to be short lived and not held across await points. +/// Use the async [`Mutex`](crate::mutex::Mutex) if you need a lock that is held across await points. +pub struct Mutex { + // NOTE: `raw` must be FIRST, so when using ThreadModeMutex the "can't drop in non-thread-mode" gets + // to run BEFORE dropping `data`. + raw: R, + data: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +impl Mutex { + /// Creates a new mutex in an unlocked state ready for use. + #[inline] + pub const fn new(val: T) -> Mutex { + Mutex { + raw: R::INIT, + data: UnsafeCell::new(val), + } + } + + /// Creates a critical section and grants temporary access to the protected data. + pub fn lock(&self, f: impl FnOnce(&T) -> U) -> U { + self.raw.lock(|| { + let ptr = self.data.get() as *const T; + let inner = unsafe { &*ptr }; + f(inner) + }) + } +} + +impl Mutex { + /// Creates a new mutex based on a pre-existing raw mutex. + /// + /// This allows creating a mutex in a constant context on stable Rust. + #[inline] + pub const fn const_new(raw_mutex: R, val: T) -> Mutex { + Mutex { + raw: raw_mutex, + data: UnsafeCell::new(val), + } + } + + /// Consumes this mutex, returning the underlying data. + #[inline] + pub fn into_inner(self) -> T { + self.data.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the `Mutex` mutably, no actual locking needs to + /// take place---the mutable borrow statically guarantees no locks exist. + #[inline] + pub fn get_mut(&mut self) -> &mut T { + unsafe { &mut *self.data.get() } + } +} + +/// A mutex that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub type CriticalSectionMutex = Mutex; + +/// A mutex that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Mutex is only safe within a single executor.** +pub type NoopMutex = Mutex; + +impl Mutex { + /// Borrows the data for the duration of the critical section + pub fn borrow<'cs>(&'cs self, _cs: critical_section::CriticalSection<'cs>) -> &'cs T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +impl Mutex { + /// Borrows the data + #[allow(clippy::should_implement_trait)] + pub fn borrow(&self) -> &T { + let ptr = self.data.get() as *const T; + unsafe { &*ptr } + } +} + +// ThreadModeMutex does NOT use the generic mutex from above because it's special: +// it's Send+Sync even if T: !Send. There's no way to do that without specialization (I think?). +// +// There's still a ThreadModeRawMutex for use with the generic Mutex (handy with Channel, for example), +// but that will require T: Send even though it shouldn't be needed. + +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode_mutex::*; +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode_mutex { + use super::*; + + /// A "mutex" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Mutex is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeMutex` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeMutex { + inner: UnsafeCell, + } + + // NOTE: ThreadModeMutex only allows borrowing from one execution context ever: thread mode. + // Therefore it cannot be used to send non-sendable stuff between execution contexts, so it can + // be Send+Sync even if T is not Send (unlike CriticalSectionMutex) + unsafe impl Sync for ThreadModeMutex {} + unsafe impl Send for ThreadModeMutex {} + + impl ThreadModeMutex { + /// Creates a new mutex + pub const fn new(value: T) -> Self { + ThreadModeMutex { + inner: UnsafeCell::new(value), + } + } + } + + impl ThreadModeMutex { + /// Lock the `ThreadModeMutex`, granting access to the data. + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn lock(&self, f: impl FnOnce(&T) -> R) -> R { + f(self.borrow()) + } + + /// Borrows the data + /// + /// # Panics + /// + /// This will panic if not currently running in thread mode. + pub fn borrow(&self) -> &T { + assert!( + raw::in_thread_mode(), + "ThreadModeMutex can only be borrowed from thread mode." + ); + unsafe { &*self.inner.get() } + } + } + + impl Drop for ThreadModeMutex { + fn drop(&mut self) { + // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so + // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if + // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, + // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. + assert!( + raw::in_thread_mode(), + "ThreadModeMutex can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } +} diff --git a/embassy-sync/src/blocking_mutex/raw.rs b/embassy-sync/src/blocking_mutex/raw.rs new file mode 100644 index 0000000..a8afcad --- /dev/null +++ b/embassy-sync/src/blocking_mutex/raw.rs @@ -0,0 +1,149 @@ +//! Mutex primitives. +//! +//! This module provides a trait for mutexes that can be used in different contexts. +use core::marker::PhantomData; + +/// Raw mutex trait. +/// +/// This mutex is "raw", which means it does not actually contain the protected data, it +/// just implements the mutex mechanism. For most uses you should use [`super::Mutex`] instead, +/// which is generic over a RawMutex and contains the protected data. +/// +/// Note that, unlike other mutexes, implementations only guarantee no +/// concurrent access from other threads: concurrent access from the current +/// thread is allowed. For example, it's possible to lock the same mutex multiple times reentrantly. +/// +/// Therefore, locking a `RawMutex` is only enough to guarantee safe shared (`&`) access +/// to the data, it is not enough to guarantee exclusive (`&mut`) access. +/// +/// # Safety +/// +/// RawMutex implementations must ensure that, while locked, no other thread can lock +/// the RawMutex concurrently. +/// +/// Unsafe code is allowed to rely on this fact, so incorrect implementations will cause undefined behavior. +pub unsafe trait RawMutex { + /// Create a new `RawMutex` instance. + /// + /// This is a const instead of a method to allow creating instances in const context. + const INIT: Self; + + /// Lock this `RawMutex`. + fn lock(&self, f: impl FnOnce() -> R) -> R; +} + +/// A mutex that allows borrowing data across executors and interrupts. +/// +/// # Safety +/// +/// This mutex is safe to share between different executors and interrupts. +pub struct CriticalSectionRawMutex { + _phantom: PhantomData<()>, +} +unsafe impl Send for CriticalSectionRawMutex {} +unsafe impl Sync for CriticalSectionRawMutex {} + +impl CriticalSectionRawMutex { + /// Create a new `CriticalSectionRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for CriticalSectionRawMutex { + const INIT: Self = Self::new(); + + fn lock(&self, f: impl FnOnce() -> R) -> R { + critical_section::with(|_| f()) + } +} + +// ================ + +/// A mutex that allows borrowing data in the context of a single executor. +/// +/// # Safety +/// +/// **This Mutex is only safe within a single executor.** +pub struct NoopRawMutex { + _phantom: PhantomData<*mut ()>, +} + +unsafe impl Send for NoopRawMutex {} + +impl NoopRawMutex { + /// Create a new `NoopRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } +} + +unsafe impl RawMutex for NoopRawMutex { + const INIT: Self = Self::new(); + fn lock(&self, f: impl FnOnce() -> R) -> R { + f() + } +} + +// ================ + +#[cfg(any(cortex_m, feature = "std"))] +mod thread_mode { + use super::*; + + /// A "mutex" that only allows borrowing from thread mode. + /// + /// # Safety + /// + /// **This Mutex is only safe on single-core systems.** + /// + /// On multi-core systems, a `ThreadModeRawMutex` **is not sufficient** to ensure exclusive access. + pub struct ThreadModeRawMutex { + _phantom: PhantomData<()>, + } + + unsafe impl Send for ThreadModeRawMutex {} + unsafe impl Sync for ThreadModeRawMutex {} + + impl ThreadModeRawMutex { + /// Create a new `ThreadModeRawMutex`. + pub const fn new() -> Self { + Self { _phantom: PhantomData } + } + } + + unsafe impl RawMutex for ThreadModeRawMutex { + const INIT: Self = Self::new(); + fn lock(&self, f: impl FnOnce() -> R) -> R { + assert!(in_thread_mode(), "ThreadModeMutex can only be locked from thread mode."); + + f() + } + } + + impl Drop for ThreadModeRawMutex { + fn drop(&mut self) { + // Only allow dropping from thread mode. Dropping calls drop on the inner `T`, so + // `drop` needs the same guarantees as `lock`. `ThreadModeMutex` is Send even if + // T isn't, so without this check a user could create a ThreadModeMutex in thread mode, + // send it to interrupt context and drop it there, which would "send" a T even if T is not Send. + assert!( + in_thread_mode(), + "ThreadModeMutex can only be dropped from thread mode." + ); + + // Drop of the inner `T` happens after this. + } + } + + pub(crate) fn in_thread_mode() -> bool { + #[cfg(feature = "std")] + return Some("main") == std::thread::current().name(); + + #[cfg(not(feature = "std"))] + // ICSR.VECTACTIVE == 0 + return unsafe { (0xE000ED04 as *const u32).read_volatile() } & 0x1FF == 0; + } +} +#[cfg(any(cortex_m, feature = "std"))] +pub use thread_mode::*; diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs new file mode 100644 index 0000000..4d1fa9e --- /dev/null +++ b/embassy-sync/src/channel.rs @@ -0,0 +1,935 @@ +//! A queue for sending values between asynchronous tasks. +//! +//! It can be used concurrently by multiple producers (senders) and multiple +//! consumers (receivers), i.e. it is an "MPMC channel". +//! +//! Receivers are competing for messages. So a message that is received by +//! one receiver is not received by any other. +//! +//! This queue takes a Mutex type so that various +//! targets can be attained. For example, a ThreadModeMutex can be used +//! for single-core Cortex-M targets where messages are only passed +//! between tasks running in thread mode. Similarly, a CriticalSectionMutex +//! can also be used for single-core targets where messages are to be +//! passed from exception mode e.g. out of an interrupt handler. +//! +//! This module provides a bounded channel that has a limit on the number of +//! messages that it can store, and if this limit is reached, trying to send +//! another message will result in an error being returned. +//! + +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use heapless::Deque; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::WakerRegistration; + +/// Send-only access to a [`Channel`]. +pub struct Sender<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Clone for Sender<'ch, M, T, N> +where + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, const N: usize> Copy for Sender<'ch, M, T, N> where M: RawMutex {} + +impl<'ch, M, T, const N: usize> Sender<'ch, M, T, N> +where + M: RawMutex, +{ + /// Sends a value. + /// + /// See [`Channel::send()`] + pub fn send(&self, message: T) -> SendFuture<'ch, M, T, N> { + self.channel.send(message) + } + + /// Attempt to immediately send a message. + /// + /// See [`Channel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send(message) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +/// Send-only access to a [`Channel`] without knowing channel size. +pub struct DynamicSender<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for DynamicSender<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for DynamicSender<'ch, T> {} + +impl<'ch, M, T, const N: usize> From> for DynamicSender<'ch, T> +where + M: RawMutex, +{ + fn from(s: Sender<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +impl<'ch, T> DynamicSender<'ch, T> { + /// Sends a value. + /// + /// See [`Channel::send()`] + pub fn send(&self, message: T) -> DynamicSendFuture<'ch, T> { + DynamicSendFuture { + channel: self.channel, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// See [`Channel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send_with_context(message, None) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`Channel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } +} + +/// Receive-only access to a [`Channel`]. +pub struct Receiver<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Clone for Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, const N: usize> Copy for Receiver<'ch, M, T, N> where M: RawMutex {} + +impl<'ch, M, T, const N: usize> Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + /// Receive the next value. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, N> { + self.channel.receive() + } + + /// Is a value ready to be received in the channel + /// + /// See [`Channel::ready_to_receive()`]. + pub fn ready_to_receive(&self) -> ReceiveReadyFuture<'_, M, T, N> { + self.channel.ready_to_receive() + } + + /// Attempt to immediately receive the next value. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive() + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`Channel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`Channel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`Channel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`Channel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`Channel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`Channel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +/// Receive-only access to a [`Channel`] without knowing channel size. +pub struct DynamicReceiver<'ch, T> { + pub(crate) channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Clone for DynamicReceiver<'ch, T> { + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, T> Copy for DynamicReceiver<'ch, T> {} + +impl<'ch, T> DynamicReceiver<'ch, T> { + /// Receive the next value. + /// + /// See [`Channel::receive()`]. + pub fn receive(&self) -> DynamicReceiveFuture<'_, T> { + DynamicReceiveFuture { channel: self.channel } + } + + /// Attempt to immediately receive the next value. + /// + /// See [`Channel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive_with_context(None) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`Channel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`Channel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +impl<'ch, M, T, const N: usize> From> for DynamicReceiver<'ch, T> +where + M: RawMutex, +{ + fn from(s: Receiver<'ch, M, T, N>) -> Self { + Self { channel: s.channel } + } +} + +impl<'ch, M, T, const N: usize> futures_util::Stream for Receiver<'ch, M, T, N> +where + M: RawMutex, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.channel.poll_receive(cx).map(Some) + } +} + +/// Future returned by [`Channel::receive`] and [`Receiver::receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiveFuture<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Future for ReceiveFuture<'ch, M, T, N> +where + M: RawMutex, +{ + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +/// Future returned by [`Channel::ready_to_receive`] and [`Receiver::ready_to_receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiveReadyFuture<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, +} + +impl<'ch, M, T, const N: usize> Future for ReceiveReadyFuture<'ch, M, T, N> +where + M: RawMutex, +{ + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } +} + +/// Future returned by [`DynamicReceiver::receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicReceiveFuture<'ch, T> { + channel: &'ch dyn DynamicChannel, +} + +impl<'ch, T> Future for DynamicReceiveFuture<'ch, T> { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.channel.try_receive_with_context(Some(cx)) { + Ok(v) => Poll::Ready(v), + Err(TryReceiveError::Empty) => Poll::Pending, + } + } +} + +impl<'ch, M: RawMutex, T, const N: usize> From> for DynamicReceiveFuture<'ch, T> { + fn from(value: ReceiveFuture<'ch, M, T, N>) -> Self { + Self { channel: value.channel } + } +} + +/// Future returned by [`Channel::send`] and [`Sender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SendFuture<'ch, M, T, const N: usize> +where + M: RawMutex, +{ + channel: &'ch Channel, + message: Option, +} + +impl<'ch, M, T, const N: usize> Future for SendFuture<'ch, M, T, N> +where + M: RawMutex, +{ + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.message.take() { + Some(m) => match self.channel.try_send_with_context(m, Some(cx)) { + Ok(..) => Poll::Ready(()), + Err(TrySendError::Full(m)) => { + self.message = Some(m); + Poll::Pending + } + }, + None => panic!("Message cannot be None"), + } + } +} + +impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {} + +/// Future returned by [`DynamicSender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicSendFuture<'ch, T> { + channel: &'ch dyn DynamicChannel, + message: Option, +} + +impl<'ch, T> Future for DynamicSendFuture<'ch, T> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.message.take() { + Some(m) => match self.channel.try_send_with_context(m, Some(cx)) { + Ok(..) => Poll::Ready(()), + Err(TrySendError::Full(m)) => { + self.message = Some(m); + Poll::Pending + } + }, + None => panic!("Message cannot be None"), + } + } +} + +impl<'ch, T> Unpin for DynamicSendFuture<'ch, T> {} + +impl<'ch, M: RawMutex, T, const N: usize> From> for DynamicSendFuture<'ch, T> { + fn from(value: SendFuture<'ch, M, T, N>) -> Self { + Self { + channel: value.channel, + message: value.message, + } + } +} + +pub(crate) trait DynamicChannel { + fn try_send_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError>; + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result; + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()>; + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()>; + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll; +} + +/// Error returned by [`try_receive`](Channel::try_receive). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryReceiveError { + /// A message could not be received because the channel is empty. + Empty, +} + +/// Error returned by [`try_send`](Channel::try_send). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TrySendError { + /// The data could not be sent on the channel because the channel is + /// currently full and sending would require blocking. + Full(T), +} + +struct ChannelState { + queue: Deque, + receiver_waker: WakerRegistration, + senders_waker: WakerRegistration, +} + +impl ChannelState { + const fn new() -> Self { + ChannelState { + queue: Deque::new(), + receiver_waker: WakerRegistration::new(), + senders_waker: WakerRegistration::new(), + } + } + + fn try_receive(&mut self) -> Result { + self.try_receive_with_context(None) + } + + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop_front() { + Ok(message) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + + fn poll_receive(&mut self, cx: &mut Context<'_>) -> Poll { + if self.queue.is_full() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop_front() { + Poll::Ready(message) + } else { + self.receiver_waker.register(cx.waker()); + Poll::Pending + } + } + + fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.receiver_waker.register(cx.waker()); + + if !self.queue.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn try_send(&mut self, message: T) -> Result<(), TrySendError> { + self.try_send_with_context(message, None) + } + + fn try_send_with_context(&mut self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + match self.queue.push_back(message) { + Ok(()) => { + self.receiver_waker.wake(); + Ok(()) + } + Err(message) => { + if let Some(cx) = cx { + self.senders_waker.register(cx.waker()); + } + Err(TrySendError::Full(message)) + } + } + } + + fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.senders_waker.register(cx.waker()); + + if !self.queue.is_full() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn clear(&mut self) { + if self.queue.is_full() { + self.senders_waker.wake(); + } + self.queue.clear(); + } + + fn len(&self) -> usize { + self.queue.len() + } + + fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + fn is_full(&self) -> bool { + self.queue.is_full() + } +} + +/// A bounded channel for communicating between asynchronous tasks +/// with backpressure. +/// +/// The channel will buffer up to the provided number of messages. Once the +/// buffer is full, attempts to `send` new messages will wait until a message is +/// received from the channel. +/// +/// All data sent will become available in the same order as it was sent. +pub struct Channel +where + M: RawMutex, +{ + inner: Mutex>>, +} + +impl Channel +where + M: RawMutex, +{ + /// Establish a new bounded channel. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy_sync::channel::Channel; + /// use embassy_sync::blocking_mutex::raw::NoopRawMutex; + /// + /// // Declare a bounded channel of 3 u32s. + /// let mut channel = Channel::::new(); + /// ``` + pub const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(ChannelState::new())), + } + } + + fn lock(&self, f: impl FnOnce(&mut ChannelState) -> R) -> R { + self.inner.lock(|rc| f(&mut *unwrap!(rc.try_borrow_mut()))) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + self.lock(|c| c.try_receive_with_context(cx)) + } + + /// Poll the channel for the next message + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.lock(|c| c.poll_receive(cx)) + } + + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + self.lock(|c| c.try_send_with_context(m, cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_receive(cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to send + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_send(cx)) + } + + /// Get a sender for this channel. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender { channel: self } + } + + /// Get a receiver for this channel. + pub fn receiver(&self) -> Receiver<'_, M, T, N> { + Receiver { channel: self } + } + + /// Get a sender for this channel using dynamic dispatch. + pub fn dyn_sender(&self) -> DynamicSender<'_, T> { + DynamicSender { channel: self } + } + + /// Get a receiver for this channel using dynamic dispatch. + pub fn dyn_receiver(&self) -> DynamicReceiver<'_, T> { + DynamicReceiver { channel: self } + } + + /// Send a value, waiting until there is capacity. + /// + /// Sending completes when the value has been pushed to the channel's queue. + /// This doesn't mean the value has been received yet. + pub fn send(&self, message: T) -> SendFuture<'_, M, T, N> { + SendFuture { + channel: self, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// This method differs from [`send`](Channel::send) by returning immediately if the channel's + /// buffer is full, instead of waiting. + /// + /// # Errors + /// + /// If the channel capacity has been reached, i.e., the channel has `n` + /// buffered values where `n` is the argument passed to [`Channel`], then an + /// error is returned. + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.lock(|c| c.try_send(message)) + } + + /// Receive the next value. + /// + /// If there are no messages in the channel's buffer, this method will + /// wait until a message is sent. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, N> { + ReceiveFuture { channel: self } + } + + /// Is a value ready to be received in the channel + /// + /// If there are no messages in the channel's buffer, this method will + /// wait until there is at least one + pub fn ready_to_receive(&self) -> ReceiveReadyFuture<'_, M, T, N> { + ReceiveReadyFuture { channel: self } + } + + /// Attempt to immediately receive a message. + /// + /// This method will either receive a message from the channel immediately or return an error + /// if the channel is empty. + pub fn try_receive(&self) -> Result { + self.lock(|c| c.try_receive()) + } + + /// Returns the maximum number of elements the channel can hold. + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + N - self.len() + } + + /// Clears all elements in the channel. + pub fn clear(&self) { + self.lock(|c| c.clear()); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.lock(|c| c.len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.lock(|c| c.is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.lock(|c| c.is_full()) + } +} + +/// Implements the DynamicChannel to allow creating types that are unaware of the queue size with the +/// tradeoff cost of dynamic dispatch. +impl DynamicChannel for Channel +where + M: RawMutex, +{ + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + Channel::try_send_with_context(self, m, cx) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + Channel::try_receive_with_context(self, cx) + } + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + Channel::poll_ready_to_send(self, cx) + } + + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + Channel::poll_ready_to_receive(self, cx) + } + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + Channel::poll_receive(self, cx) + } +} + +impl futures_util::Stream for Channel +where + M: RawMutex, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_receive(cx).map(Some) + } +} + +#[cfg(test)] +mod tests { + use core::time::Duration; + + use futures_executor::ThreadPool; + use futures_timer::Delay; + use futures_util::task::SpawnExt; + use static_cell::StaticCell; + + use super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + fn capacity(c: &ChannelState) -> usize { + c.queue.capacity() - c.queue.len() + } + + #[test] + fn sending_once() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(capacity(&c), 2); + } + + #[test] + fn sending_when_full() { + let mut c = ChannelState::::new(); + let _ = c.try_send(1); + let _ = c.try_send(1); + let _ = c.try_send(1); + match c.try_send(2) { + Err(TrySendError::Full(2)) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 0); + } + + #[test] + fn receiving_once_with_one_send() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + assert_eq!(capacity(&c), 3); + } + + #[test] + fn receiving_when_empty() { + let mut c = ChannelState::::new(); + match c.try_receive() { + Err(TryReceiveError::Empty) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 3); + } + + #[test] + fn simple_send_and_receive() { + let c = Channel::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + } + + #[test] + fn cloning() { + let c = Channel::::new(); + let r1 = c.receiver(); + let s1 = c.sender(); + + let _ = r1.clone(); + let _ = s1.clone(); + } + + #[test] + fn dynamic_dispatch_into() { + let c = Channel::::new(); + let s: DynamicSender<'_, u32> = c.sender().into(); + let r: DynamicReceiver<'_, u32> = c.receiver().into(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + + #[test] + fn dynamic_dispatch_constructor() { + let c = Channel::::new(); + let s = c.dyn_sender(); + let r = c.dyn_receiver(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + + #[futures_test::test] + async fn receiver_receives_given_try_send_async() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(Channel::new()); + let c2 = c; + assert!(executor + .spawn(async move { + assert!(c2.try_send(1).is_ok()); + }) + .is_ok()); + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn sender_send_completes_if_capacity() { + let c = Channel::::new(); + c.send(1).await; + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn senders_sends_wait_until_capacity() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(Channel::new()); + assert!(c.try_send(1).is_ok()); + + let c2 = c; + let send_task_1 = executor.spawn_with_handle(async move { c2.send(2).await }); + let c2 = c; + let send_task_2 = executor.spawn_with_handle(async move { c2.send(3).await }); + // Wish I could think of a means of determining that the async send is waiting instead. + // However, I've used the debugger to observe that the send does indeed wait. + Delay::new(Duration::from_millis(500)).await; + assert_eq!(c.receive().await, 1); + assert!(executor + .spawn(async move { + loop { + c.receive().await; + } + }) + .is_ok()); + send_task_1.unwrap().await; + send_task_2.unwrap().await; + } +} diff --git a/embassy-sync/src/fmt.rs b/embassy-sync/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-sync/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-sync/src/lazy_lock.rs b/embassy-sync/src/lazy_lock.rs new file mode 100644 index 0000000..18e3c20 --- /dev/null +++ b/embassy-sync/src/lazy_lock.rs @@ -0,0 +1,152 @@ +//! Synchronization primitive for initializing a value once, allowing others to get a reference to the value. + +use core::cell::UnsafeCell; +use core::mem::ManuallyDrop; +use core::sync::atomic::{AtomicBool, Ordering}; + +/// The `LazyLock` is a synchronization primitive that allows for +/// initializing a value once, and allowing others to obtain a +/// reference to the value. This is useful for lazy initialization of +/// a static value. +/// +/// # Example +/// ``` +/// use futures_executor::block_on; +/// use embassy_sync::lazy_lock::LazyLock; +/// +/// // Define a static value that will be lazily initialized +/// // at runtime at the first access. +/// static VALUE: LazyLock = LazyLock::new(|| 20); +/// +/// let reference = VALUE.get(); +/// assert_eq!(reference, &20); +/// ``` +pub struct LazyLock T> { + init: AtomicBool, + data: UnsafeCell>, +} + +union Data { + value: ManuallyDrop, + f: ManuallyDrop, +} + +unsafe impl Sync for LazyLock {} + +impl T> LazyLock { + /// Create a new uninitialized `StaticLock`. + pub const fn new(init_fn: F) -> Self { + Self { + init: AtomicBool::new(false), + data: UnsafeCell::new(Data { + f: ManuallyDrop::new(init_fn), + }), + } + } + + /// Get a reference to the underlying value, initializing it if it + /// has not been done already. + #[inline] + pub fn get(&self) -> &T { + self.ensure_init_fast(); + unsafe { &(*self.data.get()).value } + } + + /// Consume the `LazyLock`, returning the underlying value. The + /// initialization function will be called if it has not been + /// already. + #[inline] + pub fn into_inner(self) -> T { + self.ensure_init_fast(); + let this = ManuallyDrop::new(self); + let data = unsafe { core::ptr::read(&this.data) }.into_inner(); + + ManuallyDrop::into_inner(unsafe { data.value }) + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// This function is a fast track to [`Self::ensure_init`] + /// which does not require a critical section in most cases when + /// the value has been initialized already. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + #[inline] + fn ensure_init_fast(&self) { + if !self.init.load(Ordering::Acquire) { + self.ensure_init(); + } + } + + /// Initialize the `LazyLock` if it has not been initialized yet. + /// When this function returns, `self.data` is guaranteed to be + /// initialized and visible on the current core. + fn ensure_init(&self) { + critical_section::with(|_| { + if !self.init.load(Ordering::Acquire) { + let data = unsafe { &mut *self.data.get() }; + let f = unsafe { ManuallyDrop::take(&mut data.f) }; + let value = f(); + data.value = ManuallyDrop::new(value); + + self.init.store(true, Ordering::Release); + } + }); + } +} + +impl Drop for LazyLock { + fn drop(&mut self) { + if self.init.load(Ordering::Acquire) { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().value) }; + } else { + unsafe { ManuallyDrop::drop(&mut self.data.get_mut().f) }; + } + } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicU32, Ordering}; + + use super::*; + + #[test] + fn test_lazy_lock() { + static VALUE: LazyLock = LazyLock::new(|| 20); + let reference = VALUE.get(); + assert_eq!(reference, &20); + } + #[test] + fn test_lazy_lock_into_inner() { + let lazy: LazyLock = LazyLock::new(|| 20); + let value = lazy.into_inner(); + assert_eq!(value, 20); + } + + static DROP_CHECKER: AtomicU32 = AtomicU32::new(0); + struct DropCheck; + + impl Drop for DropCheck { + fn drop(&mut self) { + DROP_CHECKER.fetch_add(1, Ordering::Acquire); + } + } + + #[test] + fn test_lazy_drop() { + let lazy: LazyLock = LazyLock::new(|| DropCheck); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 0); + lazy.get(); + drop(lazy); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + + let dropper = DropCheck; + let lazy_fn: LazyLock = LazyLock::new(move || { + let _a = dropper; + 20 + }); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 1); + drop(lazy_fn); + assert_eq!(DROP_CHECKER.load(Ordering::Acquire), 2); + } +} diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs new file mode 100644 index 0000000..df0f5e8 --- /dev/null +++ b/embassy-sync/src/lib.rs @@ -0,0 +1,25 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(async_fn_in_trait)] +#![allow(clippy::new_without_default)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +// internal use +mod ring_buffer; + +pub mod blocking_mutex; +pub mod channel; +pub mod lazy_lock; +pub mod mutex; +pub mod once_lock; +pub mod pipe; +pub mod priority_channel; +pub mod pubsub; +pub mod semaphore; +pub mod signal; +pub mod waitqueue; +pub mod watch; +pub mod zerocopy_channel; diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs new file mode 100644 index 0000000..7528a9f --- /dev/null +++ b/embassy-sync/src/mutex.rs @@ -0,0 +1,391 @@ +//! Async mutex. +//! +//! This module provides a mutex that can be used to synchronize data between asynchronous tasks. +use core::cell::{RefCell, UnsafeCell}; +use core::future::{poll_fn, Future}; +use core::ops::{Deref, DerefMut}; +use core::task::Poll; +use core::{fmt, mem}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex as BlockingMutex; +use crate::waitqueue::WakerRegistration; + +/// Error returned by [`Mutex::try_lock`] +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TryLockError; + +struct State { + locked: bool, + waker: WakerRegistration, +} + +/// Async mutex. +/// +/// The mutex is generic over a blocking [`RawMutex`](crate::blocking_mutex::raw::RawMutex). +/// The raw mutex is used to guard access to the internal "is locked" flag. It +/// is held for very short periods only, while locking and unlocking. It is *not* held +/// for the entire time the async Mutex is locked. +/// +/// Which implementation you select depends on the context in which you're using the mutex. +/// +/// Use [`CriticalSectionRawMutex`](crate::blocking_mutex::raw::CriticalSectionRawMutex) when data can be shared between threads and interrupts. +/// +/// Use [`NoopRawMutex`](crate::blocking_mutex::raw::NoopRawMutex) when data is only shared between tasks running on the same executor. +/// +/// Use [`ThreadModeRawMutex`](crate::blocking_mutex::raw::ThreadModeRawMutex) when data is shared between tasks running on the same executor but you want a singleton. +/// +pub struct Mutex +where + M: RawMutex, + T: ?Sized, +{ + state: BlockingMutex>, + inner: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +/// Async mutex. +impl Mutex +where + M: RawMutex, +{ + /// Create a new mutex with the given value. + pub const fn new(value: T) -> Self { + Self { + inner: UnsafeCell::new(value), + state: BlockingMutex::new(RefCell::new(State { + locked: false, + waker: WakerRegistration::new(), + })), + } + } +} + +impl Mutex +where + M: RawMutex, + T: ?Sized, +{ + /// Lock the mutex. + /// + /// This will wait for the mutex to be unlocked if it's already locked. + pub fn lock(&self) -> impl Future> { + poll_fn(|cx| { + let ready = self.state.lock(|s| { + let mut s = s.borrow_mut(); + if s.locked { + s.waker.register(cx.waker()); + false + } else { + s.locked = true; + true + } + }); + + if ready { + Poll::Ready(MutexGuard { mutex: self }) + } else { + Poll::Pending + } + }) + } + + /// Attempt to immediately lock the mutex. + /// + /// If the mutex is already locked, this will return an error instead of waiting. + pub fn try_lock(&self) -> Result, TryLockError> { + self.state.lock(|s| { + let mut s = s.borrow_mut(); + if s.locked { + Err(TryLockError) + } else { + s.locked = true; + Ok(()) + } + })?; + + Ok(MutexGuard { mutex: self }) + } + + /// Consumes this mutex, returning the underlying data. + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the Mutex mutably, no actual locking needs to + /// take place -- the mutable borrow statically guarantees no locks exist. + pub fn get_mut(&mut self) -> &mut T { + self.inner.get_mut() + } +} + +impl From for Mutex { + fn from(from: T) -> Self { + Self::new(from) + } +} + +impl Default for Mutex +where + M: RawMutex, + T: Default, +{ + fn default() -> Self { + Self::new(Default::default()) + } +} + +impl fmt::Debug for Mutex +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut d = f.debug_struct("Mutex"); + match self.try_lock() { + Ok(value) => { + d.field("inner", &&*value); + } + Err(TryLockError) => { + d.field("inner", &format_args!("")); + } + } + + d.finish_non_exhaustive() + } +} + +/// Async mutex guard. +/// +/// Owning an instance of this type indicates having +/// successfully locked the mutex, and grants access to the contents. +/// +/// Dropping it unlocks the mutex. +#[clippy::has_significant_drop] +#[must_use = "if unused the Mutex will immediately unlock"] +pub struct MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + mutex: &'a Mutex, +} + +impl<'a, M, T> MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> { + let mutex = this.mutex; + let value = fun(unsafe { &mut *this.mutex.inner.get() }); + // Don't run the `drop` method for MutexGuard. The ownership of the underlying + // locked state is being moved to the returned MappedMutexGuard. + mem::forget(this); + MappedMutexGuard { + state: &mutex.state, + value, + } + } +} + +impl<'a, M, T> Drop for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.mutex.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.locked = false; + s.waker.wake(); + }) + } +} + +impl<'a, M, T> Deref for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &*(self.mutex.inner.get() as *const T) } + } +} + +impl<'a, M, T> DerefMut for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &mut *(self.mutex.inner.get()) } + } +} + +impl<'a, M, T> fmt::Debug for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, M, T> fmt::Display for MutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +/// A handle to a held `Mutex` that has had a function applied to it via [`MutexGuard::map`] or +/// [`MappedMutexGuard::map`]. +/// +/// This can be used to hold a subfield of the protected data. +#[clippy::has_significant_drop] +pub struct MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + state: &'a BlockingMutex>, + value: *mut T, +} + +impl<'a, M, T> MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + /// Returns a locked view over a portion of the locked data. + pub fn map(this: Self, fun: impl FnOnce(&mut T) -> &mut U) -> MappedMutexGuard<'a, M, U> { + let state = this.state; + let value = fun(unsafe { &mut *this.value }); + // Don't run the `drop` method for MutexGuard. The ownership of the underlying + // locked state is being moved to the returned MappedMutexGuard. + mem::forget(this); + MappedMutexGuard { state, value } + } +} + +impl<'a, M, T> Deref for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + type Target = T; + fn deref(&self) -> &Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &*self.value } + } +} + +impl<'a, M, T> DerefMut for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn deref_mut(&mut self) -> &mut Self::Target { + // Safety: the MutexGuard represents exclusive access to the contents + // of the mutex, so it's OK to get it. + unsafe { &mut *self.value } + } +} + +impl<'a, M, T> Drop for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized, +{ + fn drop(&mut self) { + self.state.lock(|s| { + let mut s = unwrap!(s.try_borrow_mut()); + s.locked = false; + s.waker.wake(); + }) + } +} + +unsafe impl Send for MappedMutexGuard<'_, M, T> +where + M: RawMutex + Sync, + T: Send + ?Sized, +{ +} + +unsafe impl Sync for MappedMutexGuard<'_, M, T> +where + M: RawMutex + Sync, + T: Sync + ?Sized, +{ +} + +impl<'a, M, T> fmt::Debug for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl<'a, M, T> fmt::Display for MappedMutexGuard<'a, M, T> +where + M: RawMutex, + T: ?Sized + fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +#[cfg(test)] +mod tests { + use crate::blocking_mutex::raw::NoopRawMutex; + use crate::mutex::{Mutex, MutexGuard}; + + #[futures_test::test] + async fn mapped_guard_releases_lock_when_dropped() { + let mutex: Mutex = Mutex::new([0, 1]); + + { + let guard = mutex.lock().await; + assert_eq!(*guard, [0, 1]); + let mut mapped = MutexGuard::map(guard, |this| &mut this[1]); + assert_eq!(*mapped, 1); + *mapped = 2; + } + + { + let guard = mutex.lock().await; + assert_eq!(*guard, [0, 2]); + let mut mapped = MutexGuard::map(guard, |this| &mut this[1]); + assert_eq!(*mapped, 2); + *mapped = 3; + } + + assert_eq!(*mutex.lock().await, [0, 3]); + } +} diff --git a/embassy-sync/src/once_lock.rs b/embassy-sync/src/once_lock.rs new file mode 100644 index 0000000..cd05b98 --- /dev/null +++ b/embassy-sync/src/once_lock.rs @@ -0,0 +1,235 @@ +//! Synchronization primitive for initializing a value once, allowing others to await a reference to the value. + +use core::cell::Cell; +use core::future::{poll_fn, Future}; +use core::mem::MaybeUninit; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +/// The `OnceLock` is a synchronization primitive that allows for +/// initializing a value once, and allowing others to `.await` a +/// reference to the value. This is useful for lazy initialization of +/// a static value. +/// +/// **Note**: this implementation uses a busy loop to poll the value, +/// which is not as efficient as registering a dedicated `Waker`. +/// However, if the usecase for it is to initialize a static variable +/// relatively early in the program life cycle, it should be fine. +/// +/// # Example +/// ``` +/// use futures_executor::block_on; +/// use embassy_sync::once_lock::OnceLock; +/// +/// // Define a static value that will be lazily initialized +/// static VALUE: OnceLock = OnceLock::new(); +/// +/// let f = async { +/// +/// // Initialize the value +/// let reference = VALUE.get_or_init(|| 20); +/// assert_eq!(reference, &20); +/// +/// // Wait for the value to be initialized +/// // and get a static reference it +/// assert_eq!(VALUE.get().await, &20); +/// +/// }; +/// block_on(f) +/// ``` +pub struct OnceLock { + init: AtomicBool, + data: Cell>, +} + +unsafe impl Sync for OnceLock {} + +impl OnceLock { + /// Create a new uninitialized `OnceLock`. + pub const fn new() -> Self { + Self { + init: AtomicBool::new(false), + data: Cell::new(MaybeUninit::zeroed()), + } + } + + /// Get a reference to the underlying value, waiting for it to be set. + /// If the value is already set, this will return immediately. + pub fn get(&self) -> impl Future { + poll_fn(|cx| match self.try_get() { + Some(data) => Poll::Ready(data), + None => { + cx.waker().wake_by_ref(); + Poll::Pending + } + }) + } + + /// Try to get a reference to the underlying value if it exists. + pub fn try_get(&self) -> Option<&T> { + if self.init.load(Ordering::Relaxed) { + Some(unsafe { self.get_ref_unchecked() }) + } else { + None + } + } + + /// Set the underlying value. If the value is already set, this will return an error with the given value. + pub fn init(&self, value: T) -> Result<(), T> { + // Critical section is required to ensure that the value is + // not simultaneously initialized elsewhere at the same time. + critical_section::with(|_| { + // If the value is not set, set it and return Ok. + if !self.init.load(Ordering::Relaxed) { + self.data.set(MaybeUninit::new(value)); + self.init.store(true, Ordering::Relaxed); + Ok(()) + + // Otherwise return an error with the given value. + } else { + Err(value) + } + }) + } + + /// Get a reference to the underlying value, initializing it if it does not exist. + pub fn get_or_init(&self, f: F) -> &T + where + F: FnOnce() -> T, + { + // Critical section is required to ensure that the value is + // not simultaneously initialized elsewhere at the same time. + critical_section::with(|_| { + // If the value is not set, set it. + if !self.init.load(Ordering::Relaxed) { + self.data.set(MaybeUninit::new(f())); + self.init.store(true, Ordering::Relaxed); + } + }); + + // Return a reference to the value. + unsafe { self.get_ref_unchecked() } + } + + /// Consume the `OnceLock`, returning the underlying value if it was initialized. + pub fn into_inner(self) -> Option { + if self.init.load(Ordering::Relaxed) { + Some(unsafe { self.data.into_inner().assume_init() }) + } else { + None + } + } + + /// Take the underlying value if it was initialized, uninitializing the `OnceLock` in the process. + pub fn take(&mut self) -> Option { + // If the value is set, uninitialize the lock and return the value. + critical_section::with(|_| { + if self.init.load(Ordering::Relaxed) { + let val = unsafe { self.data.replace(MaybeUninit::zeroed()).assume_init() }; + self.init.store(false, Ordering::Relaxed); + Some(val) + + // Otherwise return None. + } else { + None + } + }) + } + + /// Check if the value has been set. + pub fn is_set(&self) -> bool { + self.init.load(Ordering::Relaxed) + } + + /// Get a reference to the underlying value. + /// # Safety + /// Must only be used if a value has been set. + unsafe fn get_ref_unchecked(&self) -> &T { + (*self.data.as_ptr()).assume_init_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn once_lock() { + let lock = OnceLock::new(); + assert_eq!(lock.try_get(), None); + assert_eq!(lock.is_set(), false); + + let v = 42; + assert_eq!(lock.init(v), Ok(())); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&v)); + assert_eq!(lock.try_get(), Some(&v)); + + let v = 43; + assert_eq!(lock.init(v), Err(v)); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&42)); + } + + #[test] + fn once_lock_get_or_init() { + let lock = OnceLock::new(); + assert_eq!(lock.try_get(), None); + assert_eq!(lock.is_set(), false); + + let v = lock.get_or_init(|| 42); + assert_eq!(v, &42); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&42)); + + let v = lock.get_or_init(|| 43); + assert_eq!(v, &42); + assert_eq!(lock.is_set(), true); + assert_eq!(lock.try_get(), Some(&42)); + } + + #[test] + fn once_lock_static() { + static LOCK: OnceLock = OnceLock::new(); + + let v: &'static i32 = LOCK.get_or_init(|| 42); + assert_eq!(v, &42); + + let v: &'static i32 = LOCK.get_or_init(|| 43); + assert_eq!(v, &42); + } + + #[futures_test::test] + async fn once_lock_async() { + static LOCK: OnceLock = OnceLock::new(); + + assert!(LOCK.init(42).is_ok()); + + let v: &'static i32 = LOCK.get().await; + assert_eq!(v, &42); + } + + #[test] + fn once_lock_into_inner() { + let lock: OnceLock = OnceLock::new(); + + let v = lock.get_or_init(|| 42); + assert_eq!(v, &42); + + assert_eq!(lock.into_inner(), Some(42)); + } + + #[test] + fn once_lock_take_init() { + let mut lock: OnceLock = OnceLock::new(); + + assert_eq!(lock.get_or_init(|| 42), &42); + assert_eq!(lock.is_set(), true); + + assert_eq!(lock.take(), Some(42)); + assert_eq!(lock.is_set(), false); + + assert_eq!(lock.get_or_init(|| 43), &43); + assert_eq!(lock.is_set(), true); + } +} diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs new file mode 100644 index 0000000..2598652 --- /dev/null +++ b/embassy-sync/src/pipe.rs @@ -0,0 +1,919 @@ +//! Async byte stream pipe. + +use core::cell::{RefCell, UnsafeCell}; +use core::convert::Infallible; +use core::future::Future; +use core::ops::Range; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::ring_buffer::RingBuffer; +use crate::waitqueue::WakerRegistration; + +/// Write-only access to a [`Pipe`]. +pub struct Writer<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, +} + +impl<'p, M, const N: usize> Clone for Writer<'p, M, N> +where + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'p, M, const N: usize> Copy for Writer<'p, M, N> where M: RawMutex {} + +impl<'p, M, const N: usize> Writer<'p, M, N> +where + M: RawMutex, +{ + /// Write some bytes to the pipe. + /// + /// See [`Pipe::write()`] + pub fn write<'a>(&'a self, buf: &'a [u8]) -> WriteFuture<'a, M, N> { + self.pipe.write(buf) + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// See [`Pipe::try_write()`] + pub fn try_write(&self, buf: &[u8]) -> Result { + self.pipe.try_write(buf) + } +} + +/// Future returned by [`Pipe::write`] and [`Writer::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct WriteFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, + buf: &'p [u8], +} + +impl<'p, M, const N: usize> Future for WriteFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_write_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryWriteError::Full) => Poll::Pending, + } + } +} + +impl<'p, M, const N: usize> Unpin for WriteFuture<'p, M, N> where M: RawMutex {} + +/// Read-only access to a [`Pipe`]. +pub struct Reader<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, +} + +impl<'p, M, const N: usize> Reader<'p, M, N> +where + M: RawMutex, +{ + /// Read some bytes from the pipe. + /// + /// See [`Pipe::read()`] + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> ReadFuture<'a, M, N> { + self.pipe.read(buf) + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// See [`Pipe::try_read()`] + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.pipe.try_read(buf) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn fill_buf(&mut self) -> FillBufFuture<'_, M, N> { + FillBufFuture { pipe: Some(self.pipe) } + } + + /// Try returning contents of the internal buffer. + /// + /// If no bytes are currently available to read, this function returns `Err(TryReadError::Empty)`. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn try_fill_buf(&mut self) -> Result<&[u8], TryReadError> { + unsafe { self.pipe.try_fill_buf_with_context(None) } + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.pipe.consume(amt) + } +} + +/// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReadFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: &'p Pipe, + buf: &'p mut [u8], +} + +impl<'p, M, const N: usize> Future for ReadFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = usize; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_read_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryReadError::Empty) => Poll::Pending, + } + } +} + +impl<'p, M, const N: usize> Unpin for ReadFuture<'p, M, N> where M: RawMutex {} + +/// Future returned by [`Pipe::fill_buf`] and [`Reader::fill_buf`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FillBufFuture<'p, M, const N: usize> +where + M: RawMutex, +{ + pipe: Option<&'p Pipe>, +} + +impl<'p, M, const N: usize> Future for FillBufFuture<'p, M, N> +where + M: RawMutex, +{ + type Output = &'p [u8]; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pipe = self.pipe.take().unwrap(); + match unsafe { pipe.try_fill_buf_with_context(Some(cx)) } { + Ok(buf) => Poll::Ready(buf), + Err(TryReadError::Empty) => { + self.pipe = Some(pipe); + Poll::Pending + } + } + } +} + +impl<'p, M, const N: usize> Unpin for FillBufFuture<'p, M, N> where M: RawMutex {} + +/// Error returned by [`try_read`](Pipe::try_read). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryReadError { + /// No data could be read from the pipe because it is currently + /// empty, and reading would require blocking. + Empty, +} + +/// Error returned by [`try_write`](Pipe::try_write). +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TryWriteError { + /// No data could be written to the pipe because it is + /// currently full, and writing would require blocking. + Full, +} + +struct PipeState { + buffer: RingBuffer, + read_waker: WakerRegistration, + write_waker: WakerRegistration, +} + +#[repr(transparent)] +struct Buffer(UnsafeCell<[u8; N]>); + +impl Buffer { + unsafe fn get<'a>(&self, r: Range) -> &'a [u8] { + let p = self.0.get() as *const u8; + core::slice::from_raw_parts(p.add(r.start), r.end - r.start) + } + + unsafe fn get_mut<'a>(&self, r: Range) -> &'a mut [u8] { + let p = self.0.get() as *mut u8; + core::slice::from_raw_parts_mut(p.add(r.start), r.end - r.start) + } +} + +unsafe impl Send for Buffer {} +unsafe impl Sync for Buffer {} + +/// A bounded byte-oriented pipe for communicating between asynchronous tasks +/// with backpressure. +/// +/// The pipe will buffer up to the provided number of bytes. Once the +/// buffer is full, attempts to `write` new bytes will wait until buffer space is freed up. +/// +/// All data written will become available in the same order as it was written. +pub struct Pipe +where + M: RawMutex, +{ + buf: Buffer, + inner: Mutex>>, +} + +impl Pipe +where + M: RawMutex, +{ + /// Establish a new bounded pipe. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy_sync::pipe::Pipe; + /// use embassy_sync::blocking_mutex::raw::NoopRawMutex; + /// + /// // Declare a bounded pipe, with a buffer of 256 bytes. + /// let mut pipe = Pipe::::new(); + /// ``` + pub const fn new() -> Self { + Self { + buf: Buffer(UnsafeCell::new([0; N])), + inner: Mutex::new(RefCell::new(PipeState { + buffer: RingBuffer::new(), + read_waker: WakerRegistration::new(), + write_waker: WakerRegistration::new(), + })), + } + } + + fn lock(&self, f: impl FnOnce(&mut PipeState) -> R) -> R { + self.inner.lock(|rc| f(&mut *rc.borrow_mut())) + } + + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_full() { + s.write_waker.wake(); + } + + let available = unsafe { self.buf.get(s.buffer.pop_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.read_waker.register(cx.waker()); + } + return Err(TryReadError::Empty); + } + + let n = available.len().min(buf.len()); + buf[..n].copy_from_slice(&available[..n]); + s.buffer.pop(n); + Ok(n) + }) + } + + // safety: While the returned slice is alive, + // no `read` or `consume` methods in the pipe must be called. + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError> { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_full() { + s.write_waker.wake(); + } + + let available = unsafe { self.buf.get(s.buffer.pop_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.read_waker.register(cx.waker()); + } + return Err(TryReadError::Empty); + } + + Ok(available) + }) + } + + fn consume(&self, amt: usize) { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + let available = s.buffer.pop_buf(); + assert!(amt <= available.len()); + s.buffer.pop(amt); + }) + } + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + if s.buffer.is_empty() { + s.read_waker.wake(); + } + + let available = unsafe { self.buf.get_mut(s.buffer.push_buf()) }; + if available.is_empty() { + if let Some(cx) = cx { + s.write_waker.register(cx.waker()); + } + return Err(TryWriteError::Full); + } + + let n = available.len().min(buf.len()); + available[..n].copy_from_slice(&buf[..n]); + s.buffer.push(n); + Ok(n) + }) + } + + /// Split this pipe into a BufRead-capable reader and a writer. + /// + /// The reader and writer borrow the current pipe mutably, so it is not + /// possible to use it directly while they exist. This is needed because + /// implementing `BufRead` requires there is a single reader. + /// + /// The writer is cloneable, the reader is not. + pub fn split(&mut self) -> (Reader<'_, M, N>, Writer<'_, M, N>) { + (Reader { pipe: self }, Writer { pipe: self }) + } + + /// Write some bytes to the pipe. + /// + /// This method writes a nonzero amount of bytes from `buf` into the pipe, and + /// returns the amount of bytes written. + /// + /// If it is not possible to write a nonzero amount of bytes because the pipe's buffer is full, + /// this method will wait until it isn't. See [`try_write`](Self::try_write) for a variant that + /// returns an error instead of waiting. + /// + /// It is not guaranteed that all bytes in the buffer are written, even if there's enough + /// free space in the pipe buffer for all. In other words, it is possible for `write` to return + /// without writing all of `buf` (returning a number less than `buf.len()`) and still leave + /// free space in the pipe buffer. You should always `write` in a loop, or use helpers like + /// `write_all` from the `embedded-io` crate. + pub fn write<'a>(&'a self, buf: &'a [u8]) -> WriteFuture<'a, M, N> { + WriteFuture { pipe: self, buf } + } + + /// Write all bytes to the pipe. + /// + /// This method writes all bytes from `buf` into the pipe + pub async fn write_all(&self, mut buf: &[u8]) { + while !buf.is_empty() { + let n = self.write(buf).await; + buf = &buf[n..]; + } + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// This method will either write a nonzero amount of bytes to the pipe immediately, + /// or return an error if the pipe is empty. See [`write`](Self::write) for a variant + /// that waits instead of returning an error. + pub fn try_write(&self, buf: &[u8]) -> Result { + self.try_write_with_context(None, buf) + } + + /// Read some bytes from the pipe. + /// + /// This method reads a nonzero amount of bytes from the pipe into `buf` and + /// returns the amount of bytes read. + /// + /// If it is not possible to read a nonzero amount of bytes because the pipe's buffer is empty, + /// this method will wait until it isn't. See [`try_read`](Self::try_read) for a variant that + /// returns an error instead of waiting. + /// + /// It is not guaranteed that all bytes in the buffer are read, even if there's enough + /// space in `buf` for all. In other words, it is possible for `read` to return + /// without filling `buf` (returning a number less than `buf.len()`) and still leave bytes + /// in the pipe buffer. You should always `read` in a loop, or use helpers like + /// `read_exact` from the `embedded-io` crate. + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> ReadFuture<'a, M, N> { + ReadFuture { pipe: self, buf } + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// This method will either read a nonzero amount of bytes from the pipe immediately, + /// or return an error if the pipe is empty. See [`read`](Self::read) for a variant + /// that waits instead of returning an error. + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.try_read_with_context(None, buf) + } + + /// Clear the data in the pipe's buffer. + pub fn clear(&self) { + self.inner.lock(|rc: &RefCell>| { + let s = &mut *rc.borrow_mut(); + + s.buffer.clear(); + s.write_waker.wake(); + }) + } + + /// Return whether the pipe is full (no free space in the buffer) + pub fn is_full(&self) -> bool { + self.len() == N + } + + /// Return whether the pipe is empty (no data buffered) + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Total byte capacity. + /// + /// This is the same as the `N` generic param. + pub fn capacity(&self) -> usize { + N + } + + /// Used byte capacity. + pub fn len(&self) -> usize { + self.lock(|c| c.buffer.len()) + } + + /// Free byte capacity. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + N - self.len() + } +} + +impl embedded_io_async::ErrorType for Pipe { + type Error = Infallible; +} + +impl embedded_io_async::Read for Pipe { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) + } +} + +impl embedded_io_async::Write for Pipe { + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl embedded_io_async::ErrorType for &Pipe { + type Error = Infallible; +} + +impl embedded_io_async::Read for &Pipe { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) + } +} + +impl embedded_io_async::Write for &Pipe { + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl embedded_io_async::ErrorType for Reader<'_, M, N> { + type Error = Infallible; +} + +impl embedded_io_async::Read for Reader<'_, M, N> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Reader::read(self, buf).await) + } +} + +impl embedded_io_async::BufRead for Reader<'_, M, N> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Ok(Reader::fill_buf(self).await) + } + + fn consume(&mut self, amt: usize) { + Reader::consume(self, amt) + } +} + +impl embedded_io_async::ErrorType for Writer<'_, M, N> { + type Error = Infallible; +} + +impl embedded_io_async::Write for Writer<'_, M, N> { + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Writer::write(self, buf).await) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } +} + +// +// Type-erased variants +// + +pub(crate) trait DynamicPipe { + fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a>; + fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a>; + + fn try_read(&self, buf: &mut [u8]) -> Result; + fn try_write(&self, buf: &[u8]) -> Result; + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result; + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result; + + fn consume(&self, amt: usize); + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError>; +} + +impl DynamicPipe for Pipe +where + M: RawMutex, +{ + fn consume(&self, amt: usize) { + Pipe::consume(self, amt) + } + + unsafe fn try_fill_buf_with_context(&self, cx: Option<&mut Context<'_>>) -> Result<&[u8], TryReadError> { + Pipe::try_fill_buf_with_context(self, cx) + } + + fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a> { + Pipe::write(self, buf).into() + } + + fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a> { + Pipe::read(self, buf).into() + } + + fn try_read(&self, buf: &mut [u8]) -> Result { + Pipe::try_read(self, buf) + } + + fn try_write(&self, buf: &[u8]) -> Result { + Pipe::try_write(self, buf) + } + + fn try_write_with_context(&self, cx: Option<&mut Context<'_>>, buf: &[u8]) -> Result { + Pipe::try_write_with_context(self, cx, buf) + } + + fn try_read_with_context(&self, cx: Option<&mut Context<'_>>, buf: &mut [u8]) -> Result { + Pipe::try_read_with_context(self, cx, buf) + } +} + +/// Write-only access to a [`DynamicPipe`]. +pub struct DynamicWriter<'p> { + pipe: &'p dyn DynamicPipe, +} + +impl<'p> Clone for DynamicWriter<'p> { + fn clone(&self) -> Self { + *self + } +} + +impl<'p> Copy for DynamicWriter<'p> {} + +impl<'p> DynamicWriter<'p> { + /// Write some bytes to the pipe. + /// + /// See [`Pipe::write()`] + pub fn write<'a>(&'a self, buf: &'a [u8]) -> DynamicWriteFuture<'a> { + self.pipe.write(buf) + } + + /// Attempt to immediately write some bytes to the pipe. + /// + /// See [`Pipe::try_write()`] + pub fn try_write(&self, buf: &[u8]) -> Result { + self.pipe.try_write(buf) + } +} + +impl<'p, M, const N: usize> From> for DynamicWriter<'p> +where + M: RawMutex, +{ + fn from(value: Writer<'p, M, N>) -> Self { + Self { pipe: value.pipe } + } +} + +/// Future returned by [`DynamicWriter::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicWriteFuture<'p> { + pipe: &'p dyn DynamicPipe, + buf: &'p [u8], +} + +impl<'p> Future for DynamicWriteFuture<'p> { + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_write_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryWriteError::Full) => Poll::Pending, + } + } +} + +impl<'p> Unpin for DynamicWriteFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicWriteFuture<'p> +where + M: RawMutex, +{ + fn from(value: WriteFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe, + buf: value.buf, + } + } +} + +/// Read-only access to a [`DynamicPipe`]. +pub struct DynamicReader<'p> { + pipe: &'p dyn DynamicPipe, +} + +impl<'p> DynamicReader<'p> { + /// Read some bytes from the pipe. + /// + /// See [`Pipe::read()`] + pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> DynamicReadFuture<'a> { + self.pipe.read(buf) + } + + /// Attempt to immediately read some bytes from the pipe. + /// + /// See [`Pipe::try_read()`] + pub fn try_read(&self, buf: &mut [u8]) -> Result { + self.pipe.try_read(buf) + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + /// + /// If no bytes are currently available to read, this function waits until at least one byte is available. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn fill_buf(&mut self) -> DynamicFillBufFuture<'_> { + DynamicFillBufFuture { pipe: Some(self.pipe) } + } + + /// Try returning contents of the internal buffer. + /// + /// If no bytes are currently available to read, this function returns `Err(TryReadError::Empty)`. + /// + /// If the reader is at end-of-file (EOF), an empty slice is returned. + pub fn try_fill_buf(&mut self) -> Result<&[u8], TryReadError> { + unsafe { self.pipe.try_fill_buf_with_context(None) } + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.pipe.consume(amt) + } +} + +impl<'p, M, const N: usize> From> for DynamicReader<'p> +where + M: RawMutex, +{ + fn from(value: Reader<'p, M, N>) -> Self { + Self { pipe: value.pipe } + } +} + +/// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicReadFuture<'p> { + pipe: &'p dyn DynamicPipe, + buf: &'p mut [u8], +} + +impl<'p> Future for DynamicReadFuture<'p> { + type Output = usize; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.pipe.try_read_with_context(Some(cx), self.buf) { + Ok(n) => Poll::Ready(n), + Err(TryReadError::Empty) => Poll::Pending, + } + } +} + +impl<'p> Unpin for DynamicReadFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicReadFuture<'p> +where + M: RawMutex, +{ + fn from(value: ReadFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe, + buf: value.buf, + } + } +} + +/// Future returned by [`DynamicPipe::fill_buf`] and [`DynamicReader::fill_buf`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct DynamicFillBufFuture<'p> { + pipe: Option<&'p dyn DynamicPipe>, +} + +impl<'p> Future for DynamicFillBufFuture<'p> { + type Output = &'p [u8]; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pipe = self.pipe.take().unwrap(); + match unsafe { pipe.try_fill_buf_with_context(Some(cx)) } { + Ok(buf) => Poll::Ready(buf), + Err(TryReadError::Empty) => { + self.pipe = Some(pipe); + Poll::Pending + } + } + } +} + +impl<'p> Unpin for DynamicFillBufFuture<'p> {} + +impl<'p, M, const N: usize> From> for DynamicFillBufFuture<'p> +where + M: RawMutex, +{ + fn from(value: FillBufFuture<'p, M, N>) -> Self { + Self { + pipe: value.pipe.map(|p| p as &dyn DynamicPipe), + } + } +} + +#[cfg(test)] +mod tests { + use futures_executor::ThreadPool; + use futures_util::task::SpawnExt; + use static_cell::StaticCell; + + use super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + #[test] + fn writing_once() { + let c = Pipe::::new(); + assert!(c.try_write(&[1]).is_ok()); + assert_eq!(c.free_capacity(), 2); + } + + #[test] + fn writing_when_full() { + let c = Pipe::::new(); + assert_eq!(c.try_write(&[42]), Ok(1)); + assert_eq!(c.try_write(&[43]), Ok(1)); + assert_eq!(c.try_write(&[44]), Ok(1)); + assert_eq!(c.try_write(&[45]), Err(TryWriteError::Full)); + assert_eq!(c.free_capacity(), 0); + } + + #[test] + fn receiving_once_with_one_send() { + let c = Pipe::::new(); + assert!(c.try_write(&[42]).is_ok()); + let mut buf = [0; 16]; + assert_eq!(c.try_read(&mut buf), Ok(1)); + assert_eq!(buf[0], 42); + assert_eq!(c.free_capacity(), 3); + } + + #[test] + fn receiving_when_empty() { + let c = Pipe::::new(); + let mut buf = [0; 16]; + assert_eq!(c.try_read(&mut buf), Err(TryReadError::Empty)); + assert_eq!(c.free_capacity(), 3); + } + + #[test] + fn simple_send_and_receive() { + let c = Pipe::::new(); + assert!(c.try_write(&[42]).is_ok()); + let mut buf = [0; 16]; + assert_eq!(c.try_read(&mut buf), Ok(1)); + assert_eq!(buf[0], 42); + } + + #[test] + fn read_buf() { + let mut c = Pipe::::new(); + let (mut r, w) = c.split(); + assert!(w.try_write(&[42, 43]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[43]); + r.consume(1); + assert_eq!(r.try_fill_buf(), Err(TryReadError::Empty)); + assert_eq!(w.try_write(&[44, 45, 46]), Ok(1)); + assert_eq!(w.try_write(&[45, 46]), Ok(2)); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[44]); // only one byte due to wraparound. + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46]); + assert!(w.try_write(&[47]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46, 47]); + r.consume(3); + } + + #[test] + fn writer_is_cloneable() { + let mut c = Pipe::::new(); + let (_r, w) = c.split(); + let _ = w.clone(); + } + + #[test] + fn dynamic_dispatch_pipe() { + let mut c = Pipe::::new(); + let (r, w) = c.split(); + let (mut r, w): (DynamicReader<'_>, DynamicWriter<'_>) = (r.into(), w.into()); + + assert!(w.try_write(&[42, 43]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[42, 43]); + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[43]); + r.consume(1); + assert_eq!(r.try_fill_buf(), Err(TryReadError::Empty)); + assert_eq!(w.try_write(&[44, 45, 46]), Ok(1)); + assert_eq!(w.try_write(&[45, 46]), Ok(2)); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[44]); // only one byte due to wraparound. + r.consume(1); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46]); + assert!(w.try_write(&[47]).is_ok()); + let buf = r.try_fill_buf().unwrap(); + assert_eq!(buf, &[45, 46, 47]); + r.consume(3); + } + + #[futures_test::test] + async fn receiver_receives_given_try_write_async() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(Pipe::new()); + let c2 = c; + let f = async move { + assert_eq!(c2.try_write(&[42]), Ok(1)); + }; + executor.spawn(f).unwrap(); + let mut buf = [0; 16]; + assert_eq!(c.read(&mut buf).await, 1); + assert_eq!(buf[0], 42); + } + + #[futures_test::test] + async fn sender_send_completes_if_capacity() { + let c = Pipe::::new(); + c.write(&[42]).await; + let mut buf = [0; 16]; + assert_eq!(c.read(&mut buf).await, 1); + assert_eq!(buf[0], 42); + } +} diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs new file mode 100644 index 0000000..3695920 --- /dev/null +++ b/embassy-sync/src/priority_channel.rs @@ -0,0 +1,779 @@ +//! A queue for sending values between asynchronous tasks. +//! +//! Similar to a [`Channel`](crate::channel::Channel), however [`PriorityChannel`] sifts higher priority items to the front of the queue. +//! Priority is determined by the `Ord` trait. Priority behavior is determined by the [`Kind`](heapless::binary_heap::Kind) parameter of the channel. + +use core::cell::RefCell; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +pub use heapless::binary_heap::{Kind, Max, Min}; +use heapless::BinaryHeap; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::channel::{DynamicChannel, DynamicReceiver, DynamicSender, TryReceiveError, TrySendError}; +use crate::waitqueue::WakerRegistration; + +/// Send-only access to a [`PriorityChannel`]. +pub struct Sender<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, +} + +impl<'ch, M, T, K, const N: usize> Clone for Sender<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, K, const N: usize> Copy for Sender<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ +} + +impl<'ch, M, T, K, const N: usize> Sender<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + /// Sends a value. + /// + /// See [`PriorityChannel::send()`] + pub fn send(&self, message: T) -> SendFuture<'ch, M, T, K, N> { + self.channel.send(message) + } + + /// Attempt to immediately send a message. + /// + /// See [`PriorityChannel::send()`] + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.channel.try_send(message) + } + + /// Allows a poll_fn to poll until the channel is ready to send + /// + /// See [`PriorityChannel::poll_ready_to_send()`] + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_send(cx) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +impl<'ch, M, T, K, const N: usize> From> for DynamicSender<'ch, T> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn from(s: Sender<'ch, M, T, K, N>) -> Self { + Self { channel: s.channel } + } +} + +/// Receive-only access to a [`PriorityChannel`]. +pub struct Receiver<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, +} + +impl<'ch, M, T, K, const N: usize> Clone for Receiver<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn clone(&self) -> Self { + *self + } +} + +impl<'ch, M, T, K, const N: usize> Copy for Receiver<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ +} + +impl<'ch, M, T, K, const N: usize> Receiver<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + /// Receive the next value. + /// + /// See [`PriorityChannel::receive()`]. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, K, N> { + self.channel.receive() + } + + /// Attempt to immediately receive the next value. + /// + /// See [`PriorityChannel::try_receive()`] + pub fn try_receive(&self) -> Result { + self.channel.try_receive() + } + + /// Allows a poll_fn to poll until the channel is ready to receive + /// + /// See [`PriorityChannel::poll_ready_to_receive()`] + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.channel.poll_ready_to_receive(cx) + } + + /// Poll the channel for the next item + /// + /// See [`PriorityChannel::poll_receive()`] + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } + + /// Removes the elements from the channel that satisfy the predicate. + /// + /// See [`PriorityChannel::remove_if()`] + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.channel.remove_if(predicate) + } + + /// Returns the maximum number of elements the channel can hold. + /// + /// See [`PriorityChannel::capacity()`] + pub const fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the channel. + /// + /// See [`PriorityChannel::free_capacity()`] + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the channel. + /// + /// See [`PriorityChannel::clear()`] + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the channel. + /// + /// See [`PriorityChannel::len()`] + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the channel is empty. + /// + /// See [`PriorityChannel::is_empty()`] + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the channel is full. + /// + /// See [`PriorityChannel::is_full()`] + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +impl<'ch, M, T, K, const N: usize> From> for DynamicReceiver<'ch, T> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn from(s: Receiver<'ch, M, T, K, N>) -> Self { + Self { channel: s.channel } + } +} + +/// Future returned by [`PriorityChannel::receive`] and [`Receiver::receive`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct ReceiveFuture<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, +} + +impl<'ch, M, T, K, const N: usize> Future for ReceiveFuture<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.channel.poll_receive(cx) + } +} + +/// Future returned by [`PriorityChannel::send`] and [`Sender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SendFuture<'ch, M, T, K, const N: usize> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + channel: &'ch PriorityChannel, + message: Option, +} + +impl<'ch, M, T, K, const N: usize> Future for SendFuture<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.message.take() { + Some(m) => match self.channel.try_send_with_context(m, Some(cx)) { + Ok(..) => Poll::Ready(()), + Err(TrySendError::Full(m)) => { + self.message = Some(m); + Poll::Pending + } + }, + None => panic!("Message cannot be None"), + } + } +} + +impl<'ch, M, T, K, const N: usize> Unpin for SendFuture<'ch, M, T, K, N> +where + T: Ord, + K: Kind, + M: RawMutex, +{ +} + +struct ChannelState { + queue: BinaryHeap, + receiver_waker: WakerRegistration, + senders_waker: WakerRegistration, +} + +impl ChannelState +where + T: Ord, + K: Kind, +{ + const fn new() -> Self { + ChannelState { + queue: BinaryHeap::new(), + receiver_waker: WakerRegistration::new(), + senders_waker: WakerRegistration::new(), + } + } + + fn try_receive(&mut self) -> Result { + self.try_receive_with_context(None) + } + + fn try_receive_with_context(&mut self, cx: Option<&mut Context<'_>>) -> Result { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop() { + Ok(message) + } else { + if let Some(cx) = cx { + self.receiver_waker.register(cx.waker()); + } + Err(TryReceiveError::Empty) + } + } + + fn poll_receive(&mut self, cx: &mut Context<'_>) -> Poll { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + + if let Some(message) = self.queue.pop() { + Poll::Ready(message) + } else { + self.receiver_waker.register(cx.waker()); + Poll::Pending + } + } + + fn poll_ready_to_receive(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.receiver_waker.register(cx.waker()); + + if !self.queue.is_empty() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn try_send(&mut self, message: T) -> Result<(), TrySendError> { + self.try_send_with_context(message, None) + } + + fn try_send_with_context(&mut self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + match self.queue.push(message) { + Ok(()) => { + self.receiver_waker.wake(); + Ok(()) + } + Err(message) => { + if let Some(cx) = cx { + self.senders_waker.register(cx.waker()); + } + Err(TrySendError::Full(message)) + } + } + } + + fn poll_ready_to_send(&mut self, cx: &mut Context<'_>) -> Poll<()> { + self.senders_waker.register(cx.waker()); + + if !self.queue.len() == self.queue.capacity() { + Poll::Ready(()) + } else { + Poll::Pending + } + } + + fn clear(&mut self) { + if self.queue.len() == self.queue.capacity() { + self.senders_waker.wake(); + } + self.queue.clear(); + } + + fn len(&self) -> usize { + self.queue.len() + } + + fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + fn is_full(&self) -> bool { + self.queue.len() == self.queue.capacity() + } +} + +/// A bounded channel for communicating between asynchronous tasks +/// with backpressure. +/// +/// The channel will buffer up to the provided number of messages. Once the +/// buffer is full, attempts to `send` new messages will wait until a message is +/// received from the channel. +/// +/// Sent data may be reordered based on their priority within the channel. +/// For example, in a [`Max`](heapless::binary_heap::Max) [`PriorityChannel`] +/// containing `u32`'s, data sent in the following order `[1, 2, 3]` will be received as `[3, 2, 1]`. +pub struct PriorityChannel +where + T: Ord, + K: Kind, + M: RawMutex, +{ + inner: Mutex>>, +} + +impl PriorityChannel +where + T: Ord, + K: Kind, + M: RawMutex, +{ + /// Establish a new bounded channel. For example, to create one with a NoopMutex: + /// + /// ``` + /// use embassy_sync::priority_channel::{PriorityChannel, Max}; + /// use embassy_sync::blocking_mutex::raw::NoopRawMutex; + /// + /// // Declare a bounded channel of 3 u32s. + /// let mut channel = PriorityChannel::::new(); + /// ``` + pub const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(ChannelState::new())), + } + } + + fn lock(&self, f: impl FnOnce(&mut ChannelState) -> R) -> R { + self.inner.lock(|rc| f(&mut *unwrap!(rc.try_borrow_mut()))) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + self.lock(|c| c.try_receive_with_context(cx)) + } + + /// Poll the channel for the next message + pub fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + self.lock(|c| c.poll_receive(cx)) + } + + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + self.lock(|c| c.try_send_with_context(m, cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to receive + pub fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_receive(cx)) + } + + /// Allows a poll_fn to poll until the channel is ready to send + pub fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + self.lock(|c| c.poll_ready_to_send(cx)) + } + + /// Get a sender for this channel. + pub fn sender(&self) -> Sender<'_, M, T, K, N> { + Sender { channel: self } + } + + /// Get a receiver for this channel. + pub fn receiver(&self) -> Receiver<'_, M, T, K, N> { + Receiver { channel: self } + } + + /// Send a value, waiting until there is capacity. + /// + /// Sending completes when the value has been pushed to the channel's queue. + /// This doesn't mean the value has been received yet. + pub fn send(&self, message: T) -> SendFuture<'_, M, T, K, N> { + SendFuture { + channel: self, + message: Some(message), + } + } + + /// Attempt to immediately send a message. + /// + /// This method differs from [`send`](PriorityChannel::send) by returning immediately if the channel's + /// buffer is full, instead of waiting. + /// + /// # Errors + /// + /// If the channel capacity has been reached, i.e., the channel has `n` + /// buffered values where `n` is the argument passed to [`PriorityChannel`], then an + /// error is returned. + pub fn try_send(&self, message: T) -> Result<(), TrySendError> { + self.lock(|c| c.try_send(message)) + } + + /// Receive the next value. + /// + /// If there are no messages in the channel's buffer, this method will + /// wait until a message is sent. + pub fn receive(&self) -> ReceiveFuture<'_, M, T, K, N> { + ReceiveFuture { channel: self } + } + + /// Attempt to immediately receive a message. + /// + /// This method will either receive a message from the channel immediately or return an error + /// if the channel is empty. + pub fn try_receive(&self) -> Result { + self.lock(|c| c.try_receive()) + } + + /// Removes elements from the channel based on the given predicate. + pub fn remove_if(&self, predicate: F) + where + F: Fn(&T) -> bool, + T: Clone, + { + self.lock(|c| { + let mut new_heap = BinaryHeap::::new(); + for item in c.queue.iter() { + if !predicate(item) { + match new_heap.push(item.clone()) { + Ok(_) => (), + Err(_) => panic!("Error pushing item to heap"), + } + } + } + c.queue = new_heap; + }); + } + + /// Returns the maximum number of elements the channel can hold. + pub const fn capacity(&self) -> usize { + N + } + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + N - self.len() + } + + /// Clears all elements in the channel. + pub fn clear(&self) { + self.lock(|c| c.clear()); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.lock(|c| c.len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.lock(|c| c.is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.lock(|c| c.is_full()) + } +} + +/// Implements the DynamicChannel to allow creating types that are unaware of the queue size with the +/// tradeoff cost of dynamic dispatch. +impl DynamicChannel for PriorityChannel +where + T: Ord, + K: Kind, + M: RawMutex, +{ + fn try_send_with_context(&self, m: T, cx: Option<&mut Context<'_>>) -> Result<(), TrySendError> { + PriorityChannel::try_send_with_context(self, m, cx) + } + + fn try_receive_with_context(&self, cx: Option<&mut Context<'_>>) -> Result { + PriorityChannel::try_receive_with_context(self, cx) + } + + fn poll_ready_to_send(&self, cx: &mut Context<'_>) -> Poll<()> { + PriorityChannel::poll_ready_to_send(self, cx) + } + + fn poll_ready_to_receive(&self, cx: &mut Context<'_>) -> Poll<()> { + PriorityChannel::poll_ready_to_receive(self, cx) + } + + fn poll_receive(&self, cx: &mut Context<'_>) -> Poll { + PriorityChannel::poll_receive(self, cx) + } +} + +#[cfg(test)] +mod tests { + use core::time::Duration; + + use futures_executor::ThreadPool; + use futures_timer::Delay; + use futures_util::task::SpawnExt; + use heapless::binary_heap::{Kind, Max}; + use static_cell::StaticCell; + + use super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + fn capacity(c: &ChannelState) -> usize + where + T: Ord, + K: Kind, + { + c.queue.capacity() - c.queue.len() + } + + #[test] + fn sending_once() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(capacity(&c), 2); + } + + #[test] + fn sending_when_full() { + let mut c = ChannelState::::new(); + let _ = c.try_send(1); + let _ = c.try_send(1); + let _ = c.try_send(1); + match c.try_send(2) { + Err(TrySendError::Full(2)) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 0); + } + + #[test] + fn send_priority() { + // Prio channel with kind `Max` sifts larger numbers to the front of the queue + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert!(c.try_send(2).is_ok()); + assert!(c.try_send(3).is_ok()); + assert_eq!(c.try_receive().unwrap(), 3); + assert_eq!(c.try_receive().unwrap(), 2); + assert_eq!(c.try_receive().unwrap(), 1); + } + + #[test] + fn receiving_once_with_one_send() { + let mut c = ChannelState::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + assert_eq!(capacity(&c), 3); + } + + #[test] + fn receiving_when_empty() { + let mut c = ChannelState::::new(); + match c.try_receive() { + Err(TryReceiveError::Empty) => assert!(true), + _ => assert!(false), + } + assert_eq!(capacity(&c), 3); + } + + #[test] + fn simple_send_and_receive() { + let c = PriorityChannel::::new(); + assert!(c.try_send(1).is_ok()); + assert_eq!(c.try_receive().unwrap(), 1); + } + + #[test] + fn cloning() { + let c = PriorityChannel::::new(); + let r1 = c.receiver(); + let s1 = c.sender(); + + let _ = r1.clone(); + let _ = s1.clone(); + } + + #[test] + fn dynamic_dispatch() { + let c = PriorityChannel::::new(); + let s: DynamicSender<'_, u32> = c.sender().into(); + let r: DynamicReceiver<'_, u32> = c.receiver().into(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + + #[futures_test::test] + async fn receiver_receives_given_try_send_async() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(PriorityChannel::new()); + let c2 = c; + assert!(executor + .spawn(async move { + assert!(c2.try_send(1).is_ok()); + }) + .is_ok()); + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn sender_send_completes_if_capacity() { + let c = PriorityChannel::::new(); + c.send(1).await; + assert_eq!(c.receive().await, 1); + } + + #[futures_test::test] + async fn senders_sends_wait_until_capacity() { + let executor = ThreadPool::new().unwrap(); + + static CHANNEL: StaticCell> = StaticCell::new(); + let c = &*CHANNEL.init(PriorityChannel::new()); + assert!(c.try_send(1).is_ok()); + + let c2 = c; + let send_task_1 = executor.spawn_with_handle(async move { c2.send(2).await }); + let c2 = c; + let send_task_2 = executor.spawn_with_handle(async move { c2.send(3).await }); + // Wish I could think of a means of determining that the async send is waiting instead. + // However, I've used the debugger to observe that the send does indeed wait. + Delay::new(Duration::from_millis(500)).await; + assert_eq!(c.receive().await, 1); + assert!(executor + .spawn(async move { + loop { + c.receive().await; + } + }) + .is_ok()); + send_task_1.unwrap().await; + send_task_2.unwrap().await; + } +} diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs new file mode 100644 index 0000000..606efff --- /dev/null +++ b/embassy-sync/src/pubsub/mod.rs @@ -0,0 +1,787 @@ +//! Implementation of [PubSubChannel], a queue where published messages get received by all subscribers. + +#![deny(missing_docs)] + +use core::cell::RefCell; +use core::fmt::Debug; +use core::task::{Context, Poll}; + +use heapless::Deque; + +use self::publisher::{ImmediatePub, Pub}; +use self::subscriber::Sub; +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +pub mod publisher; +pub mod subscriber; + +pub use publisher::{DynImmediatePublisher, DynPublisher, ImmediatePublisher, Publisher}; +pub use subscriber::{DynSubscriber, Subscriber}; + +/// A broadcast channel implementation where multiple publishers can send messages to multiple subscribers +/// +/// Any published message can be read by all subscribers. +/// A publisher can choose how it sends its message. +/// +/// - With [Pub::publish()] the publisher has to wait until there is space in the internal message queue. +/// - With [Pub::publish_immediate()] the publisher doesn't await and instead lets the oldest message +/// in the queue drop if necessary. This will cause any [Subscriber] that missed the message to receive +/// an error to indicate that it has lagged. +/// +/// ## Example +/// +/// ``` +/// # use embassy_sync::blocking_mutex::raw::NoopRawMutex; +/// # use embassy_sync::pubsub::WaitResult; +/// # use embassy_sync::pubsub::PubSubChannel; +/// # use futures_executor::block_on; +/// # let test = async { +/// // Create the channel. This can be static as well +/// let channel = PubSubChannel::::new(); +/// +/// // This is a generic subscriber with a direct reference to the channel +/// let mut sub0 = channel.subscriber().unwrap(); +/// // This is a dynamic subscriber with a dynamic (trait object) reference to the channel +/// let mut sub1 = channel.dyn_subscriber().unwrap(); +/// +/// let pub0 = channel.publisher().unwrap(); +/// +/// // Publish a message, but wait if the queue is full +/// pub0.publish(42).await; +/// +/// // Publish a message, but if the queue is full, just kick out the oldest message. +/// // This may cause some subscribers to miss a message +/// pub0.publish_immediate(43); +/// +/// // Wait for a new message. If the subscriber missed a message, the WaitResult will be a Lag result +/// assert_eq!(sub0.next_message().await, WaitResult::Message(42)); +/// assert_eq!(sub1.next_message().await, WaitResult::Message(42)); +/// +/// // Wait again, but this time ignore any Lag results +/// assert_eq!(sub0.next_message_pure().await, 43); +/// assert_eq!(sub1.next_message_pure().await, 43); +/// +/// // There's also a polling interface +/// assert_eq!(sub0.try_next_message(), None); +/// assert_eq!(sub1.try_next_message(), None); +/// # }; +/// # +/// # block_on(test); +/// ``` +/// +pub struct PubSubChannel { + inner: Mutex>>, +} + +impl + PubSubChannel +{ + /// Create a new channel + pub const fn new() -> Self { + Self { + inner: Mutex::const_new(M::INIT, RefCell::new(PubSubState::new())), + } + } + + /// Create a new subscriber. It will only receive messages that are published after its creation. + /// + /// If there are no subscriber slots left, an error will be returned. + pub fn subscriber(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.subscriber_count >= SUBS { + Err(Error::MaximumSubscribersReached) + } else { + s.subscriber_count += 1; + Ok(Subscriber(Sub::new(s.next_message_id, self))) + } + }) + } + + /// Create a new subscriber. It will only receive messages that are published after its creation. + /// + /// If there are no subscriber slots left, an error will be returned. + pub fn dyn_subscriber(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.subscriber_count >= SUBS { + Err(Error::MaximumSubscribersReached) + } else { + s.subscriber_count += 1; + Ok(DynSubscriber(Sub::new(s.next_message_id, self))) + } + }) + } + + /// Create a new publisher + /// + /// If there are no publisher slots left, an error will be returned. + pub fn publisher(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.publisher_count >= PUBS { + Err(Error::MaximumPublishersReached) + } else { + s.publisher_count += 1; + Ok(Publisher(Pub::new(self))) + } + }) + } + + /// Create a new publisher + /// + /// If there are no publisher slots left, an error will be returned. + pub fn dyn_publisher(&self) -> Result, Error> { + self.inner.lock(|inner| { + let mut s = inner.borrow_mut(); + + if s.publisher_count >= PUBS { + Err(Error::MaximumPublishersReached) + } else { + s.publisher_count += 1; + Ok(DynPublisher(Pub::new(self))) + } + }) + } + + /// Create a new publisher that can only send immediate messages. + /// This kind of publisher does not take up a publisher slot. + pub fn immediate_publisher(&self) -> ImmediatePublisher { + ImmediatePublisher(ImmediatePub::new(self)) + } + + /// Create a new publisher that can only send immediate messages. + /// This kind of publisher does not take up a publisher slot. + pub fn dyn_immediate_publisher(&self) -> DynImmediatePublisher { + DynImmediatePublisher(ImmediatePub::new(self)) + } + + /// Returns the maximum number of elements the channel can hold. + pub const fn capacity(&self) -> usize { + CAP + } + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + CAP - self.len() + } + + /// Clears all elements in the channel. + pub fn clear(&self) { + self.inner.lock(|inner| inner.borrow_mut().clear()); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.inner.lock(|inner| inner.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.inner.lock(|inner| inner.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.inner.lock(|inner| inner.borrow().is_full()) + } +} + +impl crate::pubsub::PubSubBehavior + for PubSubChannel +{ + fn publish_immediate(&self, message: T) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.publish_immediate(message) + }) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn is_full(&self) -> bool { + self.is_full() + } +} + +impl SealedPubSubBehavior + for PubSubChannel +{ + fn get_message_with_context(&self, next_message_id: &mut u64, cx: Option<&mut Context<'_>>) -> Poll> { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + + // Check if we can read a message + match s.get_message(*next_message_id) { + // Yes, so we are done polling + Some(WaitResult::Message(message)) => { + *next_message_id += 1; + Poll::Ready(WaitResult::Message(message)) + } + // No, so we need to reregister our waker and sleep again + None => { + if let Some(cx) = cx { + s.subscriber_wakers.register(cx.waker()); + } + Poll::Pending + } + // We missed a couple of messages. We must do our internal bookkeeping and return that we lagged + Some(WaitResult::Lagged(amount)) => { + *next_message_id += amount; + Poll::Ready(WaitResult::Lagged(amount)) + } + } + }) + } + + fn available(&self, next_message_id: u64) -> u64 { + self.inner.lock(|s| s.borrow().next_message_id - next_message_id) + } + + fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T> { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + // Try to publish the message + match s.try_publish(message) { + // We did it, we are ready + Ok(()) => Ok(()), + // The queue is full, so we need to reregister our waker and go to sleep + Err(message) => { + if let Some(cx) = cx { + s.publisher_wakers.register(cx.waker()); + } + Err(message) + } + } + }) + } + + fn unregister_subscriber(&self, subscriber_next_message_id: u64) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.unregister_subscriber(subscriber_next_message_id) + }) + } + + fn unregister_publisher(&self) { + self.inner.lock(|s| { + let mut s = s.borrow_mut(); + s.unregister_publisher() + }) + } + + fn free_capacity(&self) -> usize { + self.free_capacity() + } + + fn clear(&self) { + self.clear(); + } + + fn len(&self) -> usize { + self.len() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } +} + +/// Internal state for the PubSub channel +struct PubSubState { + /// The queue contains the last messages that have been published and a countdown of how many subscribers are yet to read it + queue: Deque<(T, usize), CAP>, + /// Every message has an id. + /// Don't worry, we won't run out. + /// If a million messages were published every second, then the ID's would run out in about 584942 years. + next_message_id: u64, + /// Collection of wakers for Subscribers that are waiting. + subscriber_wakers: MultiWakerRegistration, + /// Collection of wakers for Publishers that are waiting. + publisher_wakers: MultiWakerRegistration, + /// The amount of subscribers that are active + subscriber_count: usize, + /// The amount of publishers that are active + publisher_count: usize, +} + +impl PubSubState { + /// Create a new internal channel state + const fn new() -> Self { + Self { + queue: Deque::new(), + next_message_id: 0, + subscriber_wakers: MultiWakerRegistration::new(), + publisher_wakers: MultiWakerRegistration::new(), + subscriber_count: 0, + publisher_count: 0, + } + } + + fn try_publish(&mut self, message: T) -> Result<(), T> { + if self.subscriber_count == 0 { + // We don't need to publish anything because there is no one to receive it + return Ok(()); + } + + if self.queue.is_full() { + return Err(message); + } + // We just did a check for this + self.queue.push_back((message, self.subscriber_count)).ok().unwrap(); + + self.next_message_id += 1; + + // Wake all of the subscribers + self.subscriber_wakers.wake(); + + Ok(()) + } + + fn publish_immediate(&mut self, message: T) { + // Make space in the queue if required + if self.queue.is_full() { + self.queue.pop_front(); + } + + // This will succeed because we made sure there is space + self.try_publish(message).ok().unwrap(); + } + + fn get_message(&mut self, message_id: u64) -> Option> { + let start_id = self.next_message_id - self.queue.len() as u64; + + if message_id < start_id { + return Some(WaitResult::Lagged(start_id - message_id)); + } + + let current_message_index = (message_id - start_id) as usize; + + if current_message_index >= self.queue.len() { + return None; + } + + // We've checked that the index is valid + let queue_item = self.queue.iter_mut().nth(current_message_index).unwrap(); + + // We're reading this item, so decrement the counter + queue_item.1 -= 1; + + let message = if current_message_index == 0 && queue_item.1 == 0 { + let (message, _) = self.queue.pop_front().unwrap(); + self.publisher_wakers.wake(); + // Return pop'd message without clone + message + } else { + queue_item.0.clone() + }; + + Some(WaitResult::Message(message)) + } + + fn unregister_subscriber(&mut self, subscriber_next_message_id: u64) { + self.subscriber_count -= 1; + + // All messages that haven't been read yet by this subscriber must have their counter decremented + let start_id = self.next_message_id - self.queue.len() as u64; + if subscriber_next_message_id >= start_id { + let current_message_index = (subscriber_next_message_id - start_id) as usize; + self.queue + .iter_mut() + .skip(current_message_index) + .for_each(|(_, counter)| *counter -= 1); + + let mut wake_publishers = false; + while let Some((_, count)) = self.queue.front() { + if *count == 0 { + self.queue.pop_front().unwrap(); + wake_publishers = true; + } else { + break; + } + } + + if wake_publishers { + self.publisher_wakers.wake(); + } + } + } + + fn unregister_publisher(&mut self) { + self.publisher_count -= 1; + } + + fn clear(&mut self) { + if self.is_full() { + self.publisher_wakers.wake(); + } + self.queue.clear(); + } + + fn len(&self) -> usize { + self.queue.len() + } + + fn is_empty(&self) -> bool { + self.queue.is_empty() + } + + fn is_full(&self) -> bool { + self.queue.is_full() + } +} + +/// Error type for the [PubSubChannel] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// All subscriber slots are used. To add another subscriber, first another subscriber must be dropped or + /// the capacity of the channels must be increased. + MaximumSubscribersReached, + /// All publisher slots are used. To add another publisher, first another publisher must be dropped or + /// the capacity of the channels must be increased. + MaximumPublishersReached, +} + +trait SealedPubSubBehavior { + /// Try to get a message from the queue with the given message id. + /// + /// If the message is not yet present and a context is given, then its waker is registered in the subscriber wakers. + fn get_message_with_context(&self, next_message_id: &mut u64, cx: Option<&mut Context<'_>>) -> Poll>; + + /// Get the amount of messages that are between the given the next_message_id and the most recent message. + /// This is not necessarily the amount of messages a subscriber can still received as it may have lagged. + fn available(&self, next_message_id: u64) -> u64; + + /// Try to publish a message to the queue. + /// + /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. + fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T>; + + /// Returns the free capacity of the channel. + /// + /// This is equivalent to `capacity() - len()` + fn free_capacity(&self) -> usize; + + /// Clears all elements in the channel. + fn clear(&self); + + /// Returns the number of elements currently in the channel. + fn len(&self) -> usize; + + /// Returns whether the channel is empty. + fn is_empty(&self) -> bool; + + /// Let the channel know that a subscriber has dropped + fn unregister_subscriber(&self, subscriber_next_message_id: u64); + + /// Let the channel know that a publisher has dropped + fn unregister_publisher(&self); +} + +/// 'Middle level' behaviour of the pubsub channel. +/// This trait is used so that Sub and Pub can be generic over the channel. +#[allow(private_bounds)] +pub trait PubSubBehavior: SealedPubSubBehavior { + /// Publish a message immediately + fn publish_immediate(&self, message: T); + + /// Returns the maximum number of elements the channel can hold. + fn capacity(&self) -> usize; + + /// Returns whether the channel is full. + fn is_full(&self) -> bool; +} + +/// The result of the subscriber wait procedure +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WaitResult { + /// The subscriber did not receive all messages and lagged by the given amount of messages. + /// (This is the amount of messages that were missed) + Lagged(u64), + /// A message was received + Message(T), +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::blocking_mutex::raw::NoopRawMutex; + + #[futures_test::test] + async fn dyn_pub_sub_works() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.dyn_subscriber().unwrap(); + let mut sub1 = channel.dyn_subscriber().unwrap(); + let pub0 = channel.dyn_publisher().unwrap(); + + pub0.publish(42).await; + + assert_eq!(sub0.next_message().await, WaitResult::Message(42)); + assert_eq!(sub1.next_message().await, WaitResult::Message(42)); + + assert_eq!(sub0.try_next_message(), None); + assert_eq!(sub1.try_next_message(), None); + } + + #[futures_test::test] + async fn all_subscribers_receive() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + pub0.publish(42).await; + + assert_eq!(sub0.next_message().await, WaitResult::Message(42)); + assert_eq!(sub1.next_message().await, WaitResult::Message(42)); + + assert_eq!(sub0.try_next_message(), None); + assert_eq!(sub1.try_next_message(), None); + } + + #[futures_test::test] + async fn lag_when_queue_full_on_immediate_publish() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + pub0.publish_immediate(42); + pub0.publish_immediate(43); + pub0.publish_immediate(44); + pub0.publish_immediate(45); + pub0.publish_immediate(46); + pub0.publish_immediate(47); + + assert_eq!(sub0.try_next_message(), Some(WaitResult::Lagged(2))); + assert_eq!(sub0.next_message().await, WaitResult::Message(44)); + assert_eq!(sub0.next_message().await, WaitResult::Message(45)); + assert_eq!(sub0.next_message().await, WaitResult::Message(46)); + assert_eq!(sub0.next_message().await, WaitResult::Message(47)); + assert_eq!(sub0.try_next_message(), None); + } + + #[test] + fn limited_subs_and_pubs() { + let channel = PubSubChannel::::new(); + + let sub0 = channel.subscriber(); + let sub1 = channel.subscriber(); + let sub2 = channel.subscriber(); + let sub3 = channel.subscriber(); + let sub4 = channel.subscriber(); + + assert!(sub0.is_ok()); + assert!(sub1.is_ok()); + assert!(sub2.is_ok()); + assert!(sub3.is_ok()); + assert_eq!(sub4.err().unwrap(), Error::MaximumSubscribersReached); + + drop(sub0); + + let sub5 = channel.subscriber(); + assert!(sub5.is_ok()); + + // publishers + + let pub0 = channel.publisher(); + let pub1 = channel.publisher(); + let pub2 = channel.publisher(); + let pub3 = channel.publisher(); + let pub4 = channel.publisher(); + + assert!(pub0.is_ok()); + assert!(pub1.is_ok()); + assert!(pub2.is_ok()); + assert!(pub3.is_ok()); + assert_eq!(pub4.err().unwrap(), Error::MaximumPublishersReached); + + drop(pub0); + + let pub5 = channel.publisher(); + assert!(pub5.is_ok()); + } + + #[test] + fn publisher_wait_on_full_queue() { + let channel = PubSubChannel::::new(); + + let pub0 = channel.publisher().unwrap(); + + // There are no subscribers, so the queue will never be full + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + + let sub0 = channel.subscriber().unwrap(); + + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert_eq!(pub0.try_publish(0), Ok(())); + assert!(pub0.is_full()); + assert_eq!(pub0.try_publish(0), Err(0)); + + drop(sub0); + } + + #[futures_test::test] + async fn correct_available() { + let channel = PubSubChannel::::new(); + + let sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + assert_eq!(sub0.available(), 0); + assert_eq!(sub1.available(), 0); + + pub0.publish(42).await; + + assert_eq!(sub0.available(), 1); + assert_eq!(sub1.available(), 1); + + sub1.next_message().await; + + assert_eq!(sub1.available(), 0); + + pub0.publish(42).await; + + assert_eq!(sub0.available(), 2); + assert_eq!(sub1.available(), 1); + } + + #[futures_test::test] + async fn correct_len() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + assert!(sub0.is_empty()); + assert!(sub1.is_empty()); + assert!(pub0.is_empty()); + assert_eq!(pub0.free_capacity(), 4); + assert_eq!(pub0.len(), 0); + + pub0.publish(42).await; + + assert_eq!(pub0.free_capacity(), 3); + assert_eq!(pub0.len(), 1); + + pub0.publish(42).await; + + assert_eq!(pub0.free_capacity(), 2); + assert_eq!(pub0.len(), 2); + + sub0.next_message().await; + sub0.next_message().await; + + assert_eq!(pub0.free_capacity(), 2); + assert_eq!(pub0.len(), 2); + + sub1.next_message().await; + assert_eq!(pub0.free_capacity(), 3); + assert_eq!(pub0.len(), 1); + + sub1.next_message().await; + assert_eq!(pub0.free_capacity(), 4); + assert_eq!(pub0.len(), 0); + } + + #[futures_test::test] + async fn empty_channel_when_last_subscriber_is_dropped() { + let channel = PubSubChannel::::new(); + + let pub0 = channel.publisher().unwrap(); + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + + assert_eq!(4, pub0.free_capacity()); + + pub0.publish(1).await; + pub0.publish(2).await; + + assert_eq!(2, channel.free_capacity()); + + assert_eq!(1, sub0.try_next_message_pure().unwrap()); + assert_eq!(2, sub0.try_next_message_pure().unwrap()); + + assert_eq!(2, channel.free_capacity()); + + drop(sub0); + + assert_eq!(2, channel.free_capacity()); + + assert_eq!(1, sub1.try_next_message_pure().unwrap()); + + assert_eq!(3, channel.free_capacity()); + + drop(sub1); + + assert_eq!(4, channel.free_capacity()); + } + + struct CloneCallCounter(usize); + + impl Clone for CloneCallCounter { + fn clone(&self) -> Self { + Self(self.0 + 1) + } + } + + #[futures_test::test] + async fn skip_clone_for_last_message() { + let channel = PubSubChannel::::new(); + let pub0 = channel.publisher().unwrap(); + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + + pub0.publish(CloneCallCounter(0)).await; + + assert_eq!(1, sub0.try_next_message_pure().unwrap().0); + assert_eq!(0, sub1.try_next_message_pure().unwrap().0); + } + + #[futures_test::test] + async fn publisher_sink() { + use futures_util::{SinkExt, StreamExt}; + + let channel = PubSubChannel::::new(); + + let mut sub = channel.subscriber().unwrap(); + + let publ = channel.publisher().unwrap(); + let mut sink = publ.sink(); + + sink.send(0).await.unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + + sink.send(1).await.unwrap(); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + + sink.send_all(&mut futures_util::stream::iter(0..4).map(Ok)) + .await + .unwrap(); + assert_eq!(0, sub.try_next_message_pure().unwrap()); + assert_eq!(1, sub.try_next_message_pure().unwrap()); + assert_eq!(2, sub.try_next_message_pure().unwrap()); + assert_eq!(3, sub.try_next_message_pure().unwrap()); + } +} diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs new file mode 100644 index 0000000..7a1ab66 --- /dev/null +++ b/embassy-sync/src/pubsub/publisher.rs @@ -0,0 +1,314 @@ +//! Implementation of anything directly publisher related + +use core::future::Future; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use super::{PubSubBehavior, PubSubChannel}; +use crate::blocking_mutex::raw::RawMutex; + +/// A publisher to a channel +pub struct Pub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The channel we are a publisher for + channel: &'a PSB, + _phantom: PhantomData, +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { + pub(super) fn new(channel: &'a PSB) -> Self { + Self { + channel, + _phantom: Default::default(), + } + } + + /// Publish a message right now even when the queue is full. + /// This may cause a subscriber to miss an older message. + pub fn publish_immediate(&self, message: T) { + self.channel.publish_immediate(message) + } + + /// Publish a message. But if the message queue is full, wait for all subscribers to have read the last message + pub fn publish<'s>(&'s self, message: T) -> PublisherWaitFuture<'s, 'a, PSB, T> { + PublisherWaitFuture { + message: Some(message), + publisher: self, + } + } + + /// Publish a message if there is space in the message queue + pub fn try_publish(&self, message: T) -> Result<(), T> { + self.channel.publish_with_context(message, None) + } + + /// Returns the maximum number of elements the ***channel*** can hold. + pub fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the ***channel***. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the ***channel***. + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the ***channel***. + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the ***channel*** is empty. + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the ***channel*** is full. + pub fn is_full(&self) -> bool { + self.channel.is_full() + } + + /// Create a [`futures::Sink`] adapter for this publisher. + #[inline] + pub const fn sink(&self) -> PubSink<'a, '_, PSB, T> { + PubSink { publ: self, fut: None } + } +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { + fn drop(&mut self) { + self.channel.unregister_publisher() + } +} + +/// A publisher that holds a dynamic reference to the channel +pub struct DynPublisher<'a, T: Clone>(pub(super) Pub<'a, dyn PubSubBehavior + 'a, T>); + +impl<'a, T: Clone> Deref for DynPublisher<'a, T> { + type Target = Pub<'a, dyn PubSubBehavior + 'a, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynPublisher<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A publisher that holds a generic reference to the channel +pub struct Publisher<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( + pub(super) Pub<'a, PubSubChannel, T>, +); + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> Deref + for Publisher<'a, M, T, CAP, SUBS, PUBS> +{ + type Target = Pub<'a, PubSubChannel, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> DerefMut + for Publisher<'a, M, T, CAP, SUBS, PUBS> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A publisher that can only use the `publish_immediate` function, but it doesn't have to be registered with the channel. +/// (So an infinite amount is possible) +pub struct ImmediatePub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The channel we are a publisher for + channel: &'a PSB, + _phantom: PhantomData, +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> ImmediatePub<'a, PSB, T> { + pub(super) fn new(channel: &'a PSB) -> Self { + Self { + channel, + _phantom: Default::default(), + } + } + /// Publish the message right now even when the queue is full. + /// This may cause a subscriber to miss an older message. + pub fn publish_immediate(&self, message: T) { + self.channel.publish_immediate(message) + } + + /// Publish a message if there is space in the message queue + pub fn try_publish(&self, message: T) -> Result<(), T> { + self.channel.publish_with_context(message, None) + } + + /// Returns the maximum number of elements the ***channel*** can hold. + pub fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the ***channel***. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the ***channel***. + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the ***channel***. + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the ***channel*** is empty. + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the ***channel*** is full. + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +/// An immediate publisher that holds a dynamic reference to the channel +pub struct DynImmediatePublisher<'a, T: Clone>(pub(super) ImmediatePub<'a, dyn PubSubBehavior + 'a, T>); + +impl<'a, T: Clone> Deref for DynImmediatePublisher<'a, T> { + type Target = ImmediatePub<'a, dyn PubSubBehavior + 'a, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynImmediatePublisher<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// An immediate publisher that holds a generic reference to the channel +pub struct ImmediatePublisher<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( + pub(super) ImmediatePub<'a, PubSubChannel, T>, +); + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> Deref + for ImmediatePublisher<'a, M, T, CAP, SUBS, PUBS> +{ + type Target = ImmediatePub<'a, PubSubChannel, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> DerefMut + for ImmediatePublisher<'a, M, T, CAP, SUBS, PUBS> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[must_use = "Sinks do nothing unless polled"] +/// [`futures_sink::Sink`] adapter for [`Pub`]. +pub struct PubSink<'a, 'p, PSB, T> +where + T: Clone, + PSB: PubSubBehavior + ?Sized, +{ + publ: &'p Pub<'a, PSB, T>, + fut: Option>, +} + +impl<'a, 'p, PSB, T> PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + /// Try to make progress on the pending future if we have one. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + let Some(mut fut) = self.fut.take() else { + return Poll::Ready(()); + }; + + if Pin::new(&mut fut).poll(cx).is_pending() { + self.fut = Some(fut); + return Poll::Pending; + } + + Poll::Ready(()) + } +} + +impl<'a, 'p, PSB, T> futures_sink::Sink for PubSink<'a, 'p, PSB, T> +where + PSB: PubSubBehavior + ?Sized, + T: Clone, +{ + type Error = core::convert::Infallible; + + #[inline] + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.fut = Some(self.publ.publish(item)); + + Ok(()) + } + + #[inline] + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } + + #[inline] + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll(cx).map(Ok) + } +} + +/// Future for the publisher wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The message we need to publish + message: Option, + publisher: &'s Pub<'a, PSB, T>, +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Future for PublisherWaitFuture<'s, 'a, PSB, T> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let message = self.message.take().unwrap(); + match self.publisher.channel.publish_with_context(message, Some(cx)) { + Ok(()) => Poll::Ready(()), + Err(message) => { + self.message = Some(message); + Poll::Pending + } + } + } +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for PublisherWaitFuture<'s, 'a, PSB, T> {} diff --git a/embassy-sync/src/pubsub/subscriber.rs b/embassy-sync/src/pubsub/subscriber.rs new file mode 100644 index 0000000..6ad660c --- /dev/null +++ b/embassy-sync/src/pubsub/subscriber.rs @@ -0,0 +1,192 @@ +//! Implementation of anything directly subscriber related + +use core::future::Future; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use super::{PubSubBehavior, PubSubChannel, WaitResult}; +use crate::blocking_mutex::raw::RawMutex; + +/// A subscriber to a channel +pub struct Sub<'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + /// The message id of the next message we are yet to receive + next_message_id: u64, + /// The channel we are a subscriber to + channel: &'a PSB, + _phantom: PhantomData, +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Sub<'a, PSB, T> { + pub(super) fn new(next_message_id: u64, channel: &'a PSB) -> Self { + Self { + next_message_id, + channel, + _phantom: Default::default(), + } + } + + /// Wait for a published message + pub fn next_message<'s>(&'s mut self) -> SubscriberWaitFuture<'s, 'a, PSB, T> { + SubscriberWaitFuture { subscriber: self } + } + + /// Wait for a published message (ignoring lag results) + pub async fn next_message_pure(&mut self) -> T { + loop { + match self.next_message().await { + WaitResult::Lagged(_) => continue, + WaitResult::Message(message) => break message, + } + } + } + + /// Try to see if there's a published message we haven't received yet. + /// + /// This function does not peek. The message is received if there is one. + pub fn try_next_message(&mut self) -> Option> { + match self.channel.get_message_with_context(&mut self.next_message_id, None) { + Poll::Ready(result) => Some(result), + Poll::Pending => None, + } + } + + /// Try to see if there's a published message we haven't received yet (ignoring lag results). + /// + /// This function does not peek. The message is received if there is one. + pub fn try_next_message_pure(&mut self) -> Option { + loop { + match self.try_next_message() { + Some(WaitResult::Lagged(_)) => continue, + Some(WaitResult::Message(message)) => break Some(message), + None => break None, + } + } + } + + /// The amount of messages this subscriber hasn't received yet. This is like [Self::len] but specifically + /// for this subscriber. + pub fn available(&self) -> u64 { + self.channel.available(self.next_message_id) + } + + /// Returns the maximum number of elements the ***channel*** can hold. + pub fn capacity(&self) -> usize { + self.channel.capacity() + } + + /// Returns the free capacity of the ***channel***. + /// + /// This is equivalent to `capacity() - len()` + pub fn free_capacity(&self) -> usize { + self.channel.free_capacity() + } + + /// Clears all elements in the ***channel***. + pub fn clear(&self) { + self.channel.clear(); + } + + /// Returns the number of elements currently in the ***channel***. + /// See [Self::available] for how many messages are available for this subscriber. + pub fn len(&self) -> usize { + self.channel.len() + } + + /// Returns whether the ***channel*** is empty. + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns whether the ***channel*** is full. + pub fn is_full(&self) -> bool { + self.channel.is_full() + } +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Sub<'a, PSB, T> { + fn drop(&mut self) { + self.channel.unregister_subscriber(self.next_message_id) + } +} + +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for Sub<'a, PSB, T> {} + +/// Warning: The stream implementation ignores lag results and returns all messages. +/// This might miss some messages without you knowing it. +impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> futures_util::Stream for Sub<'a, PSB, T> { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self + .channel + .get_message_with_context(&mut self.next_message_id, Some(cx)) + { + Poll::Ready(WaitResult::Message(message)) => Poll::Ready(Some(message)), + Poll::Ready(WaitResult::Lagged(_)) => { + cx.waker().wake_by_ref(); + Poll::Pending + } + Poll::Pending => Poll::Pending, + } + } +} + +/// A subscriber that holds a dynamic reference to the channel +pub struct DynSubscriber<'a, T: Clone>(pub(super) Sub<'a, dyn PubSubBehavior + 'a, T>); + +impl<'a, T: Clone> Deref for DynSubscriber<'a, T> { + type Target = Sub<'a, dyn PubSubBehavior + 'a, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSubscriber<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A subscriber that holds a generic reference to the channel +pub struct Subscriber<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize>( + pub(super) Sub<'a, PubSubChannel, T>, +); + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> Deref + for Subscriber<'a, M, T, CAP, SUBS, PUBS> +{ + type Target = Sub<'a, PubSubChannel, T>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: usize> DerefMut + for Subscriber<'a, M, T, CAP, SUBS, PUBS> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Future for the subscriber wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SubscriberWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { + subscriber: &'s mut Sub<'a, PSB, T>, +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Future for SubscriberWaitFuture<'s, 'a, PSB, T> { + type Output = WaitResult; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.subscriber + .channel + .get_message_with_context(&mut self.subscriber.next_message_id, Some(cx)) + } +} + +impl<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> Unpin for SubscriberWaitFuture<'s, 'a, PSB, T> {} diff --git a/embassy-sync/src/ring_buffer.rs b/embassy-sync/src/ring_buffer.rs new file mode 100644 index 0000000..81e60c4 --- /dev/null +++ b/embassy-sync/src/ring_buffer.rs @@ -0,0 +1,138 @@ +use core::ops::Range; + +pub struct RingBuffer { + start: usize, + end: usize, + full: bool, +} + +impl RingBuffer { + pub const fn new() -> Self { + Self { + start: 0, + end: 0, + full: false, + } + } + + pub fn push_buf(&mut self) -> Range { + if self.is_full() { + trace!(" ringbuf: push_buf full"); + return 0..0; + } + + let n = if self.start <= self.end { + N - self.end + } else { + self.start - self.end + }; + + trace!(" ringbuf: push_buf {:?}..{:?}", self.end, self.end + n); + self.end..self.end + n + } + + pub fn push(&mut self, n: usize) { + trace!(" ringbuf: push {:?}", n); + if n == 0 { + return; + } + + self.end = self.wrap(self.end + n); + self.full = self.start == self.end; + } + + pub fn pop_buf(&mut self) -> Range { + if self.is_empty() { + trace!(" ringbuf: pop_buf empty"); + return 0..0; + } + + let n = if self.end <= self.start { + N - self.start + } else { + self.end - self.start + }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", self.start, self.start + n); + self.start..self.start + n + } + + pub fn pop(&mut self, n: usize) { + trace!(" ringbuf: pop {:?}", n); + if n == 0 { + return; + } + + self.start = self.wrap(self.start + n); + self.full = false; + } + + pub fn is_full(&self) -> bool { + self.full + } + + pub fn is_empty(&self) -> bool { + self.start == self.end && !self.full + } + + #[allow(unused)] + pub fn len(&self) -> usize { + if self.is_empty() { + 0 + } else if self.start < self.end { + self.end - self.start + } else { + N + self.end - self.start + } + } + + pub fn clear(&mut self) { + self.start = 0; + self.end = 0; + self.full = false; + } + + fn wrap(&self, n: usize) -> usize { + assert!(n <= N); + if n == N { + 0 + } else { + n + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_pop() { + let mut rb: RingBuffer<4> = RingBuffer::new(); + let buf = rb.push_buf(); + assert_eq!(0..4, buf); + rb.push(4); + + let buf = rb.pop_buf(); + assert_eq!(0..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(1..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(2..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(3..4, buf); + rb.pop(1); + + let buf = rb.pop_buf(); + assert_eq!(0..0, buf); + + let buf = rb.push_buf(); + assert_eq!(0..4, buf); + } +} diff --git a/embassy-sync/src/semaphore.rs b/embassy-sync/src/semaphore.rs new file mode 100644 index 0000000..d30eee3 --- /dev/null +++ b/embassy-sync/src/semaphore.rs @@ -0,0 +1,772 @@ +//! A synchronization primitive for controlling access to a pool of resources. +use core::cell::{Cell, RefCell}; +use core::convert::Infallible; +use core::future::{poll_fn, Future}; +use core::task::{Poll, Waker}; + +use heapless::Deque; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::WakerRegistration; + +/// An asynchronous semaphore. +/// +/// A semaphore tracks a number of permits, typically representing a pool of shared resources. +/// Users can acquire permits to synchronize access to those resources. The semaphore does not +/// contain the resources themselves, only the count of available permits. +pub trait Semaphore: Sized { + /// The error returned when the semaphore is unable to acquire the requested permits. + type Error; + + /// Asynchronously acquire one or more permits from the semaphore. + async fn acquire(&self, permits: usize) -> Result, Self::Error>; + + /// Try to immediately acquire one or more permits from the semaphore. + fn try_acquire(&self, permits: usize) -> Option>; + + /// Asynchronously acquire all permits controlled by the semaphore. + /// + /// This method will wait until at least `min` permits are available, then acquire all available permits + /// from the semaphore. Note that other tasks may have already acquired some permits which could be released + /// back to the semaphore at any time. The number of permits actually acquired may be determined by calling + /// [`SemaphoreReleaser::permits`]. + async fn acquire_all(&self, min: usize) -> Result, Self::Error>; + + /// Try to immediately acquire all available permits from the semaphore, if at least `min` permits are available. + fn try_acquire_all(&self, min: usize) -> Option>; + + /// Release `permits` back to the semaphore, making them available to be acquired. + fn release(&self, permits: usize); + + /// Reset the number of available permints in the semaphore to `permits`. + fn set(&self, permits: usize); +} + +/// A representation of a number of acquired permits. +/// +/// The acquired permits will be released back to the [`Semaphore`] when this is dropped. +pub struct SemaphoreReleaser<'a, S: Semaphore> { + semaphore: &'a S, + permits: usize, +} + +impl<'a, S: Semaphore> Drop for SemaphoreReleaser<'a, S> { + fn drop(&mut self) { + self.semaphore.release(self.permits); + } +} + +impl<'a, S: Semaphore> SemaphoreReleaser<'a, S> { + /// The number of acquired permits. + pub fn permits(&self) -> usize { + self.permits + } + + /// Prevent the acquired permits from being released on drop. + /// + /// Returns the number of acquired permits. + pub fn disarm(self) -> usize { + let permits = self.permits; + core::mem::forget(self); + permits + } +} + +/// A greedy [`Semaphore`] implementation. +/// +/// Tasks can acquire permits as soon as they become available, even if another task +/// is waiting on a larger number of permits. +pub struct GreedySemaphore { + state: Mutex>, +} + +impl Default for GreedySemaphore { + fn default() -> Self { + Self::new(0) + } +} + +impl GreedySemaphore { + /// Create a new `Semaphore`. + pub const fn new(permits: usize) -> Self { + Self { + state: Mutex::new(Cell::new(SemaphoreState { + permits, + waker: WakerRegistration::new(), + })), + } + } + + #[cfg(test)] + fn permits(&self) -> usize { + self.state.lock(|cell| { + let state = cell.replace(SemaphoreState::EMPTY); + let permits = state.permits; + cell.replace(state); + permits + }) + } + + fn poll_acquire( + &self, + permits: usize, + acquire_all: bool, + waker: Option<&Waker>, + ) -> Poll, Infallible>> { + self.state.lock(|cell| { + let mut state = cell.replace(SemaphoreState::EMPTY); + if let Some(permits) = state.take(permits, acquire_all) { + cell.set(state); + Poll::Ready(Ok(SemaphoreReleaser { + semaphore: self, + permits, + })) + } else { + if let Some(waker) = waker { + state.register(waker); + } + cell.set(state); + Poll::Pending + } + }) + } +} + +impl Semaphore for GreedySemaphore { + type Error = Infallible; + + async fn acquire(&self, permits: usize) -> Result, Self::Error> { + poll_fn(|cx| self.poll_acquire(permits, false, Some(cx.waker()))).await + } + + fn try_acquire(&self, permits: usize) -> Option> { + match self.poll_acquire(permits, false, None) { + Poll::Ready(Ok(n)) => Some(n), + _ => None, + } + } + + async fn acquire_all(&self, min: usize) -> Result, Self::Error> { + poll_fn(|cx| self.poll_acquire(min, true, Some(cx.waker()))).await + } + + fn try_acquire_all(&self, min: usize) -> Option> { + match self.poll_acquire(min, true, None) { + Poll::Ready(Ok(n)) => Some(n), + _ => None, + } + } + + fn release(&self, permits: usize) { + if permits > 0 { + self.state.lock(|cell| { + let mut state = cell.replace(SemaphoreState::EMPTY); + state.permits += permits; + state.wake(); + cell.set(state); + }); + } + } + + fn set(&self, permits: usize) { + self.state.lock(|cell| { + let mut state = cell.replace(SemaphoreState::EMPTY); + if permits > state.permits { + state.wake(); + } + state.permits = permits; + cell.set(state); + }); + } +} + +struct SemaphoreState { + permits: usize, + waker: WakerRegistration, +} + +impl SemaphoreState { + const EMPTY: SemaphoreState = SemaphoreState { + permits: 0, + waker: WakerRegistration::new(), + }; + + fn register(&mut self, w: &Waker) { + self.waker.register(w); + } + + fn take(&mut self, mut permits: usize, acquire_all: bool) -> Option { + if self.permits < permits { + None + } else { + if acquire_all { + permits = self.permits; + } + self.permits -= permits; + Some(permits) + } + } + + fn wake(&mut self) { + self.waker.wake(); + } +} + +/// A fair [`Semaphore`] implementation. +/// +/// Tasks are allowed to acquire permits in FIFO order. A task waiting to acquire +/// a large number of permits will prevent other tasks from acquiring any permits +/// until its request is satisfied. +/// +/// Up to `N` tasks may attempt to acquire permits concurrently. If additional +/// tasks attempt to acquire a permit, a [`WaitQueueFull`] error will be returned. +pub struct FairSemaphore +where + M: RawMutex, +{ + state: Mutex>>, +} + +impl Default for FairSemaphore +where + M: RawMutex, +{ + fn default() -> Self { + Self::new(0) + } +} + +impl FairSemaphore +where + M: RawMutex, +{ + /// Create a new `FairSemaphore`. + pub const fn new(permits: usize) -> Self { + Self { + state: Mutex::new(RefCell::new(FairSemaphoreState::new(permits))), + } + } + + #[cfg(test)] + fn permits(&self) -> usize { + self.state.lock(|cell| cell.borrow().permits) + } + + fn poll_acquire( + &self, + permits: usize, + acquire_all: bool, + cx: Option<(&mut Option, &Waker)>, + ) -> Poll, WaitQueueFull>> { + let ticket = cx.as_ref().map(|(x, _)| **x).unwrap_or(None); + self.state.lock(|cell| { + let mut state = cell.borrow_mut(); + if let Some(permits) = state.take(ticket, permits, acquire_all) { + Poll::Ready(Ok(SemaphoreReleaser { + semaphore: self, + permits, + })) + } else if let Some((ticket_ref, waker)) = cx { + match state.register(ticket, waker) { + Ok(ticket) => { + *ticket_ref = Some(ticket); + Poll::Pending + } + Err(err) => Poll::Ready(Err(err)), + } + } else { + Poll::Pending + } + }) + } +} + +/// An error indicating the [`FairSemaphore`]'s wait queue is full. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct WaitQueueFull; + +impl Semaphore for FairSemaphore { + type Error = WaitQueueFull; + + fn acquire(&self, permits: usize) -> impl Future, Self::Error>> { + FairAcquire { + sema: self, + permits, + ticket: None, + } + } + + fn try_acquire(&self, permits: usize) -> Option> { + match self.poll_acquire(permits, false, None) { + Poll::Ready(Ok(x)) => Some(x), + _ => None, + } + } + + fn acquire_all(&self, min: usize) -> impl Future, Self::Error>> { + FairAcquireAll { + sema: self, + min, + ticket: None, + } + } + + fn try_acquire_all(&self, min: usize) -> Option> { + match self.poll_acquire(min, true, None) { + Poll::Ready(Ok(x)) => Some(x), + _ => None, + } + } + + fn release(&self, permits: usize) { + if permits > 0 { + self.state.lock(|cell| { + let mut state = cell.borrow_mut(); + state.permits += permits; + state.wake(); + }); + } + } + + fn set(&self, permits: usize) { + self.state.lock(|cell| { + let mut state = cell.borrow_mut(); + if permits > state.permits { + state.wake(); + } + state.permits = permits; + }); + } +} + +struct FairAcquire<'a, M: RawMutex, const N: usize> { + sema: &'a FairSemaphore, + permits: usize, + ticket: Option, +} + +impl<'a, M: RawMutex, const N: usize> Drop for FairAcquire<'a, M, N> { + fn drop(&mut self) { + self.sema + .state + .lock(|cell| cell.borrow_mut().cancel(self.ticket.take())); + } +} + +impl<'a, M: RawMutex, const N: usize> core::future::Future for FairAcquire<'a, M, N> { + type Output = Result>, WaitQueueFull>; + + fn poll(mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + self.sema + .poll_acquire(self.permits, false, Some((&mut self.ticket, cx.waker()))) + } +} + +struct FairAcquireAll<'a, M: RawMutex, const N: usize> { + sema: &'a FairSemaphore, + min: usize, + ticket: Option, +} + +impl<'a, M: RawMutex, const N: usize> Drop for FairAcquireAll<'a, M, N> { + fn drop(&mut self) { + self.sema + .state + .lock(|cell| cell.borrow_mut().cancel(self.ticket.take())); + } +} + +impl<'a, M: RawMutex, const N: usize> core::future::Future for FairAcquireAll<'a, M, N> { + type Output = Result>, WaitQueueFull>; + + fn poll(mut self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll { + self.sema + .poll_acquire(self.min, true, Some((&mut self.ticket, cx.waker()))) + } +} + +struct FairSemaphoreState { + permits: usize, + next_ticket: usize, + wakers: Deque, N>, +} + +impl FairSemaphoreState { + /// Create a new empty instance + const fn new(permits: usize) -> Self { + Self { + permits, + next_ticket: 0, + wakers: Deque::new(), + } + } + + /// Register a waker. If the queue is full the function returns an error + fn register(&mut self, ticket: Option, w: &Waker) -> Result { + self.pop_canceled(); + + match ticket { + None => { + let ticket = self.next_ticket.wrapping_add(self.wakers.len()); + self.wakers.push_back(Some(w.clone())).or(Err(WaitQueueFull))?; + Ok(ticket) + } + Some(ticket) => { + self.set_waker(ticket, Some(w.clone())); + Ok(ticket) + } + } + } + + fn cancel(&mut self, ticket: Option) { + if let Some(ticket) = ticket { + self.set_waker(ticket, None); + } + } + + fn set_waker(&mut self, ticket: usize, waker: Option) { + let i = ticket.wrapping_sub(self.next_ticket); + if i < self.wakers.len() { + let (a, b) = self.wakers.as_mut_slices(); + let x = if i < a.len() { &mut a[i] } else { &mut b[i - a.len()] }; + *x = waker; + } + } + + fn take(&mut self, ticket: Option, mut permits: usize, acquire_all: bool) -> Option { + self.pop_canceled(); + + if permits > self.permits { + return None; + } + + match ticket { + Some(n) if n != self.next_ticket => return None, + None if !self.wakers.is_empty() => return None, + _ => (), + } + + if acquire_all { + permits = self.permits; + } + self.permits -= permits; + + if ticket.is_some() { + self.pop(); + if self.permits > 0 { + self.wake(); + } + } + + Some(permits) + } + + fn pop_canceled(&mut self) { + while let Some(None) = self.wakers.front() { + self.pop(); + } + } + + /// Panics if `self.wakers` is empty + fn pop(&mut self) { + self.wakers.pop_front().unwrap(); + self.next_ticket = self.next_ticket.wrapping_add(1); + } + + fn wake(&mut self) { + self.pop_canceled(); + + if let Some(Some(waker)) = self.wakers.front() { + waker.wake_by_ref(); + } + } +} + +#[cfg(test)] +mod tests { + mod greedy { + use core::pin::pin; + + use futures_util::poll; + + use super::super::*; + use crate::blocking_mutex::raw::NoopRawMutex; + + #[test] + fn try_acquire() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn disarm() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.disarm(), 1); + assert_eq!(semaphore.permits(), 2); + } + + #[futures_test::test] + async fn acquire() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.acquire(1).await.unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn try_acquire_all() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire_all(1).unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[futures_test::test] + async fn acquire_all() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.acquire_all(1).await.unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[test] + fn release() { + let semaphore = GreedySemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.release(2); + assert_eq!(semaphore.permits(), 5); + } + + #[test] + fn set() { + let semaphore = GreedySemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.set(2); + assert_eq!(semaphore.permits(), 2); + } + + #[test] + fn contested() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + let b = semaphore.try_acquire(3); + assert!(b.is_none()); + + core::mem::drop(a); + + let b = semaphore.try_acquire(3); + assert!(b.is_some()); + } + + #[futures_test::test] + async fn greedy() { + let semaphore = GreedySemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + + let b_fut = semaphore.acquire(3); + let mut b_fut = pin!(b_fut); + let b = poll!(b_fut.as_mut()); + assert!(b.is_pending()); + + // Succeed even through `b` is waiting + let c = semaphore.try_acquire(1); + assert!(c.is_some()); + + let b = poll!(b_fut.as_mut()); + assert!(b.is_pending()); + + core::mem::drop(a); + + let b = poll!(b_fut.as_mut()); + assert!(b.is_pending()); + + core::mem::drop(c); + + let b = poll!(b_fut.as_mut()); + assert!(b.is_ready()); + } + } + + mod fair { + use core::pin::pin; + use core::time::Duration; + + use futures_executor::ThreadPool; + use futures_timer::Delay; + use futures_util::poll; + use futures_util::task::SpawnExt; + use static_cell::StaticCell; + + use super::super::*; + use crate::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; + + #[test] + fn try_acquire() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn disarm() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + assert_eq!(a.disarm(), 1); + assert_eq!(semaphore.permits(), 2); + } + + #[futures_test::test] + async fn acquire() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.acquire(1).await.unwrap(); + assert_eq!(a.permits(), 1); + assert_eq!(semaphore.permits(), 2); + + core::mem::drop(a); + assert_eq!(semaphore.permits(), 3); + } + + #[test] + fn try_acquire_all() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire_all(1).unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[futures_test::test] + async fn acquire_all() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.acquire_all(1).await.unwrap(); + assert_eq!(a.permits(), 3); + assert_eq!(semaphore.permits(), 0); + } + + #[test] + fn release() { + let semaphore = FairSemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.release(2); + assert_eq!(semaphore.permits(), 5); + } + + #[test] + fn set() { + let semaphore = FairSemaphore::::new(3); + assert_eq!(semaphore.permits(), 3); + semaphore.set(2); + assert_eq!(semaphore.permits(), 2); + } + + #[test] + fn contested() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1).unwrap(); + let b = semaphore.try_acquire(3); + assert!(b.is_none()); + + core::mem::drop(a); + + let b = semaphore.try_acquire(3); + assert!(b.is_some()); + } + + #[futures_test::test] + async fn fairness() { + let semaphore = FairSemaphore::::new(3); + + let a = semaphore.try_acquire(1); + assert!(a.is_some()); + + let b_fut = semaphore.acquire(3); + let mut b_fut = pin!(b_fut); + let b = poll!(b_fut.as_mut()); // Poll `b_fut` once so it is registered + assert!(b.is_pending()); + + let c = semaphore.try_acquire(1); + assert!(c.is_none()); + + let c_fut = semaphore.acquire(1); + let mut c_fut = pin!(c_fut); + let c = poll!(c_fut.as_mut()); // Poll `c_fut` once so it is registered + assert!(c.is_pending()); // `c` is blocked behind `b` + + let d = semaphore.acquire(1).await; + assert!(matches!(d, Err(WaitQueueFull))); + + core::mem::drop(a); + + let c = poll!(c_fut.as_mut()); + assert!(c.is_pending()); // `c` is still blocked behind `b` + + let b = poll!(b_fut.as_mut()); + assert!(b.is_ready()); + + let c = poll!(c_fut.as_mut()); + assert!(c.is_pending()); // `c` is still blocked behind `b` + + core::mem::drop(b); + + let c = poll!(c_fut.as_mut()); + assert!(c.is_ready()); + } + + #[futures_test::test] + async fn wakers() { + let executor = ThreadPool::new().unwrap(); + + static SEMAPHORE: StaticCell> = StaticCell::new(); + let semaphore = &*SEMAPHORE.init(FairSemaphore::new(3)); + + let a = semaphore.try_acquire(2); + assert!(a.is_some()); + + let b_task = executor + .spawn_with_handle(async move { semaphore.acquire(2).await }) + .unwrap(); + while semaphore.state.lock(|x| x.borrow().wakers.is_empty()) { + Delay::new(Duration::from_millis(50)).await; + } + + let c_task = executor + .spawn_with_handle(async move { semaphore.acquire(1).await }) + .unwrap(); + + core::mem::drop(a); + + let b = b_task.await.unwrap(); + assert_eq!(b.permits(), 2); + + let c = c_task.await.unwrap(); + assert_eq!(c.permits(), 1); + } + } +} diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs new file mode 100644 index 0000000..a0f4b5a --- /dev/null +++ b/embassy-sync/src/signal.rs @@ -0,0 +1,140 @@ +//! A synchronization primitive for passing the latest value to a task. +use core::cell::Cell; +use core::future::{poll_fn, Future}; +use core::task::{Context, Poll, Waker}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; + +/// Single-slot signaling primitive. +/// +/// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except +/// "sending" to it (calling [`Signal::signal`]) when full will overwrite the previous value instead +/// of waiting for the receiver to pop the previous value. +/// +/// It is useful for sending data between tasks when the receiver only cares about +/// the latest data, and therefore it's fine to "lose" messages. This is often the case for "state" +/// updates. +/// +/// For more advanced use cases, you might want to use [`Channel`](crate::channel::Channel) instead. +/// +/// Signals are generally declared as `static`s and then borrowed as required. +/// +/// ``` +/// use embassy_sync::signal::Signal; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// enum SomeCommand { +/// On, +/// Off, +/// } +/// +/// static SOME_SIGNAL: Signal = Signal::new(); +/// ``` +pub struct Signal +where + M: RawMutex, +{ + state: Mutex>>, +} + +enum State { + None, + Waiting(Waker), + Signaled(T), +} + +impl Signal +where + M: RawMutex, +{ + /// Create a new `Signal`. + pub const fn new() -> Self { + Self { + state: Mutex::new(Cell::new(State::None)), + } + } +} + +impl Default for Signal +where + M: RawMutex, +{ + fn default() -> Self { + Self::new() + } +} + +impl Signal +where + M: RawMutex, +{ + /// Mark this Signal as signaled. + pub fn signal(&self, val: T) { + self.state.lock(|cell| { + let state = cell.replace(State::Signaled(val)); + if let State::Waiting(waker) = state { + waker.wake(); + } + }) + } + + /// Remove the queued value in this `Signal`, if any. + pub fn reset(&self) { + self.state.lock(|cell| cell.set(State::None)); + } + + fn poll_wait(&self, cx: &mut Context<'_>) -> Poll { + self.state.lock(|cell| { + let state = cell.replace(State::None); + match state { + State::None => { + cell.set(State::Waiting(cx.waker().clone())); + Poll::Pending + } + State::Waiting(w) if w.will_wake(cx.waker()) => { + cell.set(State::Waiting(w)); + Poll::Pending + } + State::Waiting(w) => { + cell.set(State::Waiting(cx.waker().clone())); + w.wake(); + Poll::Pending + } + State::Signaled(res) => Poll::Ready(res), + } + }) + } + + /// Future that completes when this Signal has been signaled. + pub fn wait(&self) -> impl Future + '_ { + poll_fn(move |cx| self.poll_wait(cx)) + } + + /// non-blocking method to try and take the signal value. + pub fn try_take(&self) -> Option { + self.state.lock(|cell| { + let state = cell.replace(State::None); + match state { + State::Signaled(res) => Some(res), + state => { + cell.set(state); + None + } + } + }) + } + + /// non-blocking method to check whether this signal has been signaled. This does not clear the signal. + pub fn signaled(&self) -> bool { + self.state.lock(|cell| { + let state = cell.replace(State::None); + + let res = matches!(state, State::Signaled(_)); + + cell.set(state); + + res + }) + } +} diff --git a/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy-sync/src/waitqueue/atomic_waker.rs new file mode 100644 index 0000000..231902c --- /dev/null +++ b/embassy-sync/src/waitqueue/atomic_waker.rs @@ -0,0 +1,63 @@ +use core::cell::Cell; +use core::task::Waker; + +use crate::blocking_mutex::raw::{CriticalSectionRawMutex, RawMutex}; +use crate::blocking_mutex::Mutex; + +/// Utility struct to register and wake a waker. +pub struct GenericAtomicWaker { + waker: Mutex>>, +} + +impl GenericAtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new(mutex: M) -> Self { + Self { + waker: Mutex::const_new(mutex, Cell::new(None)), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.lock(|cell| { + cell.set(match cell.replace(None) { + Some(w2) if (w2.will_wake(w)) => Some(w2), + _ => Some(w.clone()), + }) + }) + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + self.waker.lock(|cell| { + if let Some(w) = cell.replace(None) { + w.wake_by_ref(); + cell.set(Some(w)); + } + }) + } +} + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: GenericAtomicWaker, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: GenericAtomicWaker::new(CriticalSectionRawMutex::new()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.register(w); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + self.waker.wake(); + } +} diff --git a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs new file mode 100644 index 0000000..5c6a96e --- /dev/null +++ b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs @@ -0,0 +1,30 @@ +use core::ptr; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::task::Waker; + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: AtomicPtr<()>, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.store(w.as_turbo_ptr().as_ptr() as _, Ordering::Release); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + if let Some(ptr) = NonNull::new(self.waker.load(Ordering::Acquire)) { + unsafe { Waker::from_turbo_ptr(ptr) }.wake(); + } + } +} diff --git a/embassy-sync/src/waitqueue/mod.rs b/embassy-sync/src/waitqueue/mod.rs new file mode 100644 index 0000000..6b0b0c6 --- /dev/null +++ b/embassy-sync/src/waitqueue/mod.rs @@ -0,0 +1,11 @@ +//! Async low-level wait queues + +#[cfg_attr(feature = "turbowakers", path = "atomic_waker_turbo.rs")] +mod atomic_waker; +pub use atomic_waker::*; + +mod waker_registration; +pub use waker_registration::*; + +mod multi_waker; +pub use multi_waker::*; diff --git a/embassy-sync/src/waitqueue/multi_waker.rs b/embassy-sync/src/waitqueue/multi_waker.rs new file mode 100644 index 0000000..0e520bf --- /dev/null +++ b/embassy-sync/src/waitqueue/multi_waker.rs @@ -0,0 +1,58 @@ +use core::task::Waker; + +use heapless::Vec; + +/// Utility struct to register and wake multiple wakers. +pub struct MultiWakerRegistration { + wakers: Vec, +} + +impl MultiWakerRegistration { + /// Create a new empty instance + pub const fn new() -> Self { + Self { wakers: Vec::new() } + } + + /// Register a waker. If the buffer is full the function returns it in the error + pub fn register(&mut self, w: &Waker) { + // If we already have some waker that wakes the same task as `w`, do nothing. + // This avoids cloning wakers, and avoids unnecessary mass-wakes. + for w2 in &self.wakers { + if w.will_wake(w2) { + return; + } + } + + if self.wakers.is_full() { + // All waker slots were full. It's a bit inefficient, but we can wake everything. + // Any future that is still active will simply reregister. + // This won't happen a lot, so it's ok. + self.wake(); + } + + if self.wakers.push(w.clone()).is_err() { + // This can't happen unless N=0 + // (Either `wakers` wasn't full, or it was in which case `wake()` empied it) + panic!("tried to push a waker to a zero-length MultiWakerRegistration") + } + } + + /// Wake all registered wakers. This clears the buffer + pub fn wake(&mut self) { + // heapless::Vec has no `drain()`, do it unsafely ourselves... + + // First set length to 0, without dropping the contents. + // This is necessary for soundness: if wake() panics and we're using panic=unwind. + // Setting len=0 upfront ensures other code can't observe the vec in an inconsistent state. + // (it'll leak wakers, but that's not UB) + let len = self.wakers.len(); + unsafe { self.wakers.set_len(0) } + + for i in 0..len { + // Move a waker out of the vec. + let waker = unsafe { self.wakers.as_mut_ptr().add(i).read() }; + // Wake it by value, which consumes (drops) it. + waker.wake(); + } + } +} diff --git a/embassy-sync/src/waitqueue/waker_registration.rs b/embassy-sync/src/waitqueue/waker_registration.rs new file mode 100644 index 0000000..9b666e7 --- /dev/null +++ b/embassy-sync/src/waitqueue/waker_registration.rs @@ -0,0 +1,52 @@ +use core::mem; +use core::task::Waker; + +/// Utility struct to register and wake a waker. +#[derive(Debug, Default)] +pub struct WakerRegistration { + waker: Option, +} + +impl WakerRegistration { + /// Create a new `WakerRegistration`. + pub const fn new() -> Self { + Self { waker: None } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&mut self, w: &Waker) { + match self.waker { + // Optimization: If both the old and new Wakers wake the same task, we can simply + // keep the old waker, skipping the clone. (In most executor implementations, + // cloning a waker is somewhat expensive, comparable to cloning an Arc). + Some(ref w2) if (w2.will_wake(w)) => {} + _ => { + // clone the new waker and store it + if let Some(old_waker) = mem::replace(&mut self.waker, Some(w.clone())) { + // We had a waker registered for another task. Wake it, so the other task can + // reregister itself if it's still interested. + // + // If two tasks are waiting on the same thing concurrently, this will cause them + // to wake each other in a loop fighting over this WakerRegistration. This wastes + // CPU but things will still work. + // + // If the user wants to have two tasks waiting on the same thing they should use + // a more appropriate primitive that can store multiple wakers. + old_waker.wake() + } + } + } + } + + /// Wake the registered waker, if any. + pub fn wake(&mut self) { + if let Some(w) = self.waker.take() { + w.wake() + } + } + + /// Returns true if a waker is currently registered + pub fn occupied(&self) -> bool { + self.waker.is_some() + } +} diff --git a/embassy-sync/src/watch.rs b/embassy-sync/src/watch.rs new file mode 100644 index 0000000..e76646c --- /dev/null +++ b/embassy-sync/src/watch.rs @@ -0,0 +1,1121 @@ +//! A synchronization primitive for passing the latest value to **multiple** receivers. + +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::MultiWakerRegistration; + +/// The `Watch` is a single-slot signaling primitive that allows multiple receivers to concurrently await +/// changes to the value. Unlike a [`Signal`](crate::signal::Signal), `Watch` supports multiple receivers, +/// and unlike a [`PubSubChannel`](crate::pubsub::PubSubChannel), `Watch` immediately overwrites the previous +/// value when a new one is sent, without waiting for all receivers to read the previous value. +/// +/// This makes `Watch` particularly useful when a single task updates a value or "state", and multiple other tasks +/// need to be notified about changes to this value asynchronously. Receivers may "lose" stale values, as they are +/// always provided with the latest value. +/// +/// Typically, `Watch` instances are declared as `static`, and a [`Sender`] and [`Receiver`] +/// (or [`DynSender`] and/or [`DynReceiver`]) are obtained where relevant. An [`AnonReceiver`] +/// and [`DynAnonReceiver`] are also available, which do not increase the receiver count for the +/// channel, and unwrapping is therefore not required, but it is not possible to `.await` the channel. +/// ``` +/// +/// use futures_executor::block_on; +/// use embassy_sync::watch::Watch; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +/// +/// let f = async { +/// +/// static WATCH: Watch = Watch::new(); +/// +/// // Obtain receivers and sender +/// let mut rcv0 = WATCH.receiver().unwrap(); +/// let mut rcv1 = WATCH.dyn_receiver().unwrap(); +/// let mut snd = WATCH.sender(); +/// +/// // No more receivers, and no update +/// assert!(WATCH.receiver().is_none()); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(10); +/// +/// // Receive the new value (async or try) +/// assert_eq!(rcv0.changed().await, 10); +/// assert_eq!(rcv1.try_changed(), Some(10)); +/// +/// // No update +/// assert_eq!(rcv0.try_changed(), None); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// snd.send(20); +/// +/// // Using `get` marks the value as seen +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.try_changed(), None); +/// +/// // But `get` also returns when unchanged +/// assert_eq!(rcv1.get().await, 20); +/// assert_eq!(rcv1.get().await, 20); +/// +/// }; +/// block_on(f); +/// ``` +pub struct Watch { + mutex: Mutex>>, +} + +struct WatchState { + data: Option, + current_id: u64, + wakers: MultiWakerRegistration, + receiver_count: usize, +} + +trait SealedWatchBehavior { + /// Poll the `Watch` for the current value, making it as seen. + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for the value if it matches the predicate function + /// `f`, making it as seen. + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Poll the `Watch` for a changed value, marking it as seen, if an id is given. + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed, marking it as seen. + fn try_changed(&self, id: &mut u64) -> Option; + + /// Poll the `Watch` for a changed value that matches the predicate function + /// `f`, marking it as seen. + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll; + + /// Tries to retrieve the value of the `Watch` if it has changed and matches the + /// predicate function `f`, marking it as seen. + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Used when a receiver is dropped to decrement the receiver count. + /// + /// ## This method should not be called by the user. + fn drop_receiver(&self); + + /// Clears the value of the `Watch`. + fn clear(&self); + + /// Sends a new value to the `Watch`. + fn send(&self, val: T); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_modify(&self, f: &mut dyn Fn(&mut Option)); + + /// Modify the value of the `Watch` using a closure. Returns `false` if the + /// `Watch` does not already contain a value. + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool); +} + +/// A trait representing the 'inner' behavior of the `Watch`. +#[allow(private_bounds)] +pub trait WatchBehavior: SealedWatchBehavior { + /// Tries to get the value of the `Watch`, marking it as seen, if an id is given. + fn try_get(&self, id: Option<&mut u64>) -> Option; + + /// Tries to get the value of the `Watch` if it matches the predicate function + /// `f`, marking it as seen. + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option; + + /// Checks if the `Watch` is been initialized with a value. + fn contains_value(&self) -> bool; +} + +impl SealedWatchBehavior for Watch { + fn poll_get(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match &s.data { + Some(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + None => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_get_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match s.data { + Some(ref data) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn poll_changed(&self, id: &mut u64, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed(&self, id: &mut u64) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.current_id > *id { + true => { + *id = s.current_id; + s.data.clone() + } + false => None, + } + }) + } + + fn poll_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool, cx: &mut Context<'_>) -> Poll { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + Poll::Ready(data.clone()) + } + _ => { + s.wakers.register(cx.waker()); + Poll::Pending + } + } + }) + } + + fn try_changed_and(&self, id: &mut u64, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match (&s.data, s.current_id > *id) { + (Some(data), true) if f(data) => { + *id = s.current_id; + s.data.clone() + } + _ => None, + } + }) + } + + fn drop_receiver(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.receiver_count -= 1; + }) + } + + fn clear(&self) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = None; + }) + } + + fn send(&self, val: T) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + s.data = Some(val); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_modify(&self, f: &mut dyn Fn(&mut Option)) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + f(&mut s.data); + s.current_id += 1; + s.wakers.wake(); + }) + } + + fn send_if_modified(&self, f: &mut dyn Fn(&mut Option) -> bool) { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if f(&mut s.data) { + s.current_id += 1; + s.wakers.wake(); + } + }) + } +} + +impl WatchBehavior for Watch { + fn try_get(&self, id: Option<&mut u64>) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + if let Some(id) = id { + *id = s.current_id; + } + s.data.clone() + }) + } + + fn try_get_and(&self, id: Option<&mut u64>, f: &mut dyn Fn(&T) -> bool) -> Option { + self.mutex.lock(|state| { + let s = state.borrow(); + match s.data { + Some(ref data) if f(data) => { + if let Some(id) = id { + *id = s.current_id; + } + Some(data.clone()) + } + _ => None, + } + }) + } + + fn contains_value(&self) -> bool { + self.mutex.lock(|state| state.borrow().data.is_some()) + } +} + +impl Watch { + /// Create a new `Watch` channel. + pub const fn new() -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: None, + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new `Watch` channel with default data. + pub const fn new_with(data: T) -> Self { + Self { + mutex: Mutex::new(RefCell::new(WatchState { + data: Some(data), + current_id: 0, + wakers: MultiWakerRegistration::new(), + receiver_count: 0, + })), + } + } + + /// Create a new [`Sender`] for the `Watch`. + pub fn sender(&self) -> Sender<'_, M, T, N> { + Sender(Snd::new(self)) + } + + /// Create a new [`DynSender`] for the `Watch`. + pub fn dyn_sender(&self) -> DynSender<'_, T> { + DynSender(Snd::new(self)) + } + + /// Try to create a new [`Receiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(Receiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`DynReceiver`] for the `Watch`. If the + /// maximum number of receivers has been reached, `None` is returned. + pub fn dyn_receiver(&self) -> Option> { + self.mutex.lock(|state| { + let mut s = state.borrow_mut(); + if s.receiver_count < N { + s.receiver_count += 1; + Some(DynReceiver(Rcv::new(self, 0))) + } else { + None + } + }) + } + + /// Try to create a new [`AnonReceiver`] for the `Watch`. + pub fn anon_receiver(&self) -> AnonReceiver<'_, M, T, N> { + AnonReceiver(AnonRcv::new(self, 0)) + } + + /// Try to create a new [`DynAnonReceiver`] for the `Watch`. + pub fn dyn_anon_receiver(&self) -> DynAnonReceiver<'_, T> { + DynAnonReceiver(AnonRcv::new(self, 0)) + } + + /// Returns the message ID of the latest message sent to the `Watch`. + /// + /// This counter is monotonic, and is incremented every time a new message is sent. + pub fn get_msg_id(&self) -> u64 { + self.mutex.lock(|state| state.borrow().current_id) + } + + /// Tries to get the value of the `Watch`. + pub fn try_get(&self) -> Option { + WatchBehavior::try_get(self, None) + } + + /// Tries to get the value of the `Watch` if it matches the predicate function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + WatchBehavior::try_get_and(self, None, &mut f) + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Snd<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Clone for Snd<'a, T, W> { + fn clone(&self) -> Self { + Self { + watch: self.watch, + _phantom: PhantomData, + } + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Snd<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W) -> Self { + Self { + watch, + _phantom: PhantomData, + } + } + + /// Sends a new value to the `Watch`. + pub fn send(&self, val: T) { + self.watch.send(val) + } + + /// Clears the value of the `Watch`. + /// This will cause calls to [`Rcv::get`] to be pending. + pub fn clear(&self) { + self.watch.clear() + } + + /// Tries to retrieve the value of the `Watch`. + pub fn try_get(&self) -> Option { + self.watch.try_get(None) + } + + /// Tries to peek the current value of the `Watch` if it matches the predicate + /// function `f`. + pub fn try_get_and(&self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(None, &mut f) + } + + /// Returns true if the `Watch` contains a value. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } + + /// Modify the value of the `Watch` using a closure. + pub fn send_modify(&self, mut f: F) + where + F: Fn(&mut Option), + { + self.watch.send_modify(&mut f) + } + + /// Modify the value of the `Watch` using a closure. The closure must return + /// `true` if the value was modified, which notifies all receivers. + pub fn send_if_modified(&self, mut f: F) + where + F: Fn(&mut Option) -> bool, + { + self.watch.send_if_modified(&mut f) + } +} + +/// A sender of a `Watch` channel. +/// +/// For a simpler type definition, consider [`DynSender`] at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct Sender<'a, M: RawMutex, T: Clone, const N: usize>(Snd<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Clone for Sender<'a, M, T, N> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Sender<'a, M, T, N> { + /// Converts the `Sender` into a [`DynSender`]. + pub fn as_dyn(self) -> DynSender<'a, T> { + DynSender(Snd::new(self.watch)) + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Sender<'a, M, T, N> { + fn into(self) -> DynSender<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Sender<'a, M, T, N> { + type Target = Snd<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Sender<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A sender which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Sender`] with a simpler type definition, +pub struct DynSender<'a, T: Clone>(Snd<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Clone for DynSender<'a, T> { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl<'a, T: Clone> Deref for DynSender<'a, T> { + type Target = Snd<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynSender<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver can `.await` a change in the `Watch` value. +pub struct Rcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Rcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Returns the current value of the `Watch` once it is initialized, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub fn get(&mut self) -> impl Future + '_ { + poll_fn(|cx| self.watch.poll_get(&mut self.at_id, cx)) + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Returns the value of the `Watch` if it matches the predicate function `f`, + /// or waits for it to match, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn get_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_get_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Waits for the `Watch` to change and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed(&mut self) -> T { + poll_fn(|cx| self.watch.poll_changed(&mut self.at_id, cx)).await + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Waits for the `Watch` to change to a value which satisfies the predicate + /// function `f` and returns the new value, marking it as seen. + /// + /// **Note**: Futures do nothing unless you `.await` or poll them. + pub async fn changed_and(&mut self, mut f: F) -> T + where + F: Fn(&T) -> bool, + { + poll_fn(|cx| self.watch.poll_changed_and(&mut self.at_id, &mut f, cx)).await + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> Drop for Rcv<'a, T, W> { + fn drop(&mut self) { + self.watch.drop_receiver(); + } +} + +/// A anonymous receiver can NOT `.await` a change in the `Watch` value. +pub struct AnonRcv<'a, T: Clone, W: WatchBehavior + ?Sized> { + watch: &'a W, + at_id: u64, + _phantom: PhantomData, +} + +impl<'a, T: Clone, W: WatchBehavior + ?Sized> AnonRcv<'a, T, W> { + /// Creates a new `Receiver` with a reference to the `Watch`. + fn new(watch: &'a W, at_id: u64) -> Self { + Self { + watch, + at_id, + _phantom: PhantomData, + } + } + + /// Tries to get the current value of the `Watch` without waiting, marking it as seen. + pub fn try_get(&mut self) -> Option { + self.watch.try_get(Some(&mut self.at_id)) + } + + /// Tries to get the current value of the `Watch` if it matches the predicate + /// function `f` without waiting, marking it as seen. + pub fn try_get_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_get_and(Some(&mut self.at_id), &mut f) + } + + /// Tries to get the new value of the watch without waiting, marking it as seen. + pub fn try_changed(&mut self) -> Option { + self.watch.try_changed(&mut self.at_id) + } + + /// Tries to get the new value of the watch which satisfies the predicate + /// function `f` and returns the new value without waiting, marking it as seen. + pub fn try_changed_and(&mut self, mut f: F) -> Option + where + F: Fn(&T) -> bool, + { + self.watch.try_changed_and(&mut self.at_id, &mut f) + } + + /// Checks if the `Watch` contains a value. If this returns true, + /// then awaiting [`Rcv::get`] will return immediately. + pub fn contains_value(&self) -> bool { + self.watch.contains_value() + } +} + +/// A receiver of a `Watch` channel. +pub struct Receiver<'a, M: RawMutex, T: Clone, const N: usize>(Rcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> Receiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynReceiver<'a, T> { + let rcv = DynReceiver(Rcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for Receiver<'a, M, T, N> { + fn into(self) -> DynReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for Receiver<'a, M, T, N> { + type Target = Rcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for Receiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`Receiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynReceiver<'a, T: Clone>(Rcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynReceiver<'a, T> { + type Target = Rcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver of a `Watch` channel that cannot `.await` values. +pub struct AnonReceiver<'a, M: RawMutex, T: Clone, const N: usize>(AnonRcv<'a, T, Watch>); + +impl<'a, M: RawMutex, T: Clone, const N: usize> AnonReceiver<'a, M, T, N> { + /// Converts the `Receiver` into a [`DynReceiver`]. + pub fn as_dyn(self) -> DynAnonReceiver<'a, T> { + let rcv = DynAnonReceiver(AnonRcv::new(self.0.watch, self.at_id)); + core::mem::forget(self); // Ensures the destructor is not called + rcv + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Into> for AnonReceiver<'a, M, T, N> { + fn into(self) -> DynAnonReceiver<'a, T> { + self.as_dyn() + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> Deref for AnonReceiver<'a, M, T, N> { + type Target = AnonRcv<'a, T, Watch>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, M: RawMutex, T: Clone, const N: usize> DerefMut for AnonReceiver<'a, M, T, N> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// A receiver that cannot `.await` value, which holds a **dynamic** reference to a `Watch` channel. +/// +/// This is an alternative to [`AnonReceiver`] with a simpler type definition, at the expense of +/// some runtime performance due to dynamic dispatch. +pub struct DynAnonReceiver<'a, T: Clone>(AnonRcv<'a, T, dyn WatchBehavior + 'a>); + +impl<'a, T: Clone> Deref for DynAnonReceiver<'a, T> { + type Target = AnonRcv<'a, T, dyn WatchBehavior + 'a>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'a, T: Clone> DerefMut for DynAnonReceiver<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[cfg(test)] +mod tests { + use futures_executor::block_on; + + use super::Watch; + use crate::blocking_mutex::raw::CriticalSectionRawMutex; + + #[test] + fn multiple_sends() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.changed().await, 10); + + // Receive another value + snd.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + + // No update + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn all_try_get() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(WATCH.try_get(), None); + assert_eq!(rcv.try_get(), None); + assert_eq!(snd.try_get(), None); + + // Receive the new value + snd.send(10); + assert_eq!(WATCH.try_get(), Some(10)); + assert_eq!(rcv.try_get(), Some(10)); + assert_eq!(snd.try_get(), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x > &5), Some(10)); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(10)); + assert_eq!(snd.try_get_and(|x| x > &5), Some(10)); + + assert_eq!(WATCH.try_get_and(|x| x < &5), None); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert_eq!(snd.try_get_and(|x| x < &5), None); + }; + block_on(f); + } + + #[test] + fn once_lock_like() { + let f = async { + static CONFIG0: u8 = 10; + static CONFIG1: u8 = 20; + + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Not initialized + assert_eq!(rcv.try_changed(), None); + + // Receive the new value + snd.send(&CONFIG0); + let rcv0 = rcv.changed().await; + assert_eq!(rcv0, &10); + + // Receive another value + snd.send(&CONFIG1); + let rcv1 = rcv.try_changed(); + assert_eq!(rcv1, Some(&20)); + + // No update + assert_eq!(rcv.try_changed(), None); + + // Ensure similarity with original static + assert_eq!(rcv0, &CONFIG0); + assert_eq!(rcv1, Some(&CONFIG1)); + }; + block_on(f); + } + + #[test] + fn sender_modify() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Receive the new value + snd.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Modify the value inplace + snd.send_modify(|opt| { + if let Some(inner) = opt { + *inner += 5; + } + }); + + // Get the modified value + assert_eq!(rcv.try_changed(), Some(15)); + assert_eq!(rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn predicate_fn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + snd.send(15); + assert_eq!(rcv.try_get_and(|x| x > &5), Some(15)); + assert_eq!(rcv.try_get_and(|x| x < &5), None); + assert!(rcv.try_changed().is_none()); + + snd.send(20); + assert_eq!(rcv.try_changed_and(|x| x > &5), Some(20)); + assert_eq!(rcv.try_changed_and(|x| x > &5), None); + + snd.send(25); + assert_eq!(rcv.try_changed_and(|x| x < &5), None); + assert_eq!(rcv.try_changed(), Some(25)); + + snd.send(30); + assert_eq!(rcv.changed_and(|x| x > &5).await, 30); + assert_eq!(rcv.get_and(|x| x > &5).await, 30); + }; + block_on(f); + } + + #[test] + fn receive_after_create() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain sender and send value + let snd = WATCH.sender(); + snd.send(10); + + // Obtain receiver and receive value + let mut rcv = WATCH.receiver().unwrap(); + assert_eq!(rcv.try_changed(), Some(10)); + }; + block_on(f); + } + + #[test] + fn max_receivers_drop() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Try to create 3 receivers (only 2 can exist at once) + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Drop the first receiver + drop(rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + assert!(rcv3.is_some()); + }; + block_on(f); + } + + #[test] + fn multiple_receivers() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receivers and sender + let mut rcv0 = WATCH.receiver().unwrap(); + let mut rcv1 = WATCH.anon_receiver(); + let snd = WATCH.sender(); + + // No update for both + assert_eq!(rcv0.try_changed(), None); + assert_eq!(rcv1.try_changed(), None); + + // Send a new value + snd.send(0); + + // Both receivers receive the new value + assert_eq!(rcv0.try_changed(), Some(0)); + assert_eq!(rcv1.try_changed(), Some(0)); + }; + block_on(f); + } + + #[test] + fn clone_senders() { + let f = async { + // Obtain different ways to send + static WATCH: Watch = Watch::new(); + let snd0 = WATCH.sender(); + let snd1 = snd0.clone(); + + // Obtain Receiver + let mut rcv = WATCH.receiver().unwrap().as_dyn(); + + // Send a value from first sender + snd0.send(10); + assert_eq!(rcv.try_changed(), Some(10)); + + // Send a value from second sender + snd1.send(20); + assert_eq!(rcv.try_changed(), Some(20)); + }; + block_on(f); + } + + #[test] + fn use_dynamics() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let mut anon_rcv = WATCH.dyn_anon_receiver(); + let mut dyn_rcv = WATCH.dyn_receiver().unwrap(); + let dyn_snd = WATCH.dyn_sender(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn convert_to_dyn() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let anon_rcv = WATCH.anon_receiver(); + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // Convert to dynamic + let mut dyn_anon_rcv = anon_rcv.as_dyn(); + let mut dyn_rcv = rcv.as_dyn(); + let dyn_snd = snd.as_dyn(); + + // Send a value + dyn_snd.send(10); + + // Ensure the dynamic receiver receives the value + assert_eq!(dyn_anon_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), Some(10)); + assert_eq!(dyn_rcv.try_changed(), None); + }; + block_on(f); + } + + #[test] + fn dynamic_receiver_count() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv0 = WATCH.receiver(); + let rcv1 = WATCH.receiver(); + let rcv2 = WATCH.receiver(); + + // Ensure the first two are successful and the third is not + assert!(rcv0.is_some()); + assert!(rcv1.is_some()); + assert!(rcv2.is_none()); + + // Convert to dynamic + let dyn_rcv0 = rcv0.unwrap().as_dyn(); + + // Drop the (now dynamic) receiver + drop(dyn_rcv0); + + // Create another receiver and ensure it is successful + let rcv3 = WATCH.receiver(); + let rcv4 = WATCH.receiver(); + assert!(rcv3.is_some()); + assert!(rcv4.is_none()); + }; + block_on(f); + } + + #[test] + fn contains_value() { + let f = async { + static WATCH: Watch = Watch::new(); + + // Obtain receiver and sender + let rcv = WATCH.receiver().unwrap(); + let snd = WATCH.sender(); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), false); + assert_eq!(snd.contains_value(), false); + + // Send a value + snd.send(10); + + // check if the watch contains a value + assert_eq!(rcv.contains_value(), true); + assert_eq!(snd.contains_value(), true); + }; + block_on(f); + } +} diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs new file mode 100644 index 0000000..ad6fe74 --- /dev/null +++ b/embassy-sync/src/zerocopy_channel.rs @@ -0,0 +1,359 @@ +//! A zero-copy queue for sending values between asynchronous tasks. +//! +//! It can be used concurrently by a producer (sender) and a +//! consumer (receiver), i.e. it is an "SPSC channel". +//! +//! This queue takes a Mutex type so that various +//! targets can be attained. For example, a ThreadModeMutex can be used +//! for single-core Cortex-M targets where messages are only passed +//! between tasks running in thread mode. Similarly, a CriticalSectionMutex +//! can also be used for single-core targets where messages are to be +//! passed from exception mode e.g. out of an interrupt handler. +//! +//! This module provides a bounded channel that has a limit on the number of +//! messages that it can store, and if this limit is reached, trying to send +//! another message will result in an error being returned. + +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::task::{Context, Poll}; + +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; +use crate::waitqueue::WakerRegistration; + +/// A bounded zero-copy channel for communicating between asynchronous tasks +/// with backpressure. +/// +/// The channel will buffer up to the provided number of messages. Once the +/// buffer is full, attempts to `send` new messages will wait until a message is +/// received from the channel. +/// +/// All data sent will become available in the same order as it was sent. +/// +/// The channel requires a buffer of recyclable elements. Writing to the channel is done through +/// an `&mut T`. +pub struct Channel<'a, M: RawMutex, T> { + buf: BufferPtr, + phantom: PhantomData<&'a mut T>, + state: Mutex>, +} + +impl<'a, M: RawMutex, T> Channel<'a, M, T> { + /// Initialize a new [`Channel`]. + /// + /// The provided buffer will be used and reused by the channel's logic, and thus dictates the + /// channel's capacity. + pub fn new(buf: &'a mut [T]) -> Self { + let len = buf.len(); + assert!(len != 0); + + Self { + buf: BufferPtr(buf.as_mut_ptr()), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + capacity: len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + receive_waker: WakerRegistration::new(), + })), + } + } + + /// Creates a [`Sender`] and [`Receiver`] from an existing channel. + /// + /// Further Senders and Receivers can be created through [`Sender::borrow`] and + /// [`Receiver::borrow`] respectively. + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.state.lock(|s| s.borrow().is_full()) + } +} + +#[repr(transparent)] +struct BufferPtr(*mut T); + +impl BufferPtr { + unsafe fn add(&self, count: usize) -> *mut T { + self.0.add(count) + } +} + +unsafe impl Send for BufferPtr {} +unsafe impl Sync for BufferPtr {} + +/// Send-only access to a [`Channel`]. +pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, +} + +impl<'a, M: RawMutex, T> Sender<'a, M, T> { + /// Creates one further [`Sender`] over the same channel. + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { channel: self.channel } + } + + /// Attempts to send a value over the channel. + pub fn try_send(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + /// Attempts to send a value over the channel. + pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.receive_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + /// Asynchronously send a value over the channel. + pub fn send(&mut self) -> impl Future { + poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => { + let r = unsafe { &mut *self.channel.buf.add(i) }; + Poll::Ready(r) + } + None => { + s.receive_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + } + + /// Notify the channel that the sending of the value has been finalized. + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } +} + +/// Receive-only access to a [`Channel`]. +pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, +} + +impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + /// Creates one further [`Sender`] over the same channel. + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { channel: self.channel } + } + + /// Attempts to receive a value over the channel. + pub fn try_receive(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + /// Attempts to asynchronously receive a value over the channel. + pub fn poll_receive(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + /// Asynchronously receive a value over the channel. + pub fn receive(&mut self) -> impl Future { + poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => { + let r = unsafe { &mut *self.channel.buf.add(i) }; + Poll::Ready(r) + } + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + } + + /// Notify the channel that the receiving of the value has been finalized. + pub fn receive_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + + /// Clears all elements in the channel. + pub fn clear(&mut self) { + self.channel.state.lock(|s| { + s.borrow_mut().clear(); + }); + } + + /// Returns the number of elements currently in the channel. + pub fn len(&self) -> usize { + self.channel.state.lock(|s| s.borrow().len()) + } + + /// Returns whether the channel is empty. + pub fn is_empty(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_empty()) + } + + /// Returns whether the channel is full. + pub fn is_full(&self) -> bool { + self.channel.state.lock(|s| s.borrow().is_full()) + } +} + +struct State { + /// Maximum number of elements the channel can hold. + capacity: usize, + + /// Front index. Always 0..=(N-1) + front: usize, + /// Back index. Always 0..=(N-1). + back: usize, + + /// Used to distinguish "empty" and "full" cases when `front == back`. + /// May only be `true` if `front == back`, always `false` otherwise. + full: bool, + + send_waker: WakerRegistration, + receive_waker: WakerRegistration, +} + +impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.capacity { + 0 + } else { + i + 1 + } + } + + fn clear(&mut self) { + if self.full { + self.receive_waker.wake(); + } + self.front = 0; + self.back = 0; + self.full = false; + } + + fn len(&self) -> usize { + if !self.full { + if self.back >= self.front { + self.back - self.front + } else { + self.capacity + self.back - self.front + } + } else { + self.capacity + } + } + + fn is_full(&self) -> bool { + self.full + } + + fn is_empty(&self) -> bool { + self.front == self.back && !self.full + } + + fn push_index(&mut self) -> Option { + match self.is_full() { + true => None, + false => Some(self.back), + } + } + + fn push_done(&mut self) { + assert!(!self.is_full()); + self.back = self.increment(self.back); + if self.back == self.front { + self.full = true; + } + self.send_waker.wake(); + } + + fn pop_index(&mut self) -> Option { + match self.is_empty() { + true => None, + false => Some(self.front), + } + } + + fn pop_done(&mut self) { + assert!(!self.is_empty()); + self.front = self.increment(self.front); + self.full = false; + self.receive_waker.wake(); + } +} diff --git a/embassy-time-driver/CHANGELOG.md b/embassy-time-driver/CHANGELOG.md new file mode 100644 index 0000000..744b0f6 --- /dev/null +++ b/embassy-time-driver/CHANGELOG.md @@ -0,0 +1,15 @@ +# Changelog for embassy-time-driver + +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). + +## 0.2.0 - 2025-01-02 + +- The `allocate_alarm`, `set_alarm_callback`, `set_alarm` functions have been removed. +- `schedule_wake` has been added to the `Driver` trait. + +## 0.1.0 - 2024-01-11 + +Initial release diff --git a/embassy-time-driver/Cargo.toml b/embassy-time-driver/Cargo.toml new file mode 100644 index 0000000..b709caf --- /dev/null +++ b/embassy-time-driver/Cargo.toml @@ -0,0 +1,374 @@ +[package] +name = "embassy-time-driver" +version = "0.2.0" +edition = "2021" +description = "Driver trait for embassy-time" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time-driver" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +# Prevent multiple copies of this crate in the same binary. +# Needed because different copies might get different tick rates, causing +# wrong delays if the time driver is using one copy and user code is using another. +# This is especially common when mixing crates from crates.io and git. +links = "embassy-time" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-driver-v$VERSION/embassy-time-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-driver/src/" +target = "x86_64-unknown-linux-gnu" + +[features] +#! ### Tick Rate +#! +#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. +#! +#! If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*` +#! feature from your binary crate to set the tick rate. The driver will use configured tick rate. +#! If the time driver supports a fixed tick rate, it will enable one feature itself, so you should +#! not enable one. Check the time driver documentation for details. +#! +#! When using embassy-time from libraries, you should *not* enable any `tick-*` feature, to allow the +#! end user or the driver to pick. +#!

+#! Available tick rates: +#! +#! + +# BEGIN TICKS +# Generated by gen_tick.py. DO NOT EDIT. +## 1Hz Tick Rate +tick-hz-1 = [] +## 2Hz Tick Rate +tick-hz-2 = [] +## 4Hz Tick Rate +tick-hz-4 = [] +## 8Hz Tick Rate +tick-hz-8 = [] +## 10Hz Tick Rate +tick-hz-10 = [] +## 16Hz Tick Rate +tick-hz-16 = [] +## 32Hz Tick Rate +tick-hz-32 = [] +## 64Hz Tick Rate +tick-hz-64 = [] +## 100Hz Tick Rate +tick-hz-100 = [] +## 128Hz Tick Rate +tick-hz-128 = [] +## 256Hz Tick Rate +tick-hz-256 = [] +## 512Hz Tick Rate +tick-hz-512 = [] +## 1.0kHz Tick Rate +tick-hz-1_000 = [] +## 1.024kHz Tick Rate +tick-hz-1_024 = [] +## 2.0kHz Tick Rate +tick-hz-2_000 = [] +## 2.048kHz Tick Rate +tick-hz-2_048 = [] +## 4.0kHz Tick Rate +tick-hz-4_000 = [] +## 4.096kHz Tick Rate +tick-hz-4_096 = [] +## 8.0kHz Tick Rate +tick-hz-8_000 = [] +## 8.192kHz Tick Rate +tick-hz-8_192 = [] +## 10.0kHz Tick Rate +tick-hz-10_000 = [] +## 16.0kHz Tick Rate +tick-hz-16_000 = [] +## 16.384kHz Tick Rate +tick-hz-16_384 = [] +## 20.0kHz Tick Rate +tick-hz-20_000 = [] +## 32.0kHz Tick Rate +tick-hz-32_000 = [] +## 32.768kHz Tick Rate +tick-hz-32_768 = [] +## 40.0kHz Tick Rate +tick-hz-40_000 = [] +## 64.0kHz Tick Rate +tick-hz-64_000 = [] +## 65.536kHz Tick Rate +tick-hz-65_536 = [] +## 80.0kHz Tick Rate +tick-hz-80_000 = [] +## 100.0kHz Tick Rate +tick-hz-100_000 = [] +## 128.0kHz Tick Rate +tick-hz-128_000 = [] +## 131.072kHz Tick Rate +tick-hz-131_072 = [] +## 160.0kHz Tick Rate +tick-hz-160_000 = [] +## 256.0kHz Tick Rate +tick-hz-256_000 = [] +## 262.144kHz Tick Rate +tick-hz-262_144 = [] +## 320.0kHz Tick Rate +tick-hz-320_000 = [] +## 512.0kHz Tick Rate +tick-hz-512_000 = [] +## 524.288kHz Tick Rate +tick-hz-524_288 = [] +## 640.0kHz Tick Rate +tick-hz-640_000 = [] +## 1.0MHz Tick Rate +tick-hz-1_000_000 = [] +## 1.024MHz Tick Rate +tick-hz-1_024_000 = [] +## 1.048576MHz Tick Rate +tick-hz-1_048_576 = [] +## 1.28MHz Tick Rate +tick-hz-1_280_000 = [] +## 2.0MHz Tick Rate +tick-hz-2_000_000 = [] +## 2.048MHz Tick Rate +tick-hz-2_048_000 = [] +## 2.097152MHz Tick Rate +tick-hz-2_097_152 = [] +## 2.56MHz Tick Rate +tick-hz-2_560_000 = [] +## 3.0MHz Tick Rate +tick-hz-3_000_000 = [] +## 4.0MHz Tick Rate +tick-hz-4_000_000 = [] +## 4.096MHz Tick Rate +tick-hz-4_096_000 = [] +## 4.194304MHz Tick Rate +tick-hz-4_194_304 = [] +## 5.12MHz Tick Rate +tick-hz-5_120_000 = [] +## 6.0MHz Tick Rate +tick-hz-6_000_000 = [] +## 8.0MHz Tick Rate +tick-hz-8_000_000 = [] +## 8.192MHz Tick Rate +tick-hz-8_192_000 = [] +## 8.388608MHz Tick Rate +tick-hz-8_388_608 = [] +## 9.0MHz Tick Rate +tick-hz-9_000_000 = [] +## 10.0MHz Tick Rate +tick-hz-10_000_000 = [] +## 10.24MHz Tick Rate +tick-hz-10_240_000 = [] +## 12.0MHz Tick Rate +tick-hz-12_000_000 = [] +## 16.0MHz Tick Rate +tick-hz-16_000_000 = [] +## 16.384MHz Tick Rate +tick-hz-16_384_000 = [] +## 16.777216MHz Tick Rate +tick-hz-16_777_216 = [] +## 18.0MHz Tick Rate +tick-hz-18_000_000 = [] +## 20.0MHz Tick Rate +tick-hz-20_000_000 = [] +## 20.48MHz Tick Rate +tick-hz-20_480_000 = [] +## 24.0MHz Tick Rate +tick-hz-24_000_000 = [] +## 30.0MHz Tick Rate +tick-hz-30_000_000 = [] +## 32.0MHz Tick Rate +tick-hz-32_000_000 = [] +## 32.768MHz Tick Rate +tick-hz-32_768_000 = [] +## 36.0MHz Tick Rate +tick-hz-36_000_000 = [] +## 40.0MHz Tick Rate +tick-hz-40_000_000 = [] +## 40.96MHz Tick Rate +tick-hz-40_960_000 = [] +## 48.0MHz Tick Rate +tick-hz-48_000_000 = [] +## 50.0MHz Tick Rate +tick-hz-50_000_000 = [] +## 60.0MHz Tick Rate +tick-hz-60_000_000 = [] +## 64.0MHz Tick Rate +tick-hz-64_000_000 = [] +## 65.536MHz Tick Rate +tick-hz-65_536_000 = [] +## 70.0MHz Tick Rate +tick-hz-70_000_000 = [] +## 72.0MHz Tick Rate +tick-hz-72_000_000 = [] +## 80.0MHz Tick Rate +tick-hz-80_000_000 = [] +## 81.92MHz Tick Rate +tick-hz-81_920_000 = [] +## 90.0MHz Tick Rate +tick-hz-90_000_000 = [] +## 96.0MHz Tick Rate +tick-hz-96_000_000 = [] +## 100.0MHz Tick Rate +tick-hz-100_000_000 = [] +## 110.0MHz Tick Rate +tick-hz-110_000_000 = [] +## 120.0MHz Tick Rate +tick-hz-120_000_000 = [] +## 128.0MHz Tick Rate +tick-hz-128_000_000 = [] +## 130.0MHz Tick Rate +tick-hz-130_000_000 = [] +## 131.072MHz Tick Rate +tick-hz-131_072_000 = [] +## 140.0MHz Tick Rate +tick-hz-140_000_000 = [] +## 144.0MHz Tick Rate +tick-hz-144_000_000 = [] +## 150.0MHz Tick Rate +tick-hz-150_000_000 = [] +## 160.0MHz Tick Rate +tick-hz-160_000_000 = [] +## 163.84MHz Tick Rate +tick-hz-163_840_000 = [] +## 170.0MHz Tick Rate +tick-hz-170_000_000 = [] +## 180.0MHz Tick Rate +tick-hz-180_000_000 = [] +## 190.0MHz Tick Rate +tick-hz-190_000_000 = [] +## 192.0MHz Tick Rate +tick-hz-192_000_000 = [] +## 200.0MHz Tick Rate +tick-hz-200_000_000 = [] +## 210.0MHz Tick Rate +tick-hz-210_000_000 = [] +## 220.0MHz Tick Rate +tick-hz-220_000_000 = [] +## 230.0MHz Tick Rate +tick-hz-230_000_000 = [] +## 240.0MHz Tick Rate +tick-hz-240_000_000 = [] +## 250.0MHz Tick Rate +tick-hz-250_000_000 = [] +## 256.0MHz Tick Rate +tick-hz-256_000_000 = [] +## 260.0MHz Tick Rate +tick-hz-260_000_000 = [] +## 262.144MHz Tick Rate +tick-hz-262_144_000 = [] +## 270.0MHz Tick Rate +tick-hz-270_000_000 = [] +## 280.0MHz Tick Rate +tick-hz-280_000_000 = [] +## 288.0MHz Tick Rate +tick-hz-288_000_000 = [] +## 290.0MHz Tick Rate +tick-hz-290_000_000 = [] +## 300.0MHz Tick Rate +tick-hz-300_000_000 = [] +## 320.0MHz Tick Rate +tick-hz-320_000_000 = [] +## 327.68MHz Tick Rate +tick-hz-327_680_000 = [] +## 340.0MHz Tick Rate +tick-hz-340_000_000 = [] +## 360.0MHz Tick Rate +tick-hz-360_000_000 = [] +## 380.0MHz Tick Rate +tick-hz-380_000_000 = [] +## 384.0MHz Tick Rate +tick-hz-384_000_000 = [] +## 400.0MHz Tick Rate +tick-hz-400_000_000 = [] +## 420.0MHz Tick Rate +tick-hz-420_000_000 = [] +## 440.0MHz Tick Rate +tick-hz-440_000_000 = [] +## 460.0MHz Tick Rate +tick-hz-460_000_000 = [] +## 480.0MHz Tick Rate +tick-hz-480_000_000 = [] +## 500.0MHz Tick Rate +tick-hz-500_000_000 = [] +## 512.0MHz Tick Rate +tick-hz-512_000_000 = [] +## 520.0MHz Tick Rate +tick-hz-520_000_000 = [] +## 524.288MHz Tick Rate +tick-hz-524_288_000 = [] +## 540.0MHz Tick Rate +tick-hz-540_000_000 = [] +## 560.0MHz Tick Rate +tick-hz-560_000_000 = [] +## 576.0MHz Tick Rate +tick-hz-576_000_000 = [] +## 580.0MHz Tick Rate +tick-hz-580_000_000 = [] +## 600.0MHz Tick Rate +tick-hz-600_000_000 = [] +## 620.0MHz Tick Rate +tick-hz-620_000_000 = [] +## 640.0MHz Tick Rate +tick-hz-640_000_000 = [] +## 655.36MHz Tick Rate +tick-hz-655_360_000 = [] +## 660.0MHz Tick Rate +tick-hz-660_000_000 = [] +## 680.0MHz Tick Rate +tick-hz-680_000_000 = [] +## 700.0MHz Tick Rate +tick-hz-700_000_000 = [] +## 720.0MHz Tick Rate +tick-hz-720_000_000 = [] +## 740.0MHz Tick Rate +tick-hz-740_000_000 = [] +## 760.0MHz Tick Rate +tick-hz-760_000_000 = [] +## 768.0MHz Tick Rate +tick-hz-768_000_000 = [] +## 780.0MHz Tick Rate +tick-hz-780_000_000 = [] +## 800.0MHz Tick Rate +tick-hz-800_000_000 = [] +## 820.0MHz Tick Rate +tick-hz-820_000_000 = [] +## 840.0MHz Tick Rate +tick-hz-840_000_000 = [] +## 860.0MHz Tick Rate +tick-hz-860_000_000 = [] +## 880.0MHz Tick Rate +tick-hz-880_000_000 = [] +## 900.0MHz Tick Rate +tick-hz-900_000_000 = [] +## 920.0MHz Tick Rate +tick-hz-920_000_000 = [] +## 940.0MHz Tick Rate +tick-hz-940_000_000 = [] +## 960.0MHz Tick Rate +tick-hz-960_000_000 = [] +## 980.0MHz Tick Rate +tick-hz-980_000_000 = [] +## 1.0GHz Tick Rate +tick-hz-1_000_000_000 = [] +## 1.31072GHz Tick Rate +tick-hz-1_310_720_000 = [] +## 2.62144GHz Tick Rate +tick-hz-2_621_440_000 = [] +## 5.24288GHz Tick Rate +tick-hz-5_242_880_000 = [] +# END TICKS + +#!
+ +[dependencies] +document-features = "0.2.7" + +[dev-dependencies] +critical-section = "1" +embassy-time-queue-utils = { path = "../embassy-time-queue-utils" } \ No newline at end of file diff --git a/embassy-time-driver/README.md b/embassy-time-driver/README.md new file mode 100644 index 0000000..426252d --- /dev/null +++ b/embassy-time-driver/README.md @@ -0,0 +1,19 @@ +# embassy-time-driver + +This crate contains the driver trait necessary for adding [`embassy-time`](https://crates.io/crates/embassy-time) support +for a new hardware platform. + +If you want to *use* `embassy-time` with already made drivers, you should depend on the main `embassy-time` crate, not on this crate. + +If you are writing a driver, you should depend only on this crate, not on the main `embassy-time` crate. +This will allow your driver to continue working for newer `embassy-time` major versions, without needing an update, +if the driver trait has not had breaking changes. + +## How it works + +`embassy-time` is backed by a global "time driver" specified at build time. +Only one driver can be active in a program. + +All methods and structs transparently call into the active driver. This makes it +possible for libraries to use `embassy-time` in a driver-agnostic way without +requiring generic parameters. diff --git a/embassy-time-driver/build.rs b/embassy-time-driver/build.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/embassy-time-driver/build.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/embassy-time-driver/gen_tick.py b/embassy-time-driver/gen_tick.py new file mode 100644 index 0000000..af194c3 --- /dev/null +++ b/embassy-time-driver/gen_tick.py @@ -0,0 +1,81 @@ +import os +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +ticks = [] +for i in range(10): + ticks.append(10**i) +for i in range(1, 25): + ticks.append(2**i) +for i in range(1, 20): + ticks.append(2**i * 1000) +for i in range(1, 20): + ticks.append(2**i * 10000) +for i in range(1, 10): + ticks.append(2**i * 1000000) + ticks.append(2**i * 9 // 8 * 1000000) + ticks.append(2**i * 3 // 2 * 1000000) +for i in range(1, 30): + ticks.append(10 * i * 1_000_000) +for i in range(15, 50): + ticks.append(20 * i * 1_000_000) + +seen = set() +ticks = sorted([x for x in ticks if not (x in seen or seen.add(x))]) + +# ========= Update Cargo.toml + +SEPARATOR_START = '# BEGIN TICKS\n' +SEPARATOR_END = '# END TICKS\n' +HELP = '# Generated by gen_tick.py. DO NOT EDIT.\n' + +feats_time = '' +feats_driver = '' +for freq in ticks: + feature = f'tick-hz-{freq:_}' + if freq >= 1_000_000_000: + freq_human = f"{freq / 1_000_000_000}GHz" + elif freq >= 1_000_000: + freq_human = f"{freq / 1_000_000}MHz" + elif freq >= 1_000: + freq_human = f"{freq / 1000}kHz" + else: + freq_human = f"{freq}Hz" + + feats_time += f"## {freq_human} Tick Rate\n" + feats_time += f"{feature} = [\"embassy-time-driver/{feature}\"]\n" + feats_driver += f"## {freq_human} Tick Rate\n" + feats_driver += f"{feature} = []\n" + +with open('Cargo.toml', 'r') as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +with open('Cargo.toml', 'w') as f: + f.write(before + SEPARATOR_START + HELP + feats_driver + SEPARATOR_END + after) + +with open('../embassy-time/Cargo.toml', 'r') as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +with open('../embassy-time/Cargo.toml', 'w') as f: + f.write(before + SEPARATOR_START + HELP + feats_time + SEPARATOR_END + after) + +# ========= Update src/tick.rs + +with open('src/tick.rs', 'w') as f: + + f.write('// Generated by gen_tick.py. DO NOT EDIT.\n\n') + for hz in ticks: + f.write( + f'#[cfg(feature = "tick-hz-{hz:_}")] pub const TICK_HZ: u64 = {hz:_};\n') + f.write('#[cfg(not(any(\n') + for hz in ticks: + f.write(f'feature = "tick-hz-{hz:_}",\n') + f.write(')))] pub const TICK_HZ: u64 = 1_000_000;') + + +os.system('rustfmt src/tick.rs') diff --git a/embassy-time-driver/src/lib.rs b/embassy-time-driver/src/lib.rs new file mode 100644 index 0000000..32cb682 --- /dev/null +++ b/embassy-time-driver/src/lib.rs @@ -0,0 +1,173 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +//! ## Implementing a driver +//! +//! - Define a struct `MyDriver` +//! - Implement [`Driver`] for it +//! - Register it as the global driver with [`time_driver_impl`](crate::time_driver_impl). +//! +//! If your driver has a single set tick rate, enable the corresponding [`tick-hz-*`](crate#tick-rate) feature, +//! which will prevent users from needing to configure it themselves (or selecting an incorrect configuration). +//! +//! If your driver supports a small number of set tick rates, expose your own cargo features and have each one +//! enable the corresponding `embassy-time-driver/tick-*`. +//! +//! Otherwise, don’t enable any `tick-hz-*` feature to let the user configure the tick rate themselves by +//! enabling a feature on `embassy-time`. +//! +//! ### Example +//! +//! ``` +//! use core::task::Waker; +//! +//! use embassy_time_driver::Driver; +//! +//! struct MyDriver{} // not public! +//! +//! impl Driver for MyDriver { +//! fn now(&self) -> u64 { +//! todo!() +//! } +//! +//! fn schedule_wake(&self, at: u64, waker: &Waker) { +//! todo!() +//! } +//! } +//! +//! embassy_time_driver::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); +//! ``` +//! +//! ## Implementing the timer queue +//! +//! The simplest (but suboptimal) way to implement a timer queue is to define a single queue in the +//! time driver. Declare a field protected by an appropriate mutex (e.g. `critical_section::Mutex`). +//! +//! Then, you'll need to adapt the `schedule_wake` method to use this queue. +//! +//! Note that if you are using multiple queues, you will need to ensure that a single timer +//! queue item is only ever enqueued into a single queue at a time. +//! +//! ``` +//! use core::cell::RefCell; +//! use core::task::Waker; +//! +//! use critical_section::{CriticalSection, Mutex}; +//! use embassy_time_queue_utils::Queue; +//! use embassy_time_driver::Driver; +//! +//! struct MyDriver { +//! queue: Mutex>, +//! } +//! +//! impl MyDriver { +//! fn set_alarm(&self, cs: &CriticalSection, at: u64) -> bool { +//! todo!() +//! } +//! } +//! +//! impl Driver for MyDriver { +//! fn now(&self) -> u64 { todo!() } +//! +//! fn schedule_wake(&self, at: u64, waker: &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()); +//! } +//! } +//! }); +//! } +//! } +//! ``` +//! +//! # Linkage details +//! +//! Instead of the usual "trait + generic params" approach, calls from embassy to the driver are done via `extern` functions. +//! +//! `embassy` internally defines the driver function as `extern "Rust" { fn _embassy_time_now() -> u64; }` and calls it. +//! The driver crate defines the function as `#[no_mangle] fn _embassy_time_now() -> u64`. The linker will resolve the +//! calls from the `embassy` crate to call into the driver crate. +//! +//! If there is none or multiple drivers in the crate tree, linking will fail. +//! +//! This method has a few key advantages for something as foundational as timekeeping: +//! +//! - The time driver is available everywhere easily, without having to thread the implementation +//! through generic parameters. This is especially helpful for libraries. +//! - It means comparing `Instant`s will always make sense: if there were multiple drivers +//! active, one could compare an `Instant` from driver A to an `Instant` from driver B, which +//! would yield incorrect results. + +//! ## Feature flags +#![doc = document_features::document_features!(feature_label = r#"{feature}"#)] + +use core::task::Waker; + +mod tick; + +/// Ticks per second of the global timebase. +/// +/// This value is specified by the [`tick-*` Cargo features](crate#tick-rate) +pub const TICK_HZ: u64 = tick::TICK_HZ; + +/// Time driver +pub trait Driver: Send + Sync + 'static { + /// Return the current timestamp in ticks. + /// + /// Implementations MUST ensure that: + /// - This is guaranteed to be monotonic, i.e. a call to now() will always return + /// a greater or equal value than earlier calls. Time can't "roll backwards". + /// - It "never" overflows. It must not overflow in a sufficiently long time frame, say + /// in 10_000 years (Human civilization is likely to already have self-destructed + /// 10_000 years from now.). This means if your hardware only has 16bit/32bit timers + /// you MUST extend them to 64-bit, for example by counting overflows in software, + /// or chaining multiple timers together. + fn now(&self) -> u64; + + /// Schedules a waker to be awoken at moment `at`. + /// If this moment is in the past, the waker might be awoken immediately. + fn schedule_wake(&self, at: u64, waker: &Waker); +} + +extern "Rust" { + fn _embassy_time_now() -> u64; + fn _embassy_time_schedule_wake(at: u64, waker: &Waker); +} + +/// See [`Driver::now`] +#[inline] +pub fn now() -> u64 { + unsafe { _embassy_time_now() } +} + +/// Schedule the given waker to be woken at `at`. +#[inline] +pub fn schedule_wake(at: u64, waker: &Waker) { + unsafe { _embassy_time_schedule_wake(at, waker) } +} + +/// Set the time Driver implementation. +/// +/// See the module documentation for an example. +#[macro_export] +macro_rules! time_driver_impl { + (static $name:ident: $t: ty = $val:expr) => { + static $name: $t = $val; + + #[no_mangle] + #[inline] + fn _embassy_time_now() -> u64 { + <$t as $crate::Driver>::now(&$name) + } + + #[no_mangle] + #[inline] + fn _embassy_time_schedule_wake(at: u64, waker: &core::task::Waker) { + <$t as $crate::Driver>::schedule_wake(&$name, at, waker); + } + }; +} diff --git a/embassy-time-driver/src/tick.rs b/embassy-time-driver/src/tick.rs new file mode 100644 index 0000000..916ae94 --- /dev/null +++ b/embassy-time-driver/src/tick.rs @@ -0,0 +1,482 @@ +// Generated by gen_tick.py. DO NOT EDIT. + +#[cfg(feature = "tick-hz-1")] +pub const TICK_HZ: u64 = 1; +#[cfg(feature = "tick-hz-2")] +pub const TICK_HZ: u64 = 2; +#[cfg(feature = "tick-hz-4")] +pub const TICK_HZ: u64 = 4; +#[cfg(feature = "tick-hz-8")] +pub const TICK_HZ: u64 = 8; +#[cfg(feature = "tick-hz-10")] +pub const TICK_HZ: u64 = 10; +#[cfg(feature = "tick-hz-16")] +pub const TICK_HZ: u64 = 16; +#[cfg(feature = "tick-hz-32")] +pub const TICK_HZ: u64 = 32; +#[cfg(feature = "tick-hz-64")] +pub const TICK_HZ: u64 = 64; +#[cfg(feature = "tick-hz-100")] +pub const TICK_HZ: u64 = 100; +#[cfg(feature = "tick-hz-128")] +pub const TICK_HZ: u64 = 128; +#[cfg(feature = "tick-hz-256")] +pub const TICK_HZ: u64 = 256; +#[cfg(feature = "tick-hz-512")] +pub const TICK_HZ: u64 = 512; +#[cfg(feature = "tick-hz-1_000")] +pub const TICK_HZ: u64 = 1_000; +#[cfg(feature = "tick-hz-1_024")] +pub const TICK_HZ: u64 = 1_024; +#[cfg(feature = "tick-hz-2_000")] +pub const TICK_HZ: u64 = 2_000; +#[cfg(feature = "tick-hz-2_048")] +pub const TICK_HZ: u64 = 2_048; +#[cfg(feature = "tick-hz-4_000")] +pub const TICK_HZ: u64 = 4_000; +#[cfg(feature = "tick-hz-4_096")] +pub const TICK_HZ: u64 = 4_096; +#[cfg(feature = "tick-hz-8_000")] +pub const TICK_HZ: u64 = 8_000; +#[cfg(feature = "tick-hz-8_192")] +pub const TICK_HZ: u64 = 8_192; +#[cfg(feature = "tick-hz-10_000")] +pub const TICK_HZ: u64 = 10_000; +#[cfg(feature = "tick-hz-16_000")] +pub const TICK_HZ: u64 = 16_000; +#[cfg(feature = "tick-hz-16_384")] +pub const TICK_HZ: u64 = 16_384; +#[cfg(feature = "tick-hz-20_000")] +pub const TICK_HZ: u64 = 20_000; +#[cfg(feature = "tick-hz-32_000")] +pub const TICK_HZ: u64 = 32_000; +#[cfg(feature = "tick-hz-32_768")] +pub const TICK_HZ: u64 = 32_768; +#[cfg(feature = "tick-hz-40_000")] +pub const TICK_HZ: u64 = 40_000; +#[cfg(feature = "tick-hz-64_000")] +pub const TICK_HZ: u64 = 64_000; +#[cfg(feature = "tick-hz-65_536")] +pub const TICK_HZ: u64 = 65_536; +#[cfg(feature = "tick-hz-80_000")] +pub const TICK_HZ: u64 = 80_000; +#[cfg(feature = "tick-hz-100_000")] +pub const TICK_HZ: u64 = 100_000; +#[cfg(feature = "tick-hz-128_000")] +pub const TICK_HZ: u64 = 128_000; +#[cfg(feature = "tick-hz-131_072")] +pub const TICK_HZ: u64 = 131_072; +#[cfg(feature = "tick-hz-160_000")] +pub const TICK_HZ: u64 = 160_000; +#[cfg(feature = "tick-hz-256_000")] +pub const TICK_HZ: u64 = 256_000; +#[cfg(feature = "tick-hz-262_144")] +pub const TICK_HZ: u64 = 262_144; +#[cfg(feature = "tick-hz-320_000")] +pub const TICK_HZ: u64 = 320_000; +#[cfg(feature = "tick-hz-512_000")] +pub const TICK_HZ: u64 = 512_000; +#[cfg(feature = "tick-hz-524_288")] +pub const TICK_HZ: u64 = 524_288; +#[cfg(feature = "tick-hz-640_000")] +pub const TICK_HZ: u64 = 640_000; +#[cfg(feature = "tick-hz-1_000_000")] +pub const TICK_HZ: u64 = 1_000_000; +#[cfg(feature = "tick-hz-1_024_000")] +pub const TICK_HZ: u64 = 1_024_000; +#[cfg(feature = "tick-hz-1_048_576")] +pub const TICK_HZ: u64 = 1_048_576; +#[cfg(feature = "tick-hz-1_280_000")] +pub const TICK_HZ: u64 = 1_280_000; +#[cfg(feature = "tick-hz-2_000_000")] +pub const TICK_HZ: u64 = 2_000_000; +#[cfg(feature = "tick-hz-2_048_000")] +pub const TICK_HZ: u64 = 2_048_000; +#[cfg(feature = "tick-hz-2_097_152")] +pub const TICK_HZ: u64 = 2_097_152; +#[cfg(feature = "tick-hz-2_560_000")] +pub const TICK_HZ: u64 = 2_560_000; +#[cfg(feature = "tick-hz-3_000_000")] +pub const TICK_HZ: u64 = 3_000_000; +#[cfg(feature = "tick-hz-4_000_000")] +pub const TICK_HZ: u64 = 4_000_000; +#[cfg(feature = "tick-hz-4_096_000")] +pub const TICK_HZ: u64 = 4_096_000; +#[cfg(feature = "tick-hz-4_194_304")] +pub const TICK_HZ: u64 = 4_194_304; +#[cfg(feature = "tick-hz-5_120_000")] +pub const TICK_HZ: u64 = 5_120_000; +#[cfg(feature = "tick-hz-6_000_000")] +pub const TICK_HZ: u64 = 6_000_000; +#[cfg(feature = "tick-hz-8_000_000")] +pub const TICK_HZ: u64 = 8_000_000; +#[cfg(feature = "tick-hz-8_192_000")] +pub const TICK_HZ: u64 = 8_192_000; +#[cfg(feature = "tick-hz-8_388_608")] +pub const TICK_HZ: u64 = 8_388_608; +#[cfg(feature = "tick-hz-9_000_000")] +pub const TICK_HZ: u64 = 9_000_000; +#[cfg(feature = "tick-hz-10_000_000")] +pub const TICK_HZ: u64 = 10_000_000; +#[cfg(feature = "tick-hz-10_240_000")] +pub const TICK_HZ: u64 = 10_240_000; +#[cfg(feature = "tick-hz-12_000_000")] +pub const TICK_HZ: u64 = 12_000_000; +#[cfg(feature = "tick-hz-16_000_000")] +pub const TICK_HZ: u64 = 16_000_000; +#[cfg(feature = "tick-hz-16_384_000")] +pub const TICK_HZ: u64 = 16_384_000; +#[cfg(feature = "tick-hz-16_777_216")] +pub const TICK_HZ: u64 = 16_777_216; +#[cfg(feature = "tick-hz-18_000_000")] +pub const TICK_HZ: u64 = 18_000_000; +#[cfg(feature = "tick-hz-20_000_000")] +pub const TICK_HZ: u64 = 20_000_000; +#[cfg(feature = "tick-hz-20_480_000")] +pub const TICK_HZ: u64 = 20_480_000; +#[cfg(feature = "tick-hz-24_000_000")] +pub const TICK_HZ: u64 = 24_000_000; +#[cfg(feature = "tick-hz-30_000_000")] +pub const TICK_HZ: u64 = 30_000_000; +#[cfg(feature = "tick-hz-32_000_000")] +pub const TICK_HZ: u64 = 32_000_000; +#[cfg(feature = "tick-hz-32_768_000")] +pub const TICK_HZ: u64 = 32_768_000; +#[cfg(feature = "tick-hz-36_000_000")] +pub const TICK_HZ: u64 = 36_000_000; +#[cfg(feature = "tick-hz-40_000_000")] +pub const TICK_HZ: u64 = 40_000_000; +#[cfg(feature = "tick-hz-40_960_000")] +pub const TICK_HZ: u64 = 40_960_000; +#[cfg(feature = "tick-hz-48_000_000")] +pub const TICK_HZ: u64 = 48_000_000; +#[cfg(feature = "tick-hz-50_000_000")] +pub const TICK_HZ: u64 = 50_000_000; +#[cfg(feature = "tick-hz-60_000_000")] +pub const TICK_HZ: u64 = 60_000_000; +#[cfg(feature = "tick-hz-64_000_000")] +pub const TICK_HZ: u64 = 64_000_000; +#[cfg(feature = "tick-hz-65_536_000")] +pub const TICK_HZ: u64 = 65_536_000; +#[cfg(feature = "tick-hz-70_000_000")] +pub const TICK_HZ: u64 = 70_000_000; +#[cfg(feature = "tick-hz-72_000_000")] +pub const TICK_HZ: u64 = 72_000_000; +#[cfg(feature = "tick-hz-80_000_000")] +pub const TICK_HZ: u64 = 80_000_000; +#[cfg(feature = "tick-hz-81_920_000")] +pub const TICK_HZ: u64 = 81_920_000; +#[cfg(feature = "tick-hz-90_000_000")] +pub const TICK_HZ: u64 = 90_000_000; +#[cfg(feature = "tick-hz-96_000_000")] +pub const TICK_HZ: u64 = 96_000_000; +#[cfg(feature = "tick-hz-100_000_000")] +pub const TICK_HZ: u64 = 100_000_000; +#[cfg(feature = "tick-hz-110_000_000")] +pub const TICK_HZ: u64 = 110_000_000; +#[cfg(feature = "tick-hz-120_000_000")] +pub const TICK_HZ: u64 = 120_000_000; +#[cfg(feature = "tick-hz-128_000_000")] +pub const TICK_HZ: u64 = 128_000_000; +#[cfg(feature = "tick-hz-130_000_000")] +pub const TICK_HZ: u64 = 130_000_000; +#[cfg(feature = "tick-hz-131_072_000")] +pub const TICK_HZ: u64 = 131_072_000; +#[cfg(feature = "tick-hz-140_000_000")] +pub const TICK_HZ: u64 = 140_000_000; +#[cfg(feature = "tick-hz-144_000_000")] +pub const TICK_HZ: u64 = 144_000_000; +#[cfg(feature = "tick-hz-150_000_000")] +pub const TICK_HZ: u64 = 150_000_000; +#[cfg(feature = "tick-hz-160_000_000")] +pub const TICK_HZ: u64 = 160_000_000; +#[cfg(feature = "tick-hz-163_840_000")] +pub const TICK_HZ: u64 = 163_840_000; +#[cfg(feature = "tick-hz-170_000_000")] +pub const TICK_HZ: u64 = 170_000_000; +#[cfg(feature = "tick-hz-180_000_000")] +pub const TICK_HZ: u64 = 180_000_000; +#[cfg(feature = "tick-hz-190_000_000")] +pub const TICK_HZ: u64 = 190_000_000; +#[cfg(feature = "tick-hz-192_000_000")] +pub const TICK_HZ: u64 = 192_000_000; +#[cfg(feature = "tick-hz-200_000_000")] +pub const TICK_HZ: u64 = 200_000_000; +#[cfg(feature = "tick-hz-210_000_000")] +pub const TICK_HZ: u64 = 210_000_000; +#[cfg(feature = "tick-hz-220_000_000")] +pub const TICK_HZ: u64 = 220_000_000; +#[cfg(feature = "tick-hz-230_000_000")] +pub const TICK_HZ: u64 = 230_000_000; +#[cfg(feature = "tick-hz-240_000_000")] +pub const TICK_HZ: u64 = 240_000_000; +#[cfg(feature = "tick-hz-250_000_000")] +pub const TICK_HZ: u64 = 250_000_000; +#[cfg(feature = "tick-hz-256_000_000")] +pub const TICK_HZ: u64 = 256_000_000; +#[cfg(feature = "tick-hz-260_000_000")] +pub const TICK_HZ: u64 = 260_000_000; +#[cfg(feature = "tick-hz-262_144_000")] +pub const TICK_HZ: u64 = 262_144_000; +#[cfg(feature = "tick-hz-270_000_000")] +pub const TICK_HZ: u64 = 270_000_000; +#[cfg(feature = "tick-hz-280_000_000")] +pub const TICK_HZ: u64 = 280_000_000; +#[cfg(feature = "tick-hz-288_000_000")] +pub const TICK_HZ: u64 = 288_000_000; +#[cfg(feature = "tick-hz-290_000_000")] +pub const TICK_HZ: u64 = 290_000_000; +#[cfg(feature = "tick-hz-300_000_000")] +pub const TICK_HZ: u64 = 300_000_000; +#[cfg(feature = "tick-hz-320_000_000")] +pub const TICK_HZ: u64 = 320_000_000; +#[cfg(feature = "tick-hz-327_680_000")] +pub const TICK_HZ: u64 = 327_680_000; +#[cfg(feature = "tick-hz-340_000_000")] +pub const TICK_HZ: u64 = 340_000_000; +#[cfg(feature = "tick-hz-360_000_000")] +pub const TICK_HZ: u64 = 360_000_000; +#[cfg(feature = "tick-hz-380_000_000")] +pub const TICK_HZ: u64 = 380_000_000; +#[cfg(feature = "tick-hz-384_000_000")] +pub const TICK_HZ: u64 = 384_000_000; +#[cfg(feature = "tick-hz-400_000_000")] +pub const TICK_HZ: u64 = 400_000_000; +#[cfg(feature = "tick-hz-420_000_000")] +pub const TICK_HZ: u64 = 420_000_000; +#[cfg(feature = "tick-hz-440_000_000")] +pub const TICK_HZ: u64 = 440_000_000; +#[cfg(feature = "tick-hz-460_000_000")] +pub const TICK_HZ: u64 = 460_000_000; +#[cfg(feature = "tick-hz-480_000_000")] +pub const TICK_HZ: u64 = 480_000_000; +#[cfg(feature = "tick-hz-500_000_000")] +pub const TICK_HZ: u64 = 500_000_000; +#[cfg(feature = "tick-hz-512_000_000")] +pub const TICK_HZ: u64 = 512_000_000; +#[cfg(feature = "tick-hz-520_000_000")] +pub const TICK_HZ: u64 = 520_000_000; +#[cfg(feature = "tick-hz-524_288_000")] +pub const TICK_HZ: u64 = 524_288_000; +#[cfg(feature = "tick-hz-540_000_000")] +pub const TICK_HZ: u64 = 540_000_000; +#[cfg(feature = "tick-hz-560_000_000")] +pub const TICK_HZ: u64 = 560_000_000; +#[cfg(feature = "tick-hz-576_000_000")] +pub const TICK_HZ: u64 = 576_000_000; +#[cfg(feature = "tick-hz-580_000_000")] +pub const TICK_HZ: u64 = 580_000_000; +#[cfg(feature = "tick-hz-600_000_000")] +pub const TICK_HZ: u64 = 600_000_000; +#[cfg(feature = "tick-hz-620_000_000")] +pub const TICK_HZ: u64 = 620_000_000; +#[cfg(feature = "tick-hz-640_000_000")] +pub const TICK_HZ: u64 = 640_000_000; +#[cfg(feature = "tick-hz-655_360_000")] +pub const TICK_HZ: u64 = 655_360_000; +#[cfg(feature = "tick-hz-660_000_000")] +pub const TICK_HZ: u64 = 660_000_000; +#[cfg(feature = "tick-hz-680_000_000")] +pub const TICK_HZ: u64 = 680_000_000; +#[cfg(feature = "tick-hz-700_000_000")] +pub const TICK_HZ: u64 = 700_000_000; +#[cfg(feature = "tick-hz-720_000_000")] +pub const TICK_HZ: u64 = 720_000_000; +#[cfg(feature = "tick-hz-740_000_000")] +pub const TICK_HZ: u64 = 740_000_000; +#[cfg(feature = "tick-hz-760_000_000")] +pub const TICK_HZ: u64 = 760_000_000; +#[cfg(feature = "tick-hz-768_000_000")] +pub const TICK_HZ: u64 = 768_000_000; +#[cfg(feature = "tick-hz-780_000_000")] +pub const TICK_HZ: u64 = 780_000_000; +#[cfg(feature = "tick-hz-800_000_000")] +pub const TICK_HZ: u64 = 800_000_000; +#[cfg(feature = "tick-hz-820_000_000")] +pub const TICK_HZ: u64 = 820_000_000; +#[cfg(feature = "tick-hz-840_000_000")] +pub const TICK_HZ: u64 = 840_000_000; +#[cfg(feature = "tick-hz-860_000_000")] +pub const TICK_HZ: u64 = 860_000_000; +#[cfg(feature = "tick-hz-880_000_000")] +pub const TICK_HZ: u64 = 880_000_000; +#[cfg(feature = "tick-hz-900_000_000")] +pub const TICK_HZ: u64 = 900_000_000; +#[cfg(feature = "tick-hz-920_000_000")] +pub const TICK_HZ: u64 = 920_000_000; +#[cfg(feature = "tick-hz-940_000_000")] +pub const TICK_HZ: u64 = 940_000_000; +#[cfg(feature = "tick-hz-960_000_000")] +pub const TICK_HZ: u64 = 960_000_000; +#[cfg(feature = "tick-hz-980_000_000")] +pub const TICK_HZ: u64 = 980_000_000; +#[cfg(feature = "tick-hz-1_000_000_000")] +pub const TICK_HZ: u64 = 1_000_000_000; +#[cfg(feature = "tick-hz-1_310_720_000")] +pub const TICK_HZ: u64 = 1_310_720_000; +#[cfg(feature = "tick-hz-2_621_440_000")] +pub const TICK_HZ: u64 = 2_621_440_000; +#[cfg(feature = "tick-hz-5_242_880_000")] +pub const TICK_HZ: u64 = 5_242_880_000; +#[cfg(not(any( + feature = "tick-hz-1", + feature = "tick-hz-2", + feature = "tick-hz-4", + feature = "tick-hz-8", + feature = "tick-hz-10", + feature = "tick-hz-16", + feature = "tick-hz-32", + feature = "tick-hz-64", + feature = "tick-hz-100", + feature = "tick-hz-128", + feature = "tick-hz-256", + feature = "tick-hz-512", + feature = "tick-hz-1_000", + feature = "tick-hz-1_024", + feature = "tick-hz-2_000", + feature = "tick-hz-2_048", + feature = "tick-hz-4_000", + feature = "tick-hz-4_096", + feature = "tick-hz-8_000", + feature = "tick-hz-8_192", + feature = "tick-hz-10_000", + feature = "tick-hz-16_000", + feature = "tick-hz-16_384", + feature = "tick-hz-20_000", + feature = "tick-hz-32_000", + feature = "tick-hz-32_768", + feature = "tick-hz-40_000", + feature = "tick-hz-64_000", + feature = "tick-hz-65_536", + feature = "tick-hz-80_000", + feature = "tick-hz-100_000", + feature = "tick-hz-128_000", + feature = "tick-hz-131_072", + feature = "tick-hz-160_000", + feature = "tick-hz-256_000", + feature = "tick-hz-262_144", + feature = "tick-hz-320_000", + feature = "tick-hz-512_000", + feature = "tick-hz-524_288", + feature = "tick-hz-640_000", + feature = "tick-hz-1_000_000", + feature = "tick-hz-1_024_000", + feature = "tick-hz-1_048_576", + feature = "tick-hz-1_280_000", + feature = "tick-hz-2_000_000", + feature = "tick-hz-2_048_000", + feature = "tick-hz-2_097_152", + feature = "tick-hz-2_560_000", + feature = "tick-hz-3_000_000", + feature = "tick-hz-4_000_000", + feature = "tick-hz-4_096_000", + feature = "tick-hz-4_194_304", + feature = "tick-hz-5_120_000", + feature = "tick-hz-6_000_000", + feature = "tick-hz-8_000_000", + feature = "tick-hz-8_192_000", + feature = "tick-hz-8_388_608", + feature = "tick-hz-9_000_000", + feature = "tick-hz-10_000_000", + feature = "tick-hz-10_240_000", + feature = "tick-hz-12_000_000", + feature = "tick-hz-16_000_000", + feature = "tick-hz-16_384_000", + feature = "tick-hz-16_777_216", + feature = "tick-hz-18_000_000", + feature = "tick-hz-20_000_000", + feature = "tick-hz-20_480_000", + feature = "tick-hz-24_000_000", + feature = "tick-hz-30_000_000", + feature = "tick-hz-32_000_000", + feature = "tick-hz-32_768_000", + feature = "tick-hz-36_000_000", + feature = "tick-hz-40_000_000", + feature = "tick-hz-40_960_000", + feature = "tick-hz-48_000_000", + feature = "tick-hz-50_000_000", + feature = "tick-hz-60_000_000", + feature = "tick-hz-64_000_000", + feature = "tick-hz-65_536_000", + feature = "tick-hz-70_000_000", + feature = "tick-hz-72_000_000", + feature = "tick-hz-80_000_000", + feature = "tick-hz-81_920_000", + feature = "tick-hz-90_000_000", + feature = "tick-hz-96_000_000", + feature = "tick-hz-100_000_000", + feature = "tick-hz-110_000_000", + feature = "tick-hz-120_000_000", + feature = "tick-hz-128_000_000", + feature = "tick-hz-130_000_000", + feature = "tick-hz-131_072_000", + feature = "tick-hz-140_000_000", + feature = "tick-hz-144_000_000", + feature = "tick-hz-150_000_000", + feature = "tick-hz-160_000_000", + feature = "tick-hz-163_840_000", + feature = "tick-hz-170_000_000", + feature = "tick-hz-180_000_000", + feature = "tick-hz-190_000_000", + feature = "tick-hz-192_000_000", + feature = "tick-hz-200_000_000", + feature = "tick-hz-210_000_000", + feature = "tick-hz-220_000_000", + feature = "tick-hz-230_000_000", + feature = "tick-hz-240_000_000", + feature = "tick-hz-250_000_000", + feature = "tick-hz-256_000_000", + feature = "tick-hz-260_000_000", + feature = "tick-hz-262_144_000", + feature = "tick-hz-270_000_000", + feature = "tick-hz-280_000_000", + feature = "tick-hz-288_000_000", + feature = "tick-hz-290_000_000", + feature = "tick-hz-300_000_000", + feature = "tick-hz-320_000_000", + feature = "tick-hz-327_680_000", + feature = "tick-hz-340_000_000", + feature = "tick-hz-360_000_000", + feature = "tick-hz-380_000_000", + feature = "tick-hz-384_000_000", + feature = "tick-hz-400_000_000", + feature = "tick-hz-420_000_000", + feature = "tick-hz-440_000_000", + feature = "tick-hz-460_000_000", + feature = "tick-hz-480_000_000", + feature = "tick-hz-500_000_000", + feature = "tick-hz-512_000_000", + feature = "tick-hz-520_000_000", + feature = "tick-hz-524_288_000", + feature = "tick-hz-540_000_000", + feature = "tick-hz-560_000_000", + feature = "tick-hz-576_000_000", + feature = "tick-hz-580_000_000", + feature = "tick-hz-600_000_000", + feature = "tick-hz-620_000_000", + feature = "tick-hz-640_000_000", + feature = "tick-hz-655_360_000", + feature = "tick-hz-660_000_000", + feature = "tick-hz-680_000_000", + feature = "tick-hz-700_000_000", + feature = "tick-hz-720_000_000", + feature = "tick-hz-740_000_000", + feature = "tick-hz-760_000_000", + feature = "tick-hz-768_000_000", + feature = "tick-hz-780_000_000", + feature = "tick-hz-800_000_000", + feature = "tick-hz-820_000_000", + feature = "tick-hz-840_000_000", + feature = "tick-hz-860_000_000", + feature = "tick-hz-880_000_000", + feature = "tick-hz-900_000_000", + feature = "tick-hz-920_000_000", + feature = "tick-hz-940_000_000", + feature = "tick-hz-960_000_000", + feature = "tick-hz-980_000_000", + feature = "tick-hz-1_000_000_000", + feature = "tick-hz-1_310_720_000", + feature = "tick-hz-2_621_440_000", + feature = "tick-hz-5_242_880_000", +)))] +pub const TICK_HZ: u64 = 1_000_000; diff --git a/embassy-time-queue-utils/CHANGELOG.md b/embassy-time-queue-utils/CHANGELOG.md new file mode 100644 index 0000000..ae4714f --- /dev/null +++ b/embassy-time-queue-utils/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog for embassy-time-queue-utils + +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). + +## 0.1.0 - 2024-01-11 + +Initial release diff --git a/embassy-time-queue-utils/Cargo.toml b/embassy-time-queue-utils/Cargo.toml new file mode 100644 index 0000000..48be121 --- /dev/null +++ b/embassy-time-queue-utils/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "embassy-time-queue-utils" +version = "0.1.0" +edition = "2021" +description = "Timer queue driver trait for embassy-time" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time-queue-utils" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +# Prevent multiple copies of this crate in the same binary. +# Needed because different copies might get different tick rates, causing +# wrong delays if the time driver is using one copy and user code is using another. +# This is especially common when mixing crates from crates.io and git. +links = "embassy-time-queue" + +[dependencies] +heapless = "0.8" +embassy-executor = { version = "0.7.0", path = "../embassy-executor" } + +[features] +#! ### Generic Queue + +#! By default this crate uses a timer queue implementation that is faster but depends on `embassy-executor`. +#! It will panic if you try to await any timer when using another executor. +#! +#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor. +#! To enable it, enable any of the features below. +#! +#! The features also set how many timers are used for the generic queue. At most one +#! `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +#! +#! When using embassy-time-queue-driver from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +#! end user to pick. + +## Generic Queue with 8 timers +generic-queue-8 = ["_generic-queue"] +## Generic Queue with 16 timers +generic-queue-16 = ["_generic-queue"] +## Generic Queue with 32 timers +generic-queue-32 = ["_generic-queue"] +## Generic Queue with 64 timers +generic-queue-64 = ["_generic-queue"] +## Generic Queue with 128 timers +generic-queue-128 = ["_generic-queue"] + +_generic-queue = [] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-queue-utils-v$VERSION/embassy-time-queue-utils/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time-queue-utils/src/" +target = "x86_64-unknown-linux-gnu" diff --git a/embassy-time-queue-utils/README.md b/embassy-time-queue-utils/README.md new file mode 100644 index 0000000..36461f1 --- /dev/null +++ b/embassy-time-queue-utils/README.md @@ -0,0 +1,8 @@ +# embassy-time-queue-utils + +This crate contains timer queues to help implementing an [`embassy-time-driver`](https://crates.io/crates/embassy-time-driver). + +As a HAL user, you should not need to depend on this crate. + +As a HAL implementer, you need to depend on this crate if you want to implement a time driver, +but how you should do so is documented in `embassy-time-driver`. diff --git a/embassy-time-queue-utils/build.rs b/embassy-time-queue-utils/build.rs new file mode 100644 index 0000000..f328e4d --- /dev/null +++ b/embassy-time-queue-utils/build.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/embassy-time-queue-utils/src/lib.rs b/embassy-time-queue-utils/src/lib.rs new file mode 100644 index 0000000..a6f6691 --- /dev/null +++ b/embassy-time-queue-utils/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +#[cfg(feature = "_generic-queue")] +pub mod queue_generic; +#[cfg(not(feature = "_generic-queue"))] +pub mod queue_integrated; + +#[cfg(feature = "_generic-queue")] +pub use queue_generic::Queue; +#[cfg(not(feature = "_generic-queue"))] +pub use queue_integrated::Queue; diff --git a/embassy-time-queue-utils/src/queue_generic.rs b/embassy-time-queue-utils/src/queue_generic.rs new file mode 100644 index 0000000..232035b --- /dev/null +++ b/embassy-time-queue-utils/src/queue_generic.rs @@ -0,0 +1,146 @@ +//! Generic timer queue implementations. +//! +//! Time queue drivers may use this to simplify their implementation. + +use core::cmp::{min, Ordering}; +use core::task::Waker; + +use heapless::Vec; + +#[derive(Debug)] +struct Timer { + at: u64, + waker: Waker, +} + +impl PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.at == other.at + } +} + +impl Eq for Timer {} + +impl PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + self.at.partial_cmp(&other.at) + } +} + +impl Ord for Timer { + fn cmp(&self, other: &Self) -> Ordering { + self.at.cmp(&other.at) + } +} + +/// A timer queue with a pre-determined capacity. +pub struct ConstGenericQueue { + queue: Vec, +} + +impl ConstGenericQueue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { queue: Vec::new() } + } + + /// Schedules a task to run at a specific time, and returns whether any changes were made. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + self.queue + .iter_mut() + .find(|timer| timer.waker.will_wake(waker)) + .map(|timer| { + if timer.at > at { + timer.at = at; + true + } else { + false + } + }) + .unwrap_or_else(|| { + let mut timer = Timer { + waker: waker.clone(), + at, + }; + + loop { + match self.queue.push(timer) { + Ok(()) => break, + Err(e) => timer = e, + } + + self.queue.pop().unwrap().waker.wake(); + } + + true + }) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_alarm = u64::MAX; + + let mut i = 0; + while i < self.queue.len() { + let timer = &self.queue[i]; + if timer.at <= now { + let timer = self.queue.swap_remove(i); + timer.waker.wake(); + } else { + next_alarm = min(next_alarm, timer.at); + i += 1; + } + } + + next_alarm + } +} + +#[cfg(feature = "generic-queue-8")] +const QUEUE_SIZE: usize = 8; +#[cfg(feature = "generic-queue-16")] +const QUEUE_SIZE: usize = 16; +#[cfg(feature = "generic-queue-32")] +const QUEUE_SIZE: usize = 32; +#[cfg(feature = "generic-queue-64")] +const QUEUE_SIZE: usize = 64; +#[cfg(feature = "generic-queue-128")] +const QUEUE_SIZE: usize = 128; +#[cfg(not(any( + feature = "generic-queue-8", + feature = "generic-queue-16", + feature = "generic-queue-32", + feature = "generic-queue-64", + feature = "generic-queue-128" +)))] +const QUEUE_SIZE: usize = 64; + +/// A timer queue with a pre-determined capacity. +pub struct Queue { + queue: ConstGenericQueue, +} + +impl Queue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { + queue: ConstGenericQueue::new(), + } + } + + /// Schedules a task to run at a specific time, and returns whether any changes were made. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + self.queue.schedule_wake(at, waker) + } + + /// Dequeues expired timers and returns the next alarm time. + pub fn next_expiration(&mut self, now: u64) -> u64 { + self.queue.next_expiration(now) + } +} diff --git a/embassy-time-queue-utils/src/queue_integrated.rs b/embassy-time-queue-utils/src/queue_integrated.rs new file mode 100644 index 0000000..246cf1d --- /dev/null +++ b/embassy-time-queue-utils/src/queue_integrated.rs @@ -0,0 +1,89 @@ +//! Timer queue operations. +use core::cell::Cell; +use core::cmp::min; +use core::task::Waker; + +use embassy_executor::raw::TaskRef; + +/// A timer queue, with items integrated into tasks. +pub struct Queue { + head: Cell>, +} + +impl Queue { + /// Creates a new timer queue. + pub const fn new() -> Self { + Self { head: Cell::new(None) } + } + + /// Schedules a task to run at a specific time. + /// + /// If this function returns `true`, the called should find the next expiration time and set + /// a new alarm for that time. + pub fn schedule_wake(&mut self, at: u64, waker: &Waker) -> bool { + let task = embassy_executor::raw::task_from_waker(waker); + let item = task.timer_queue_item(); + if item.next.get().is_none() { + // If not in the queue, add it and update. + let prev = self.head.replace(Some(task)); + item.next.set(if prev.is_none() { + Some(unsafe { TaskRef::dangling() }) + } else { + prev + }); + item.expires_at.set(at); + true + } else if at <= item.expires_at.get() { + // If expiration is sooner than previously set, update. + item.expires_at.set(at); + true + } else { + // Task does not need to be updated. + false + } + } + + /// Dequeues expired timers and returns the next alarm time. + /// + /// The provided callback will be called for each expired task. Tasks that never expire + /// will be removed, but the callback will not be called. + pub fn next_expiration(&mut self, now: u64) -> u64 { + let mut next_expiration = u64::MAX; + + self.retain(|p| { + let item = p.timer_queue_item(); + let expires = item.expires_at.get(); + + if expires <= now { + // Timer expired, process task. + embassy_executor::raw::wake_task(p); + false + } else { + // Timer didn't yet expire, or never expires. + next_expiration = min(next_expiration, expires); + expires != u64::MAX + } + }); + + next_expiration + } + + fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { + let mut prev = &self.head; + while let Some(p) = prev.get() { + if unsafe { p == TaskRef::dangling() } { + // prev was the last item, stop + break; + } + let item = p.timer_queue_item(); + if f(p) { + // Skip to next + prev = &item.next; + } else { + // Remove it + prev.set(item.next.get()); + item.next.set(None); + } + } + } +} diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md new file mode 100644 index 0000000..09e951c --- /dev/null +++ b/embassy-time/CHANGELOG.md @@ -0,0 +1,74 @@ +# Changelog + +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). + +## 0.4.0 - 2025-01-02 + +- `embassy-time-driver` updated from v0.1 to v0.2. +- embassy-time no longer provides an `embassy-time-queue-driver` implementation + +## 0.3.2 - 2024-08-05 + +- Implement with_timeout()/with_deadline() method style call on Future +- Add collapse_debuginfo to fmt.rs macros. + +## 0.3.1 - 2024-01-11 + +- Add with\_deadline convenience function and example +- Implement Clone for Delay +- Make Ticker::next Send+Sync +- Add timestamp features + +## 0.3.0 - 2024-01-11 + +- Update `embedded-hal-async` to `1.0.0` +- Update `embedded-hal v1` to `1.0.0` +- Split the time driver to a separate `embassy-time-driver` crate. + +## 0.2.0 - 2023-12-04 + +- Added tick rates in multiples of 10 kHz +- Remove nightly and unstable-traits features in preparation for 1.75. +- Update heapless to 0.8. + +## 0.1.5 - 2023-10-16 + +- Added `links` key to Cargo.toml, to prevent multiple copies of this crate in the same binary. + Needed because different copies might get different tick rates, causing + wrong delays if the time driver is using one copy and user code is using another. + This is especially common when mixing crates from crates.io and git. + +## 0.1.4 - 2023-10-12 + +- Added more tick rates + +## 0.1.3 - 2023-08-28 + +- Update `embedded-hal-async` to `1.0.0-rc.2` +- Update `embedded-hal v1` to `1.0.0-rc.2` + +## 0.1.2 - 2023-07-05 + +- Update `embedded-hal-async` to `0.2.0-alpha.2`. +- Update `embedded-hal v1` to `1.0.0-alpha.11`. (Note: v0.2 support is kept unchanged). + +## 0.1.1 - 2023-04-13 + +- Update `embedded-hal-async` to `0.2.0-alpha.1` (uses `async fn` in traits). +- Update `embedded-hal v1` to `1.0.0-alpha.10`. (Note: v0.2 support is kept unchanged). +- Remove dep on `embassy-sync`. +- Fix reentrancy issues in the `std` time driver (#1177) +- Add `Duration::from_hz()`. +- impl `From` conversions to/from `core::time::Duration`. +- Add `#[must_use]` to all futures. +- Add inherent `async fn tick()` to `Ticker`, so you can use it directly without the `Stream` trait. +- Add more tick rates. +- impl `Default` for `Signal` +- Remove unnecessary uses of `atomic-polyfill` + +## 0.1.0 - 2022-08-26 + +- First release diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml new file mode 100644 index 0000000..7aaae89 --- /dev/null +++ b/embassy-time/Cargo.toml @@ -0,0 +1,442 @@ +[package] +name = "embassy-time" +version = "0.4.0" +edition = "2021" +description = "Instant and Duration for embedded no-std systems, with async timer support" +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-time" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-v$VERSION/embassy-time/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/src/" +features = ["defmt", "std"] +target = "x86_64-unknown-linux-gnu" + +[package.metadata.docs.rs] +features = ["defmt", "std"] + +[features] +## Display the time since startup next to defmt log messages. +## At most 1 `defmt-timestamp-uptime-*` feature can be used. +## `defmt-timestamp-uptime` is provided for backwards compatibility (provides the same format as `uptime-us`). +## To use this you must have a time driver provided. +defmt-timestamp-uptime = ["defmt"] +defmt-timestamp-uptime-s = ["defmt"] +defmt-timestamp-uptime-ms = ["defmt"] +defmt-timestamp-uptime-us = ["defmt"] +defmt-timestamp-uptime-ts = ["defmt"] +defmt-timestamp-uptime-tms = ["defmt"] +defmt-timestamp-uptime-tus = ["defmt"] + +#! ### Time Drivers + +#! Usually, time drivers are defined by a HAL, or a companion crate to the HAL. For `std` and WASM +#! environments, as well as for testing purposes, `embassy-time` provides some default time drivers +#! that may be suitable for your use case. You can enable one of the following features to use them. + +## Create a `MockDriver` that can be manually advanced for testing purposes. +mock-driver = ["tick-hz-1_000_000", "dep:embassy-time-queue-utils"] +## Create a time driver for `std` environments. +std = ["tick-hz-1_000_000", "critical-section/std", "dep:embassy-time-queue-utils"] +## Create a time driver for WASM. +wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000", "dep:embassy-time-queue-utils"] + +#! ### Generic Queue + +#! By default embassy-time uses a timer queue implementation that is faster but depends on `embassy-executor`. +#! It will panic if you try to await any timer when using another executor. +#! +#! Alternatively, you can choose to use a "generic" timer queue implementation that works on any executor. +#! To enable it, enable any of the features below. +#! +#! The features also set how many timers are used for the generic queue. At most one +#! `generic-queue-*` feature can be enabled. If none is enabled, `queue_integrated` is used. +#! +#! When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +#! end user to pick. + +## Generic Queue with 8 timers +generic-queue-8 = ["embassy-time-queue-utils/generic-queue-8"] +## Generic Queue with 16 timers +generic-queue-16 = ["embassy-time-queue-utils/generic-queue-16"] +## Generic Queue with 32 timers +generic-queue-32 = ["embassy-time-queue-utils/generic-queue-32"] +## Generic Queue with 64 timers +generic-queue-64 = ["embassy-time-queue-utils/generic-queue-64"] +## Generic Queue with 128 timers +generic-queue-128 = ["embassy-time-queue-utils/generic-queue-128"] + +#! ### Tick Rate +#! +#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. +#! +#! If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*` +#! feature from your binary crate to set the tick rate. The driver will use configured tick rate. +#! If the time driver supports a fixed tick rate, it will enable one feature itself, so you should +#! not enable one. Check the time driver documentation for details. +#! +#! When using embassy-time from libraries, you should *not* enable any `tick-*` feature, to allow the +#! end user or the driver to pick. +#!
+#! Available tick rates: +#! +#! + +# BEGIN TICKS +# Generated by gen_tick.py. DO NOT EDIT. +## 1Hz Tick Rate +tick-hz-1 = ["embassy-time-driver/tick-hz-1"] +## 2Hz Tick Rate +tick-hz-2 = ["embassy-time-driver/tick-hz-2"] +## 4Hz Tick Rate +tick-hz-4 = ["embassy-time-driver/tick-hz-4"] +## 8Hz Tick Rate +tick-hz-8 = ["embassy-time-driver/tick-hz-8"] +## 10Hz Tick Rate +tick-hz-10 = ["embassy-time-driver/tick-hz-10"] +## 16Hz Tick Rate +tick-hz-16 = ["embassy-time-driver/tick-hz-16"] +## 32Hz Tick Rate +tick-hz-32 = ["embassy-time-driver/tick-hz-32"] +## 64Hz Tick Rate +tick-hz-64 = ["embassy-time-driver/tick-hz-64"] +## 100Hz Tick Rate +tick-hz-100 = ["embassy-time-driver/tick-hz-100"] +## 128Hz Tick Rate +tick-hz-128 = ["embassy-time-driver/tick-hz-128"] +## 256Hz Tick Rate +tick-hz-256 = ["embassy-time-driver/tick-hz-256"] +## 512Hz Tick Rate +tick-hz-512 = ["embassy-time-driver/tick-hz-512"] +## 1.0kHz Tick Rate +tick-hz-1_000 = ["embassy-time-driver/tick-hz-1_000"] +## 1.024kHz Tick Rate +tick-hz-1_024 = ["embassy-time-driver/tick-hz-1_024"] +## 2.0kHz Tick Rate +tick-hz-2_000 = ["embassy-time-driver/tick-hz-2_000"] +## 2.048kHz Tick Rate +tick-hz-2_048 = ["embassy-time-driver/tick-hz-2_048"] +## 4.0kHz Tick Rate +tick-hz-4_000 = ["embassy-time-driver/tick-hz-4_000"] +## 4.096kHz Tick Rate +tick-hz-4_096 = ["embassy-time-driver/tick-hz-4_096"] +## 8.0kHz Tick Rate +tick-hz-8_000 = ["embassy-time-driver/tick-hz-8_000"] +## 8.192kHz Tick Rate +tick-hz-8_192 = ["embassy-time-driver/tick-hz-8_192"] +## 10.0kHz Tick Rate +tick-hz-10_000 = ["embassy-time-driver/tick-hz-10_000"] +## 16.0kHz Tick Rate +tick-hz-16_000 = ["embassy-time-driver/tick-hz-16_000"] +## 16.384kHz Tick Rate +tick-hz-16_384 = ["embassy-time-driver/tick-hz-16_384"] +## 20.0kHz Tick Rate +tick-hz-20_000 = ["embassy-time-driver/tick-hz-20_000"] +## 32.0kHz Tick Rate +tick-hz-32_000 = ["embassy-time-driver/tick-hz-32_000"] +## 32.768kHz Tick Rate +tick-hz-32_768 = ["embassy-time-driver/tick-hz-32_768"] +## 40.0kHz Tick Rate +tick-hz-40_000 = ["embassy-time-driver/tick-hz-40_000"] +## 64.0kHz Tick Rate +tick-hz-64_000 = ["embassy-time-driver/tick-hz-64_000"] +## 65.536kHz Tick Rate +tick-hz-65_536 = ["embassy-time-driver/tick-hz-65_536"] +## 80.0kHz Tick Rate +tick-hz-80_000 = ["embassy-time-driver/tick-hz-80_000"] +## 100.0kHz Tick Rate +tick-hz-100_000 = ["embassy-time-driver/tick-hz-100_000"] +## 128.0kHz Tick Rate +tick-hz-128_000 = ["embassy-time-driver/tick-hz-128_000"] +## 131.072kHz Tick Rate +tick-hz-131_072 = ["embassy-time-driver/tick-hz-131_072"] +## 160.0kHz Tick Rate +tick-hz-160_000 = ["embassy-time-driver/tick-hz-160_000"] +## 256.0kHz Tick Rate +tick-hz-256_000 = ["embassy-time-driver/tick-hz-256_000"] +## 262.144kHz Tick Rate +tick-hz-262_144 = ["embassy-time-driver/tick-hz-262_144"] +## 320.0kHz Tick Rate +tick-hz-320_000 = ["embassy-time-driver/tick-hz-320_000"] +## 512.0kHz Tick Rate +tick-hz-512_000 = ["embassy-time-driver/tick-hz-512_000"] +## 524.288kHz Tick Rate +tick-hz-524_288 = ["embassy-time-driver/tick-hz-524_288"] +## 640.0kHz Tick Rate +tick-hz-640_000 = ["embassy-time-driver/tick-hz-640_000"] +## 1.0MHz Tick Rate +tick-hz-1_000_000 = ["embassy-time-driver/tick-hz-1_000_000"] +## 1.024MHz Tick Rate +tick-hz-1_024_000 = ["embassy-time-driver/tick-hz-1_024_000"] +## 1.048576MHz Tick Rate +tick-hz-1_048_576 = ["embassy-time-driver/tick-hz-1_048_576"] +## 1.28MHz Tick Rate +tick-hz-1_280_000 = ["embassy-time-driver/tick-hz-1_280_000"] +## 2.0MHz Tick Rate +tick-hz-2_000_000 = ["embassy-time-driver/tick-hz-2_000_000"] +## 2.048MHz Tick Rate +tick-hz-2_048_000 = ["embassy-time-driver/tick-hz-2_048_000"] +## 2.097152MHz Tick Rate +tick-hz-2_097_152 = ["embassy-time-driver/tick-hz-2_097_152"] +## 2.56MHz Tick Rate +tick-hz-2_560_000 = ["embassy-time-driver/tick-hz-2_560_000"] +## 3.0MHz Tick Rate +tick-hz-3_000_000 = ["embassy-time-driver/tick-hz-3_000_000"] +## 4.0MHz Tick Rate +tick-hz-4_000_000 = ["embassy-time-driver/tick-hz-4_000_000"] +## 4.096MHz Tick Rate +tick-hz-4_096_000 = ["embassy-time-driver/tick-hz-4_096_000"] +## 4.194304MHz Tick Rate +tick-hz-4_194_304 = ["embassy-time-driver/tick-hz-4_194_304"] +## 5.12MHz Tick Rate +tick-hz-5_120_000 = ["embassy-time-driver/tick-hz-5_120_000"] +## 6.0MHz Tick Rate +tick-hz-6_000_000 = ["embassy-time-driver/tick-hz-6_000_000"] +## 8.0MHz Tick Rate +tick-hz-8_000_000 = ["embassy-time-driver/tick-hz-8_000_000"] +## 8.192MHz Tick Rate +tick-hz-8_192_000 = ["embassy-time-driver/tick-hz-8_192_000"] +## 8.388608MHz Tick Rate +tick-hz-8_388_608 = ["embassy-time-driver/tick-hz-8_388_608"] +## 9.0MHz Tick Rate +tick-hz-9_000_000 = ["embassy-time-driver/tick-hz-9_000_000"] +## 10.0MHz Tick Rate +tick-hz-10_000_000 = ["embassy-time-driver/tick-hz-10_000_000"] +## 10.24MHz Tick Rate +tick-hz-10_240_000 = ["embassy-time-driver/tick-hz-10_240_000"] +## 12.0MHz Tick Rate +tick-hz-12_000_000 = ["embassy-time-driver/tick-hz-12_000_000"] +## 16.0MHz Tick Rate +tick-hz-16_000_000 = ["embassy-time-driver/tick-hz-16_000_000"] +## 16.384MHz Tick Rate +tick-hz-16_384_000 = ["embassy-time-driver/tick-hz-16_384_000"] +## 16.777216MHz Tick Rate +tick-hz-16_777_216 = ["embassy-time-driver/tick-hz-16_777_216"] +## 18.0MHz Tick Rate +tick-hz-18_000_000 = ["embassy-time-driver/tick-hz-18_000_000"] +## 20.0MHz Tick Rate +tick-hz-20_000_000 = ["embassy-time-driver/tick-hz-20_000_000"] +## 20.48MHz Tick Rate +tick-hz-20_480_000 = ["embassy-time-driver/tick-hz-20_480_000"] +## 24.0MHz Tick Rate +tick-hz-24_000_000 = ["embassy-time-driver/tick-hz-24_000_000"] +## 30.0MHz Tick Rate +tick-hz-30_000_000 = ["embassy-time-driver/tick-hz-30_000_000"] +## 32.0MHz Tick Rate +tick-hz-32_000_000 = ["embassy-time-driver/tick-hz-32_000_000"] +## 32.768MHz Tick Rate +tick-hz-32_768_000 = ["embassy-time-driver/tick-hz-32_768_000"] +## 36.0MHz Tick Rate +tick-hz-36_000_000 = ["embassy-time-driver/tick-hz-36_000_000"] +## 40.0MHz Tick Rate +tick-hz-40_000_000 = ["embassy-time-driver/tick-hz-40_000_000"] +## 40.96MHz Tick Rate +tick-hz-40_960_000 = ["embassy-time-driver/tick-hz-40_960_000"] +## 48.0MHz Tick Rate +tick-hz-48_000_000 = ["embassy-time-driver/tick-hz-48_000_000"] +## 50.0MHz Tick Rate +tick-hz-50_000_000 = ["embassy-time-driver/tick-hz-50_000_000"] +## 60.0MHz Tick Rate +tick-hz-60_000_000 = ["embassy-time-driver/tick-hz-60_000_000"] +## 64.0MHz Tick Rate +tick-hz-64_000_000 = ["embassy-time-driver/tick-hz-64_000_000"] +## 65.536MHz Tick Rate +tick-hz-65_536_000 = ["embassy-time-driver/tick-hz-65_536_000"] +## 70.0MHz Tick Rate +tick-hz-70_000_000 = ["embassy-time-driver/tick-hz-70_000_000"] +## 72.0MHz Tick Rate +tick-hz-72_000_000 = ["embassy-time-driver/tick-hz-72_000_000"] +## 80.0MHz Tick Rate +tick-hz-80_000_000 = ["embassy-time-driver/tick-hz-80_000_000"] +## 81.92MHz Tick Rate +tick-hz-81_920_000 = ["embassy-time-driver/tick-hz-81_920_000"] +## 90.0MHz Tick Rate +tick-hz-90_000_000 = ["embassy-time-driver/tick-hz-90_000_000"] +## 96.0MHz Tick Rate +tick-hz-96_000_000 = ["embassy-time-driver/tick-hz-96_000_000"] +## 100.0MHz Tick Rate +tick-hz-100_000_000 = ["embassy-time-driver/tick-hz-100_000_000"] +## 110.0MHz Tick Rate +tick-hz-110_000_000 = ["embassy-time-driver/tick-hz-110_000_000"] +## 120.0MHz Tick Rate +tick-hz-120_000_000 = ["embassy-time-driver/tick-hz-120_000_000"] +## 128.0MHz Tick Rate +tick-hz-128_000_000 = ["embassy-time-driver/tick-hz-128_000_000"] +## 130.0MHz Tick Rate +tick-hz-130_000_000 = ["embassy-time-driver/tick-hz-130_000_000"] +## 131.072MHz Tick Rate +tick-hz-131_072_000 = ["embassy-time-driver/tick-hz-131_072_000"] +## 140.0MHz Tick Rate +tick-hz-140_000_000 = ["embassy-time-driver/tick-hz-140_000_000"] +## 144.0MHz Tick Rate +tick-hz-144_000_000 = ["embassy-time-driver/tick-hz-144_000_000"] +## 150.0MHz Tick Rate +tick-hz-150_000_000 = ["embassy-time-driver/tick-hz-150_000_000"] +## 160.0MHz Tick Rate +tick-hz-160_000_000 = ["embassy-time-driver/tick-hz-160_000_000"] +## 163.84MHz Tick Rate +tick-hz-163_840_000 = ["embassy-time-driver/tick-hz-163_840_000"] +## 170.0MHz Tick Rate +tick-hz-170_000_000 = ["embassy-time-driver/tick-hz-170_000_000"] +## 180.0MHz Tick Rate +tick-hz-180_000_000 = ["embassy-time-driver/tick-hz-180_000_000"] +## 190.0MHz Tick Rate +tick-hz-190_000_000 = ["embassy-time-driver/tick-hz-190_000_000"] +## 192.0MHz Tick Rate +tick-hz-192_000_000 = ["embassy-time-driver/tick-hz-192_000_000"] +## 200.0MHz Tick Rate +tick-hz-200_000_000 = ["embassy-time-driver/tick-hz-200_000_000"] +## 210.0MHz Tick Rate +tick-hz-210_000_000 = ["embassy-time-driver/tick-hz-210_000_000"] +## 220.0MHz Tick Rate +tick-hz-220_000_000 = ["embassy-time-driver/tick-hz-220_000_000"] +## 230.0MHz Tick Rate +tick-hz-230_000_000 = ["embassy-time-driver/tick-hz-230_000_000"] +## 240.0MHz Tick Rate +tick-hz-240_000_000 = ["embassy-time-driver/tick-hz-240_000_000"] +## 250.0MHz Tick Rate +tick-hz-250_000_000 = ["embassy-time-driver/tick-hz-250_000_000"] +## 256.0MHz Tick Rate +tick-hz-256_000_000 = ["embassy-time-driver/tick-hz-256_000_000"] +## 260.0MHz Tick Rate +tick-hz-260_000_000 = ["embassy-time-driver/tick-hz-260_000_000"] +## 262.144MHz Tick Rate +tick-hz-262_144_000 = ["embassy-time-driver/tick-hz-262_144_000"] +## 270.0MHz Tick Rate +tick-hz-270_000_000 = ["embassy-time-driver/tick-hz-270_000_000"] +## 280.0MHz Tick Rate +tick-hz-280_000_000 = ["embassy-time-driver/tick-hz-280_000_000"] +## 288.0MHz Tick Rate +tick-hz-288_000_000 = ["embassy-time-driver/tick-hz-288_000_000"] +## 290.0MHz Tick Rate +tick-hz-290_000_000 = ["embassy-time-driver/tick-hz-290_000_000"] +## 300.0MHz Tick Rate +tick-hz-300_000_000 = ["embassy-time-driver/tick-hz-300_000_000"] +## 320.0MHz Tick Rate +tick-hz-320_000_000 = ["embassy-time-driver/tick-hz-320_000_000"] +## 327.68MHz Tick Rate +tick-hz-327_680_000 = ["embassy-time-driver/tick-hz-327_680_000"] +## 340.0MHz Tick Rate +tick-hz-340_000_000 = ["embassy-time-driver/tick-hz-340_000_000"] +## 360.0MHz Tick Rate +tick-hz-360_000_000 = ["embassy-time-driver/tick-hz-360_000_000"] +## 380.0MHz Tick Rate +tick-hz-380_000_000 = ["embassy-time-driver/tick-hz-380_000_000"] +## 384.0MHz Tick Rate +tick-hz-384_000_000 = ["embassy-time-driver/tick-hz-384_000_000"] +## 400.0MHz Tick Rate +tick-hz-400_000_000 = ["embassy-time-driver/tick-hz-400_000_000"] +## 420.0MHz Tick Rate +tick-hz-420_000_000 = ["embassy-time-driver/tick-hz-420_000_000"] +## 440.0MHz Tick Rate +tick-hz-440_000_000 = ["embassy-time-driver/tick-hz-440_000_000"] +## 460.0MHz Tick Rate +tick-hz-460_000_000 = ["embassy-time-driver/tick-hz-460_000_000"] +## 480.0MHz Tick Rate +tick-hz-480_000_000 = ["embassy-time-driver/tick-hz-480_000_000"] +## 500.0MHz Tick Rate +tick-hz-500_000_000 = ["embassy-time-driver/tick-hz-500_000_000"] +## 512.0MHz Tick Rate +tick-hz-512_000_000 = ["embassy-time-driver/tick-hz-512_000_000"] +## 520.0MHz Tick Rate +tick-hz-520_000_000 = ["embassy-time-driver/tick-hz-520_000_000"] +## 524.288MHz Tick Rate +tick-hz-524_288_000 = ["embassy-time-driver/tick-hz-524_288_000"] +## 540.0MHz Tick Rate +tick-hz-540_000_000 = ["embassy-time-driver/tick-hz-540_000_000"] +## 560.0MHz Tick Rate +tick-hz-560_000_000 = ["embassy-time-driver/tick-hz-560_000_000"] +## 576.0MHz Tick Rate +tick-hz-576_000_000 = ["embassy-time-driver/tick-hz-576_000_000"] +## 580.0MHz Tick Rate +tick-hz-580_000_000 = ["embassy-time-driver/tick-hz-580_000_000"] +## 600.0MHz Tick Rate +tick-hz-600_000_000 = ["embassy-time-driver/tick-hz-600_000_000"] +## 620.0MHz Tick Rate +tick-hz-620_000_000 = ["embassy-time-driver/tick-hz-620_000_000"] +## 640.0MHz Tick Rate +tick-hz-640_000_000 = ["embassy-time-driver/tick-hz-640_000_000"] +## 655.36MHz Tick Rate +tick-hz-655_360_000 = ["embassy-time-driver/tick-hz-655_360_000"] +## 660.0MHz Tick Rate +tick-hz-660_000_000 = ["embassy-time-driver/tick-hz-660_000_000"] +## 680.0MHz Tick Rate +tick-hz-680_000_000 = ["embassy-time-driver/tick-hz-680_000_000"] +## 700.0MHz Tick Rate +tick-hz-700_000_000 = ["embassy-time-driver/tick-hz-700_000_000"] +## 720.0MHz Tick Rate +tick-hz-720_000_000 = ["embassy-time-driver/tick-hz-720_000_000"] +## 740.0MHz Tick Rate +tick-hz-740_000_000 = ["embassy-time-driver/tick-hz-740_000_000"] +## 760.0MHz Tick Rate +tick-hz-760_000_000 = ["embassy-time-driver/tick-hz-760_000_000"] +## 768.0MHz Tick Rate +tick-hz-768_000_000 = ["embassy-time-driver/tick-hz-768_000_000"] +## 780.0MHz Tick Rate +tick-hz-780_000_000 = ["embassy-time-driver/tick-hz-780_000_000"] +## 800.0MHz Tick Rate +tick-hz-800_000_000 = ["embassy-time-driver/tick-hz-800_000_000"] +## 820.0MHz Tick Rate +tick-hz-820_000_000 = ["embassy-time-driver/tick-hz-820_000_000"] +## 840.0MHz Tick Rate +tick-hz-840_000_000 = ["embassy-time-driver/tick-hz-840_000_000"] +## 860.0MHz Tick Rate +tick-hz-860_000_000 = ["embassy-time-driver/tick-hz-860_000_000"] +## 880.0MHz Tick Rate +tick-hz-880_000_000 = ["embassy-time-driver/tick-hz-880_000_000"] +## 900.0MHz Tick Rate +tick-hz-900_000_000 = ["embassy-time-driver/tick-hz-900_000_000"] +## 920.0MHz Tick Rate +tick-hz-920_000_000 = ["embassy-time-driver/tick-hz-920_000_000"] +## 940.0MHz Tick Rate +tick-hz-940_000_000 = ["embassy-time-driver/tick-hz-940_000_000"] +## 960.0MHz Tick Rate +tick-hz-960_000_000 = ["embassy-time-driver/tick-hz-960_000_000"] +## 980.0MHz Tick Rate +tick-hz-980_000_000 = ["embassy-time-driver/tick-hz-980_000_000"] +## 1.0GHz Tick Rate +tick-hz-1_000_000_000 = ["embassy-time-driver/tick-hz-1_000_000_000"] +## 1.31072GHz Tick Rate +tick-hz-1_310_720_000 = ["embassy-time-driver/tick-hz-1_310_720_000"] +## 2.62144GHz Tick Rate +tick-hz-2_621_440_000 = ["embassy-time-driver/tick-hz-2_621_440_000"] +## 5.24288GHz Tick Rate +tick-hz-5_242_880_000 = ["embassy-time-driver/tick-hz-5_242_880_000"] +# END TICKS + +#!
+ +[dependencies] +embassy-time-driver = { version = "0.2", path = "../embassy-time-driver" } +embassy-time-queue-utils = { version = "0.1", path = "../embassy-time-queue-utils", optional = true} + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } +embedded-hal-1 = { package = "embedded-hal", version = "1.0" } +embedded-hal-async = { version = "1.0" } + +futures-util = { version = "0.3.17", default-features = false } +critical-section = "1.1" +cfg-if = "1.0.0" + +document-features = "0.2.7" + +# WASM dependencies +wasm-bindgen = { version = "0.2.81", optional = true } +js-sys = { version = "0.3", optional = true } +wasm-timer = { version = "0.2.5", optional = true } + +[dev-dependencies] +serial_test = "0.9" +critical-section = { version = "1.1", features = ["std"] } +embassy-executor = { version = "0.7.0", path = "../embassy-executor" } diff --git a/embassy-time/README.md b/embassy-time/README.md new file mode 100644 index 0000000..6a4b049 --- /dev/null +++ b/embassy-time/README.md @@ -0,0 +1,47 @@ +# embassy-time + +Timekeeping, delays and timeouts. + +Timekeeping is done with elapsed time since system boot. Time is represented in +ticks, where the tick rate is defined either by the driver (in the case of a fixed-rate +tick) or chosen by the user with a [tick rate](#tick-rate) feature. The chosen +tick rate applies to everything in `embassy-time` and thus determines the maximum +timing resolution of (1 / tick_rate) seconds. + +Tick counts are 64 bits. The default tick rate of 1Mhz supports +representing time spans of up to ~584558 years, which is big enough for all practical +purposes and allows not having to worry about overflows. + +## Global time driver + +The `time` module is backed by a global "time driver" specified at build time. +Only one driver can be active in a program. + +All methods and structs transparently call into the active driver. This makes it +possible for libraries to use `embassy_time` in a driver-agnostic way without +requiring generic parameters. + +For more details, check the [`embassy_time_driver`](https://crates.io/crates/embassy-time-driver) crate. + +## Instants and Durations + +[`Instant`] represents a given instant of time (relative to system boot), and [`Duration`] +represents the duration of a span of time. They implement the math operations you'd expect, +like addition and substraction. + +## Delays and timeouts + +[`Timer`] allows performing async delays. [`Ticker`] allows periodic delays without drifting over time. + +An implementation of the `embedded-hal` delay traits is provided by [`Delay`], for compatibility +with libraries from the ecosystem. + +## Wall-clock time + +The `time` module deals exclusively with a monotonically increasing tick count. +Therefore it has no direct support for wall-clock time ("real life" datetimes +like `2021-08-24 13:33:21`). + +If persistence across reboots is not needed, support can be built on top of +`embassy_time` by storing the offset between "seconds elapsed since boot" +and "seconds since unix epoch". diff --git a/embassy-time/src/delay.rs b/embassy-time/src/delay.rs new file mode 100644 index 0000000..f77859d --- /dev/null +++ b/embassy-time/src/delay.rs @@ -0,0 +1,81 @@ +use super::{Duration, Instant}; +use crate::Timer; + +/// Blocks for at least `duration`. +pub fn block_for(duration: Duration) { + let expires_at = Instant::now() + duration; + while Instant::now() < expires_at {} +} + +/// Type implementing async delays and blocking `embedded-hal` delays. +/// +/// The delays are implemented in a "best-effort" way, meaning that the cpu will block for at least +/// the amount provided, but accuracy can be affected by many factors, including interrupt usage. +/// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently +/// active driver. +#[derive(Clone)] +pub struct Delay; + +impl embedded_hal_1::delay::DelayNs for Delay { + fn delay_ns(&mut self, ns: u32) { + block_for(Duration::from_nanos(ns as u64)) + } + + fn delay_us(&mut self, us: u32) { + block_for(Duration::from_micros(us as u64)) + } + + fn delay_ms(&mut self, ms: u32) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_async::delay::DelayNs for Delay { + async fn delay_ns(&mut self, ns: u32) { + Timer::after_nanos(ns as _).await + } + + async fn delay_us(&mut self, us: u32) { + Timer::after_micros(us as _).await + } + + async fn delay_ms(&mut self, ms: u32) { + Timer::after_millis(ms as _).await + } +} + +impl embedded_hal_02::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u8) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u16) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayMs for Delay { + fn delay_ms(&mut self, ms: u32) { + block_for(Duration::from_millis(ms as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u8) { + block_for(Duration::from_micros(us as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u16) { + block_for(Duration::from_micros(us as u64)) + } +} + +impl embedded_hal_02::blocking::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u32) { + block_for(Duration::from_micros(us as u64)) + } +} diff --git a/embassy-time/src/driver_mock.rs b/embassy-time/src/driver_mock.rs new file mode 100644 index 0000000..bb1961b --- /dev/null +++ b/embassy-time/src/driver_mock.rs @@ -0,0 +1,145 @@ +use core::cell::RefCell; +use core::task::Waker; + +use critical_section::Mutex as CsMutex; +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; + +use crate::{Duration, Instant}; + +/// A mock driver that can be manually advanced. +/// This is useful for testing code that works with [`Instant`] and [`Duration`]. +/// +/// This driver can also be used to test runtime functionality, such as +/// timers, delays, etc. +/// +/// # Example +/// +/// ```ignore +/// fn has_a_second_passed(reference: Instant) -> bool { +/// Instant::now().duration_since(reference) >= Duration::from_secs(1) +/// } +/// +/// fn test_second_passed() { +/// let driver = embassy_time::MockDriver::get(); +/// let reference = Instant::now(); +/// assert_eq!(false, has_a_second_passed(reference)); +/// driver.advance(Duration::from_secs(1)); +/// assert_eq!(true, has_a_second_passed(reference)); +/// } +/// ``` +pub struct MockDriver(CsMutex>); + +embassy_time_driver::time_driver_impl!(static DRIVER: MockDriver = MockDriver::new()); + +impl MockDriver { + /// Creates a new mock driver. + pub const fn new() -> Self { + Self(CsMutex::new(RefCell::new(InnerMockDriver::new()))) + } + + /// Gets a reference to the global mock driver. + pub fn get() -> &'static MockDriver { + &DRIVER + } + + /// Resets the internal state of the mock driver + /// This will clear and deallocate all alarms, and reset the current time to 0. + pub fn reset(&self) { + critical_section::with(|cs| { + self.0.borrow(cs).replace(InnerMockDriver::new()); + }); + } + + /// Advances the time by the specified [`Duration`]. + /// Calling any alarm callbacks that are due. + pub fn advance(&self, duration: Duration) { + critical_section::with(|cs| { + let inner = &mut *self.0.borrow_ref_mut(cs); + + inner.now += duration; + // wake expired tasks. + inner.queue.next_expiration(inner.now.as_ticks()); + }) + } +} + +impl Driver for MockDriver { + fn now(&self) -> u64 { + critical_section::with(|cs| self.0.borrow_ref(cs).now).as_ticks() + } + + fn schedule_wake(&self, at: u64, waker: &Waker) { + critical_section::with(|cs| { + let inner = &mut *self.0.borrow_ref_mut(cs); + // enqueue it + inner.queue.schedule_wake(at, waker); + // wake it if it's in the past. + inner.queue.next_expiration(inner.now.as_ticks()); + }) + } +} + +struct InnerMockDriver { + now: Instant, + queue: Queue, +} + +impl InnerMockDriver { + const fn new() -> Self { + Self { + now: Instant::from_ticks(0), + queue: Queue::new(), + } + } +} + +#[cfg(test)] +mod tests { + use core::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::task::Wake; + + use serial_test::serial; + + use super::*; + + fn setup() { + DRIVER.reset(); + } + + #[test] + #[serial] + fn test_advance() { + setup(); + + let driver = MockDriver::get(); + let reference = driver.now(); + driver.advance(Duration::from_secs(1)); + assert_eq!(Duration::from_secs(1).as_ticks(), driver.now() - reference); + } + + #[test] + #[serial] + fn test_schedule_wake() { + setup(); + + static CALLBACK_CALLED: AtomicBool = AtomicBool::new(false); + + struct MockWaker; + + impl Wake for MockWaker { + fn wake(self: Arc) { + CALLBACK_CALLED.store(true, Ordering::Relaxed); + } + } + let waker = Arc::new(MockWaker).into(); + + let driver = MockDriver::get(); + + driver.schedule_wake(driver.now() + 1, &waker); + assert_eq!(false, CALLBACK_CALLED.load(Ordering::Relaxed)); + driver.advance(Duration::from_secs(1)); + assert_eq!(true, CALLBACK_CALLED.load(Ordering::Relaxed)); + } +} diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs new file mode 100644 index 0000000..87d7ef7 --- /dev/null +++ b/embassy-time/src/driver_std.rs @@ -0,0 +1,104 @@ +use std::sync::{Condvar, Mutex}; +use std::thread; +use std::time::{Duration as StdDuration, Instant as StdInstant}; + +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; + +struct TimeDriver { + signaler: Signaler, + inner: Mutex, +} + +struct Inner { + zero_instant: Option, + queue: Queue, +} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + inner: Mutex::new(Inner{ + zero_instant: None, + queue: Queue::new(), + }), + signaler: Signaler::new(), +}); + +impl Inner { + fn init(&mut self) -> StdInstant { + *self.zero_instant.get_or_insert_with(|| { + thread::spawn(alarm_thread); + StdInstant::now() + }) + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + let mut inner = self.inner.lock().unwrap(); + let zero = inner.init(); + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + let mut inner = self.inner.lock().unwrap(); + inner.init(); + if inner.queue.schedule_wake(at, waker) { + self.signaler.signal(); + } + } +} + +fn alarm_thread() { + let zero = DRIVER.inner.lock().unwrap().zero_instant.unwrap(); + loop { + let now = DRIVER.now(); + + let next_alarm = DRIVER.inner.lock().unwrap().queue.next_expiration(now); + + // Ensure we don't overflow + let until = zero + .checked_add(StdDuration::from_micros(next_alarm)) + .unwrap_or_else(|| StdInstant::now() + StdDuration::from_secs(1)); + + DRIVER.signaler.wait_until(until); + } +} + +struct Signaler { + mutex: Mutex, + condvar: Condvar, +} + +impl Signaler { + const fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait_until(&self, until: StdInstant) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + let now = StdInstant::now(); + + if now >= until { + break; + } + + let dur = until - now; + let (signaled2, timeout) = self.condvar.wait_timeout(signaled, dur).unwrap(); + signaled = signaled2; + if timeout.timed_out() { + break; + } + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } +} diff --git a/embassy-time/src/driver_wasm.rs b/embassy-time/src/driver_wasm.rs new file mode 100644 index 0000000..e320769 --- /dev/null +++ b/embassy-time/src/driver_wasm.rs @@ -0,0 +1,103 @@ +use std::sync::Mutex; + +use embassy_time_driver::Driver; +use embassy_time_queue_utils::Queue; +use wasm_bindgen::prelude::*; +use wasm_timer::Instant as StdInstant; + +struct AlarmState { + token: Option, +} + +impl AlarmState { + const fn new() -> Self { + Self { token: None } + } +} + +#[wasm_bindgen] +extern "C" { + fn setTimeout(closure: &Closure, millis: u32) -> f64; + fn clearTimeout(token: f64); +} + +struct TimeDriver { + inner: Mutex, +} + +struct Inner { + alarm: AlarmState, + zero_instant: Option, + queue: Queue, + closure: Option>, +} + +unsafe impl Send for Inner {} + +embassy_time_driver::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { + inner: Mutex::new(Inner{ + zero_instant: None, + queue: Queue::new(), + alarm: AlarmState::new(), + closure: None, + }), +}); + +impl Inner { + fn init(&mut self) -> StdInstant { + *self.zero_instant.get_or_insert_with(StdInstant::now) + } + + fn now(&mut self) -> u64 { + StdInstant::now().duration_since(self.zero_instant.unwrap()).as_micros() as u64 + } + + fn set_alarm(&mut self, timestamp: u64) -> bool { + if let Some(token) = self.alarm.token { + clearTimeout(token); + } + + let now = self.now(); + if timestamp <= now { + false + } else { + let timeout = (timestamp - now) as u32; + let closure = self.closure.get_or_insert_with(|| Closure::new(dispatch)); + self.alarm.token = Some(setTimeout(closure, timeout / 1000)); + + true + } + } +} + +impl Driver for TimeDriver { + fn now(&self) -> u64 { + let mut inner = self.inner.lock().unwrap(); + let zero = inner.init(); + StdInstant::now().duration_since(zero).as_micros() as u64 + } + + fn schedule_wake(&self, at: u64, waker: &core::task::Waker) { + let mut inner = self.inner.lock().unwrap(); + inner.init(); + if inner.queue.schedule_wake(at, waker) { + let now = inner.now(); + let mut next = inner.queue.next_expiration(now); + while !inner.set_alarm(next) { + let now = inner.now(); + next = inner.queue.next_expiration(now); + } + } + } +} + +fn dispatch() { + let inner = &mut *DRIVER.inner.lock().unwrap(); + + let now = inner.now(); + let mut next = inner.queue.next_expiration(now); + while !inner.set_alarm(next) { + let now = inner.now(); + next = inner.queue.next_expiration(now); + } +} diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs new file mode 100644 index 0000000..dcda705 --- /dev/null +++ b/embassy-time/src/duration.rs @@ -0,0 +1,295 @@ +use core::fmt; +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; + +use super::{GCD_1K, GCD_1M, TICK_HZ}; +use crate::GCD_1G; + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Represents the difference between two [Instant](struct.Instant.html)s +pub struct Duration { + pub(crate) ticks: u64, +} + +impl Duration { + /// The smallest value that can be represented by the `Duration` type. + pub const MIN: Duration = Duration { ticks: u64::MIN }; + /// The largest value that can be represented by the `Duration` type. + pub const MAX: Duration = Duration { ticks: u64::MAX }; + + /// Tick count of the `Duration`. + pub const fn as_ticks(&self) -> u64 { + self.ticks + } + + /// Convert the `Duration` to seconds, rounding down. + pub const fn as_secs(&self) -> u64 { + self.ticks / TICK_HZ + } + + /// Convert the `Duration` to milliseconds, rounding down. + pub const fn as_millis(&self) -> u64 { + self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K) + } + + /// Convert the `Duration` to microseconds, rounding down. + pub const fn as_micros(&self) -> u64 { + self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M) + } + + /// Creates a duration from the specified number of clock ticks + pub const fn from_ticks(ticks: u64) -> Duration { + Duration { ticks } + } + + /// Creates a duration from the specified number of seconds, rounding up. + pub const fn from_secs(secs: u64) -> Duration { + Duration { ticks: secs * TICK_HZ } + } + + /// Creates a duration from the specified number of milliseconds, rounding up. + pub const fn from_millis(millis: u64) -> Duration { + Duration { + ticks: div_ceil(millis * (TICK_HZ / GCD_1K), 1000 / GCD_1K), + } + } + + /// Creates a duration from the specified number of microseconds, rounding up. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_micros(micros: u64) -> Duration { + Duration { + ticks: div_ceil(micros * (TICK_HZ / GCD_1M), 1_000_000 / GCD_1M), + } + } + + /// Creates a duration from the specified number of nanoseconds, rounding up. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_nanos(nanoseconds: u64) -> Duration { + Duration { + ticks: div_ceil(nanoseconds * (TICK_HZ / GCD_1G), 1_000_000_000 / GCD_1G), + } + } + + /// Creates a duration from the specified number of seconds, rounding down. + pub const fn from_secs_floor(secs: u64) -> Duration { + Duration { ticks: secs * TICK_HZ } + } + + /// Creates a duration from the specified number of milliseconds, rounding down. + pub const fn from_millis_floor(millis: u64) -> Duration { + Duration { + ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K), + } + } + + /// Creates a duration from the specified number of microseconds, rounding down. + /// NOTE: Delays this small may be inaccurate. + pub const fn from_micros_floor(micros: u64) -> Duration { + Duration { + ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M), + } + } + + /// Try to create a duration from the specified number of seconds, rounding up. + /// Fails if the number of seconds is too large. + pub const fn try_from_secs(secs: u64) -> Option { + let Some(ticks) = secs.checked_mul(TICK_HZ) else { + return None; + }; + Some(Duration { ticks }) + } + + /// Try to create a duration from the specified number of milliseconds, rounding up. + /// Fails if the number of milliseconds is too large. + pub const fn try_from_millis(millis: u64) -> Option { + let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else { + return None; + }; + Some(Duration { + ticks: div_ceil(value, 1000 / GCD_1K), + }) + } + + /// Try to create a duration from the specified number of microseconds, rounding up. + /// Fails if the number of microseconds is too large. + /// NOTE: Delays this small may be inaccurate. + pub const fn try_from_micros(micros: u64) -> Option { + let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else { + return None; + }; + Some(Duration { + ticks: div_ceil(value, 1_000_000 / GCD_1M), + }) + } + + /// Try to create a duration from the specified number of nanoseconds, rounding up. + /// Fails if the number of nanoseconds is too large. + /// NOTE: Delays this small may be inaccurate. + pub const fn try_from_nanos(nanoseconds: u64) -> Option { + let Some(value) = nanoseconds.checked_mul(TICK_HZ / GCD_1G) else { + return None; + }; + Some(Duration { + ticks: div_ceil(value, 1_000_000_000 / GCD_1G), + }) + } + + /// Try to create a duration from the specified number of seconds, rounding down. + /// Fails if the number of seconds is too large. + pub const fn try_from_secs_floor(secs: u64) -> Option { + let Some(ticks) = secs.checked_mul(TICK_HZ) else { + return None; + }; + Some(Duration { ticks }) + } + + /// Try to create a duration from the specified number of milliseconds, rounding down. + /// Fails if the number of milliseconds is too large. + pub const fn try_from_millis_floor(millis: u64) -> Option { + let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else { + return None; + }; + Some(Duration { + ticks: value / (1000 / GCD_1K), + }) + } + + /// Try to create a duration from the specified number of microseconds, rounding down. + /// Fails if the number of microseconds is too large. + /// NOTE: Delays this small may be inaccurate. + pub const fn try_from_micros_floor(micros: u64) -> Option { + let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else { + return None; + }; + Some(Duration { + ticks: value / (1_000_000 / GCD_1M), + }) + } + + /// Creates a duration corresponding to the specified Hz. + /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1 + /// tick. Doing so will not deadlock, but will certainly not produce the desired output. + pub const fn from_hz(hz: u64) -> Duration { + let ticks = { + if hz >= TICK_HZ { + 1 + } else { + (TICK_HZ + hz / 2) / hz + } + }; + Duration { ticks } + } + + /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. + pub fn checked_add(self, rhs: Duration) -> Option { + self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) + } + + /// Subtracts one Duration to another, returning a new Duration or None in the event of an overflow. + pub fn checked_sub(self, rhs: Duration) -> Option { + self.ticks.checked_sub(rhs.ticks).map(|ticks| Duration { ticks }) + } + + /// Multiplies one Duration by a scalar u32, returning a new Duration or None in the event of an overflow. + pub fn checked_mul(self, rhs: u32) -> Option { + self.ticks.checked_mul(rhs as _).map(|ticks| Duration { ticks }) + } + + /// Divides one Duration a scalar u32, returning a new Duration or None in the event of an overflow. + pub fn checked_div(self, rhs: u32) -> Option { + self.ticks.checked_div(rhs as _).map(|ticks| Duration { ticks }) + } +} + +impl Add for Duration { + type Output = Duration; + + fn add(self, rhs: Duration) -> Duration { + self.checked_add(rhs).expect("overflow when adding durations") + } +} + +impl AddAssign for Duration { + fn add_assign(&mut self, rhs: Duration) { + *self = *self + rhs; + } +} + +impl Sub for Duration { + type Output = Duration; + + fn sub(self, rhs: Duration) -> Duration { + self.checked_sub(rhs).expect("overflow when subtracting durations") + } +} + +impl SubAssign for Duration { + fn sub_assign(&mut self, rhs: Duration) { + *self = *self - rhs; + } +} + +impl Mul for Duration { + type Output = Duration; + + fn mul(self, rhs: u32) -> Duration { + self.checked_mul(rhs) + .expect("overflow when multiplying duration by scalar") + } +} + +impl Mul for u32 { + type Output = Duration; + + fn mul(self, rhs: Duration) -> Duration { + rhs * self + } +} + +impl MulAssign for Duration { + fn mul_assign(&mut self, rhs: u32) { + *self = *self * rhs; + } +} + +impl Div for Duration { + type Output = Duration; + + fn div(self, rhs: u32) -> Duration { + self.checked_div(rhs) + .expect("divide by zero error when dividing duration by scalar") + } +} + +impl DivAssign for Duration { + fn div_assign(&mut self, rhs: u32) { + *self = *self / rhs; + } +} + +impl<'a> fmt::Display for Duration { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ticks", self.ticks) + } +} + +#[inline] +const fn div_ceil(num: u64, den: u64) -> u64 { + (num + den - 1) / den +} + +impl TryFrom for Duration { + type Error = >::Error; + + /// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64. + fn try_from(value: core::time::Duration) -> Result { + Ok(Self::from_micros(value.as_micros().try_into()?)) + } +} + +impl From for core::time::Duration { + /// Converts using [`Duration::as_micros`]. + fn from(value: Duration) -> Self { + core::time::Duration::from_micros(value.as_micros()) + } +} diff --git a/embassy-time/src/fmt.rs b/embassy-time/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-time/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-time/src/instant.rs b/embassy-time/src/instant.rs new file mode 100644 index 0000000..6571bea --- /dev/null +++ b/embassy-time/src/instant.rs @@ -0,0 +1,205 @@ +use core::fmt; +use core::ops::{Add, AddAssign, Sub, SubAssign}; + +use super::{Duration, GCD_1K, GCD_1M, TICK_HZ}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// An Instant in time, based on the MCU's clock ticks since startup. +pub struct Instant { + ticks: u64, +} + +impl Instant { + /// The smallest (earliest) value that can be represented by the `Instant` type. + pub const MIN: Instant = Instant { ticks: u64::MIN }; + /// The largest (latest) value that can be represented by the `Instant` type. + pub const MAX: Instant = Instant { ticks: u64::MAX }; + + /// Returns an Instant representing the current time. + #[inline] + pub fn now() -> Instant { + Instant { + ticks: embassy_time_driver::now(), + } + } + + /// Create an Instant from a tick count since system boot. + pub const fn from_ticks(ticks: u64) -> Self { + Self { ticks } + } + + /// Create an Instant from a microsecond count since system boot. + pub const fn from_micros(micros: u64) -> Self { + Self { + ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M), + } + } + + /// Create an Instant from a millisecond count since system boot. + pub const fn from_millis(millis: u64) -> Self { + Self { + ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K), + } + } + + /// Create an Instant from a second count since system boot. + pub const fn from_secs(seconds: u64) -> Self { + Self { + ticks: seconds * TICK_HZ, + } + } + + /// Try to create an Instant from a microsecond count since system boot. + /// Fails if the number of microseconds is too large. + pub const fn try_from_micros(micros: u64) -> Option { + let Some(value) = micros.checked_mul(TICK_HZ / GCD_1M) else { + return None; + }; + Some(Self { + ticks: value / (1_000_000 / GCD_1M), + }) + } + + /// Try to create an Instant from a millisecond count since system boot. + /// Fails if the number of milliseconds is too large. + pub const fn try_from_millis(millis: u64) -> Option { + let Some(value) = millis.checked_mul(TICK_HZ / GCD_1K) else { + return None; + }; + Some(Self { + ticks: value / (1000 / GCD_1K), + }) + } + + /// Try to create an Instant from a second count since system boot. + /// Fails if the number of seconds is too large. + pub const fn try_from_secs(seconds: u64) -> Option { + let Some(ticks) = seconds.checked_mul(TICK_HZ) else { + return None; + }; + Some(Self { ticks }) + } + + /// Tick count since system boot. + pub const fn as_ticks(&self) -> u64 { + self.ticks + } + + /// Seconds since system boot. + pub const fn as_secs(&self) -> u64 { + self.ticks / TICK_HZ + } + + /// Milliseconds since system boot. + pub const fn as_millis(&self) -> u64 { + self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K) + } + + /// Microseconds since system boot. + pub const fn as_micros(&self) -> u64 { + self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M) + } + + /// Duration between this Instant and another Instant + /// Panics on over/underflow. + pub fn duration_since(&self, earlier: Instant) -> Duration { + Duration { + ticks: unwrap!(self.ticks.checked_sub(earlier.ticks)), + } + } + + /// Duration between this Instant and another Instant + pub fn checked_duration_since(&self, earlier: Instant) -> Option { + if self.ticks < earlier.ticks { + None + } else { + Some(Duration { + ticks: self.ticks - earlier.ticks, + }) + } + } + + /// Returns the duration since the "earlier" Instant. + /// If the "earlier" instant is in the future, the duration is set to zero. + pub fn saturating_duration_since(&self, earlier: Instant) -> Duration { + Duration { + ticks: if self.ticks < earlier.ticks { + 0 + } else { + self.ticks - earlier.ticks + }, + } + } + + /// Duration elapsed since this Instant. + pub fn elapsed(&self) -> Duration { + Instant::now() - *self + } + + /// Adds one Duration to self, returning a new `Instant` or None in the event of an overflow. + pub fn checked_add(&self, duration: Duration) -> Option { + self.ticks.checked_add(duration.ticks).map(|ticks| Instant { ticks }) + } + + /// Subtracts one Duration to self, returning a new `Instant` or None in the event of an overflow. + pub fn checked_sub(&self, duration: Duration) -> Option { + self.ticks.checked_sub(duration.ticks).map(|ticks| Instant { ticks }) + } + + /// Adds a Duration to self. In case of overflow, the maximum value is returned. + pub fn saturating_add(mut self, duration: Duration) -> Self { + self.ticks = self.ticks.saturating_add(duration.ticks); + self + } + + /// Subtracts a Duration from self. In case of overflow, the minimum value is returned. + pub fn saturating_sub(mut self, duration: Duration) -> Self { + self.ticks = self.ticks.saturating_sub(duration.ticks); + self + } +} + +impl Add for Instant { + type Output = Instant; + + fn add(self, other: Duration) -> Instant { + self.checked_add(other) + .expect("overflow when adding duration to instant") + } +} + +impl AddAssign for Instant { + fn add_assign(&mut self, other: Duration) { + *self = *self + other; + } +} + +impl Sub for Instant { + type Output = Instant; + + fn sub(self, other: Duration) -> Instant { + self.checked_sub(other) + .expect("overflow when subtracting duration from instant") + } +} + +impl SubAssign for Instant { + fn sub_assign(&mut self, other: Duration) { + *self = *self - other; + } +} + +impl Sub for Instant { + type Output = Duration; + + fn sub(self, other: Instant) -> Duration { + self.duration_since(other) + } +} + +impl<'a> fmt::Display for Instant { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} ticks", self.ticks) + } +} diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs new file mode 100644 index 0000000..80a3594 --- /dev/null +++ b/embassy-time/src/lib.rs @@ -0,0 +1,63 @@ +#![cfg_attr(not(any(feature = "std", feature = "wasm", test)), no_std)] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![allow(clippy::new_without_default)] +#![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; + +mod delay; +mod duration; +mod instant; +mod timer; + +#[cfg(feature = "mock-driver")] +mod driver_mock; + +#[cfg(feature = "mock-driver")] +pub use driver_mock::MockDriver; + +#[cfg(feature = "std")] +mod driver_std; +#[cfg(feature = "wasm")] +mod driver_wasm; + +pub use delay::{block_for, Delay}; +pub use duration::Duration; +pub use embassy_time_driver::TICK_HZ; +pub use instant::Instant; +pub use timer::{with_deadline, with_timeout, Ticker, TimeoutError, Timer, WithTimeout}; + +const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } +} + +pub(crate) const GCD_1K: u64 = gcd(TICK_HZ, 1_000); +pub(crate) const GCD_1M: u64 = gcd(TICK_HZ, 1_000_000); +pub(crate) const GCD_1G: u64 = gcd(TICK_HZ, 1_000_000_000); + +#[cfg(feature = "defmt-timestamp-uptime-s")] +defmt::timestamp! {"{=u64}", Instant::now().as_secs() } + +#[cfg(feature = "defmt-timestamp-uptime-ms")] +defmt::timestamp! {"{=u64:ms}", Instant::now().as_millis() } + +#[cfg(any(feature = "defmt-timestamp-uptime", feature = "defmt-timestamp-uptime-us"))] +defmt::timestamp! {"{=u64:us}", Instant::now().as_micros() } + +#[cfg(feature = "defmt-timestamp-uptime-ts")] +defmt::timestamp! {"{=u64:ts}", Instant::now().as_secs() } + +#[cfg(feature = "defmt-timestamp-uptime-tms")] +defmt::timestamp! {"{=u64:tms}", Instant::now().as_millis() } + +#[cfg(feature = "defmt-timestamp-uptime-tus")] +defmt::timestamp! {"{=u64:tus}", Instant::now().as_micros() } diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs new file mode 100644 index 0000000..34e5762 --- /dev/null +++ b/embassy-time/src/timer.rs @@ -0,0 +1,276 @@ +use core::future::{poll_fn, Future}; +use core::pin::{pin, Pin}; +use core::task::{Context, Poll}; + +use futures_util::future::{select, Either}; +use futures_util::stream::FusedStream; +use futures_util::Stream; + +use crate::{Duration, Instant}; + +/// Error returned by [`with_timeout`] and [`with_deadline`] on timeout. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct TimeoutError; + +/// Runs a given future with a timeout. +/// +/// If the future completes before the timeout, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. +pub async fn with_timeout(timeout: Duration, fut: F) -> Result { + let timeout_fut = Timer::after(timeout); + match select(pin!(fut), timeout_fut).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } +} + +/// Runs a given future with a deadline time. +/// +/// If the future completes before the deadline, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. +pub async fn with_deadline(at: Instant, fut: F) -> Result { + let timeout_fut = Timer::at(at); + match select(pin!(fut), timeout_fut).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } +} + +/// Provides functions to run a given future with a timeout or a deadline. +pub trait WithTimeout { + /// Output type of the future. + type Output; + + /// Runs a given future with a timeout. + /// + /// If the future completes before the timeout, its output is returned. Otherwise, on timeout, + /// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. + async fn with_timeout(self, timeout: Duration) -> Result; + + /// Runs a given future with a deadline time. + /// + /// If the future completes before the deadline, its output is returned. Otherwise, on timeout, + /// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. + async fn with_deadline(self, at: Instant) -> Result; +} + +impl WithTimeout for F { + type Output = F::Output; + + async fn with_timeout(self, timeout: Duration) -> Result { + with_timeout(timeout, self).await + } + + async fn with_deadline(self, at: Instant) -> Result { + with_deadline(at, self).await + } +} + +/// A future that completes at a specified [Instant](struct.Instant.html). +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Timer { + expires_at: Instant, + yielded_once: bool, +} + +impl Timer { + /// Expire at specified [Instant](struct.Instant.html) + pub fn at(expires_at: Instant) -> Self { + Self { + expires_at, + yielded_once: false, + } + } + + /// Expire after specified [Duration](struct.Duration.html). + /// This can be used as a `sleep` abstraction. + /// + /// Example: + /// ``` no_run + /// use embassy_time::{Duration, Timer}; + /// + /// #[embassy_executor::task] + /// async fn demo_sleep_seconds() { + /// // suspend this task for one second. + /// Timer::after(Duration::from_secs(1)).await; + /// } + /// ``` + pub fn after(duration: Duration) -> Self { + Self { + expires_at: Instant::now() + duration, + yielded_once: false, + } + } + + /// Expire after the specified number of ticks. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_ticks())`. + /// For more details, refer to [`Timer::after()`] and [`Duration::from_ticks()`]. + #[inline] + pub fn after_ticks(ticks: u64) -> Self { + Self::after(Duration::from_ticks(ticks)) + } + + /// Expire after the specified number of nanoseconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_nanos())`. + /// For more details, refer to [`Timer::after()`] and [`Duration::from_nanos()`]. + #[inline] + pub fn after_nanos(nanos: u64) -> Self { + Self::after(Duration::from_nanos(nanos)) + } + + /// Expire after the specified number of microseconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_micros())`. + /// For more details, refer to [`Timer::after()`] and [`Duration::from_micros()`]. + #[inline] + pub fn after_micros(micros: u64) -> Self { + Self::after(Duration::from_micros(micros)) + } + + /// Expire after the specified number of milliseconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_millis())`. + /// For more details, refer to [`Timer::after`] and [`Duration::from_millis()`]. + #[inline] + pub fn after_millis(millis: u64) -> Self { + Self::after(Duration::from_millis(millis)) + } + + /// Expire after the specified number of seconds. + /// + /// This method is a convenience wrapper for calling `Timer::after(Duration::from_secs())`. + /// For more details, refer to [`Timer::after`] and [`Duration::from_secs()`]. + #[inline] + pub fn after_secs(secs: u64) -> Self { + Self::after(Duration::from_secs(secs)) + } +} + +impl Unpin for Timer {} + +impl Future for Timer { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if self.yielded_once && self.expires_at <= Instant::now() { + Poll::Ready(()) + } else { + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + self.yielded_once = true; + Poll::Pending + } + } +} + +/// Asynchronous stream that yields every Duration, indefinitely. +/// +/// This stream will tick at uniform intervals, even if blocking work is performed between ticks. +/// +/// For instance, consider the following code fragment. +/// ``` no_run +/// use embassy_time::{Duration, Timer}; +/// # fn foo() {} +/// +/// #[embassy_executor::task] +/// async fn ticker_example_0() { +/// loop { +/// foo(); +/// Timer::after(Duration::from_secs(1)).await; +/// } +/// } +/// ``` +/// +/// This fragment will not call `foo` every second. +/// Instead, it will call it every second + the time it took to previously call `foo`. +/// +/// Example using ticker, which will consistently call `foo` once a second. +/// +/// ``` no_run +/// use embassy_time::{Duration, Ticker}; +/// # fn foo(){} +/// +/// #[embassy_executor::task] +/// async fn ticker_example_1() { +/// let mut ticker = Ticker::every(Duration::from_secs(1)); +/// loop { +/// foo(); +/// ticker.next().await; +/// } +/// } +/// ``` +/// +/// ## Cancel safety +/// It is safe to cancel waiting for the next tick, +/// meaning no tick is lost if the Future is dropped. +pub struct Ticker { + expires_at: Instant, + duration: Duration, +} + +impl Ticker { + /// Creates a new ticker that ticks at the specified duration interval. + pub fn every(duration: Duration) -> Self { + let expires_at = Instant::now() + duration; + Self { expires_at, duration } + } + + /// Resets the ticker back to its original state. + /// This causes the ticker to go back to zero, even if the current tick isn't over yet. + pub fn reset(&mut self) { + self.expires_at = Instant::now() + self.duration; + } + + /// Reset the ticker at the deadline. + /// If the deadline is in the past, the ticker will fire instantly. + pub fn reset_at(&mut self, deadline: Instant) { + self.expires_at = deadline + self.duration; + } + + /// Resets the ticker, after the specified duration has passed. + /// If the specified duration is zero, the next tick will be after the duration of the ticker. + pub fn reset_after(&mut self, after: Duration) { + self.expires_at = Instant::now() + after + self.duration; + } + + /// Waits for the next tick. + /// + /// ## Cancel safety + /// The produced Future is cancel safe, meaning no tick is lost if the Future is dropped. + pub fn next(&mut self) -> impl Future + Send + Sync + '_ { + poll_fn(|cx| { + if self.expires_at <= Instant::now() { + let dur = self.duration; + self.expires_at += dur; + Poll::Ready(()) + } else { + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + Poll::Pending + } + }) + } +} + +impl Unpin for Ticker {} + +impl Stream for Ticker { + type Item = (); + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.expires_at <= Instant::now() { + let dur = self.duration; + self.expires_at += dur; + Poll::Ready(Some(())) + } else { + embassy_time_driver::schedule_wake(self.expires_at.as_ticks(), cx.waker()); + Poll::Pending + } + } +} + +impl FusedStream for Ticker { + fn is_terminated(&self) -> bool { + // `Ticker` keeps yielding values until dropped, it never terminates. + false + } +} diff --git a/embassy-usb-driver/Cargo.toml b/embassy-usb-driver/Cargo.toml new file mode 100644 index 0000000..41493f0 --- /dev/null +++ b/embassy-usb-driver/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "embassy-usb-driver" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Driver trait for `embassy-usb`, an async USB device stack for embedded devices." +keywords = ["embedded", "async", "usb", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb-driver" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-driver-v$VERSION/embassy-usb-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-driver/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } diff --git a/embassy-usb-driver/README.md b/embassy-usb-driver/README.md new file mode 100644 index 0000000..7628a69 --- /dev/null +++ b/embassy-usb-driver/README.md @@ -0,0 +1,17 @@ +# embassy-usb-driver + +This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these +traits to add support for using `embassy-usb` for a given chip/platform. + +The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`] +APIs don't cause a semver-major bump of this crate. This allows existing HALs/BSPs to be used +with the newer `embassy-usb` without needing updates. + +If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate +instead of this one. + +[`embassy-usb`]: https://crates.io/crates/embassy-usb + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs new file mode 100644 index 0000000..3b705c8 --- /dev/null +++ b/embassy-usb-driver/src/lib.rs @@ -0,0 +1,397 @@ +#![no_std] +#![allow(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from +/// the perspective of the host, which is backward for devices, but the standard directions are used +/// for consistency. +/// +/// The values of the enum also match the direction bit used in endpoint addresses and control +/// request types. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Direction { + /// Host to device (OUT) + Out, + /// Device to host (IN) + In, +} + +/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the +/// transfer bmAttributes transfer type bits. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointType { + /// Control endpoint. Used for device management. Only the host can initiate requests. Usually + /// used only endpoint 0. + Control = 0b00, + /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet. + Isochronous = 0b01, + /// Bulk endpoint. Used for large amounts of best-effort reliable data. + Bulk = 0b10, + /// Interrupt endpoint. Used for small amounts of time-critical reliable data. + Interrupt = 0b11, +} + +/// Type-safe endpoint address. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAddress(u8); + +impl From for EndpointAddress { + #[inline] + fn from(addr: u8) -> EndpointAddress { + EndpointAddress(addr) + } +} + +impl From for u8 { + #[inline] + fn from(addr: EndpointAddress) -> u8 { + addr.0 + } +} + +impl EndpointAddress { + const INBITS: u8 = 0x80; + + /// Constructs a new EndpointAddress with the given index and direction. + #[inline] + pub fn from_parts(index: usize, dir: Direction) -> Self { + let dir_u8 = match dir { + Direction::Out => 0x00, + Direction::In => Self::INBITS, + }; + EndpointAddress(index as u8 | dir_u8) + } + + /// Gets the direction part of the address. + #[inline] + pub fn direction(&self) -> Direction { + if (self.0 & Self::INBITS) != 0 { + Direction::In + } else { + Direction::Out + } + } + + /// Returns true if the direction is IN, otherwise false. + #[inline] + pub fn is_in(&self) -> bool { + (self.0 & Self::INBITS) != 0 + } + + /// Returns true if the direction is OUT, otherwise false. + #[inline] + pub fn is_out(&self) -> bool { + (self.0 & Self::INBITS) == 0 + } + + /// Gets the index part of the endpoint address. + #[inline] + pub fn index(&self) -> usize { + (self.0 & !Self::INBITS) as usize + } +} + +/// Information for an endpoint. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointInfo { + /// Endpoint's address. + pub addr: EndpointAddress, + /// Endpoint's type. + pub ep_type: EndpointType, + /// Max packet size, in bytes. + pub max_packet_size: u16, + /// Polling interval, in milliseconds. + pub interval_ms: u8, +} + +/// Main USB driver trait. +/// +/// Implement this to add support for a new hardware platform. +pub trait Driver<'a> { + /// Type of the OUT endpoints for this driver. + type EndpointOut: EndpointOut + 'a; + /// Type of the IN endpoints for this driver. + type EndpointIn: EndpointIn + 'a; + /// Type of the control pipe for this driver. + type ControlPipe: ControlPipe + 'a; + /// Type for bus control for this driver. + type Bus: Bus + 'a; + + /// Allocates an OUT endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result; + + /// Allocates an IN endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result; + + /// Start operation of the USB device. + /// + /// This returns the `Bus` and `ControlPipe` instances that are used to operate + /// the USB device. Additionally, this makes all the previously allocated endpoints + /// start operating. + /// + /// This consumes the `Driver` instance, so it's no longer possible to allocate more + /// endpoints. + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); +} + +/// USB bus trait. +/// +/// This trait provides methods that act on the whole bus. It is kept owned by +/// the main USB task, and used to manage the bus. +pub trait Bus { + /// Enable the USB peripheral. + async fn enable(&mut self); + + /// Disable and powers down the USB peripheral. + async fn disable(&mut self); + + /// Wait for a bus-related event. + /// + /// This method should asynchronously wait for an event to happen, then + /// return it. See [`Event`] for the list of events this method should return. + async fn poll(&mut self) -> Event; + + /// Enable or disable an endpoint. + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); + + /// Set or clear the STALL condition for an endpoint. + /// + /// If the endpoint is an OUT endpoint, it should be prepared to receive data again. + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); + + /// Get whether the STALL condition is set for an endpoint. + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; + + /// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// device. + /// + /// The default implementation just returns `Unsupported`. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support + /// simulating a disconnect or it has not been enabled at creation time. + fn force_reset(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } + + /// Initiate a remote wakeup of the host by the device. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support + /// remote wakeup or it has not been enabled at creation time. + async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; +} + +/// Endpoint trait, common for OUT and IN. +pub trait Endpoint { + /// Get the endpoint address + fn info(&self) -> &EndpointInfo; + + /// Wait for the endpoint to be enabled. + async fn wait_enabled(&mut self); +} + +/// OUT Endpoint trait. +pub trait EndpointOut: Endpoint { + /// Read a single packet of data from the endpoint, and return the actual length of + /// the packet. + /// + /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. + async fn read(&mut self, buf: &mut [u8]) -> Result; +} + +/// USB control pipe trait. +/// +/// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single +/// unit, and manages them together to implement the control pipe state machine. +/// +/// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that +/// many USB peripherals treat the control pipe endpoints differently (different registers, +/// different procedures), usually to accelerate processing in hardware somehow. A separate +/// trait allows the driver to handle it specially. +/// +/// The call sequences made by the USB stack to the ControlPipe are the following: +/// +/// - control in/out with len=0: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept() or reject() +/// ``` +/// +/// - control out for setting the device address: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept_set_address(addr) or reject() +/// ``` +/// +/// - control out with len != 0: +/// +/// ```not_rust +/// setup() +/// data_out(first=true, last=false) +/// data_out(first=false, last=false) +/// ... +/// data_out(first=false, last=false) +/// data_out(first=false, last=true) +/// (...processing...) +/// accept() or reject() +/// ``` +/// +/// - control in with len != 0, accepted: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// data_in(first=true, last=false) +/// data_in(first=false, last=false) +/// ... +/// data_in(first=false, last=false) +/// data_in(first=false, last=true) +/// (NO `accept()`!!! This is because calling `data_in` already implies acceptance.) +/// ``` +/// +/// - control in with len != 0, rejected: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// reject() +/// ``` +/// +/// The driver is responsible for handling the status stage. The stack DOES NOT do zero-length +/// calls to `data_in` or `data_out` for the status zero-length packet. The status stage should +/// be triggered by either `accept()`, or `data_in` with `last = true`. +/// +/// Note that the host can abandon a control request and send a new SETUP packet any time. If +/// a SETUP packet arrives at any time during `data_out`, `data_in`, `accept` or `reject`, +/// the driver must immediately return (with `EndpointError::Disabled` from `data_in`, `data_out`) +/// to let the stack call `setup()` again to start handling the new control request. Not doing +/// so will cause things to get stuck, because the host will never read/send the packet we're +/// waiting for. +pub trait ControlPipe { + /// Maximum packet size for the control pipe + fn max_packet_size(&self) -> usize; + + /// Read a single setup packet from the endpoint. + async fn setup(&mut self) -> [u8; 8]; + + /// Read a DATA OUT packet into `buf` in response to a control write request. + /// + /// Must be called after `setup()` for requests with `direction` of `Out` + /// and `length` greater than zero. + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result; + + /// Send a DATA IN packet with `data` in response to a control read request. + /// + /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; + + /// Accept a control request. + /// + /// Causes the STATUS packet for the current request to be ACKed. + async fn accept(&mut self); + + /// Reject a control request. + /// + /// Sets a STALL condition on the pipe to indicate an error. + async fn reject(&mut self); + + /// Accept SET_ADDRESS control and change bus address. + /// + /// For most drivers this function should firstly call `accept()` and then change the bus address. + /// However, there are peripherals (Synopsys USB OTG) that have reverse order. + async fn accept_set_address(&mut self, addr: u8); +} + +/// IN Endpoint trait. +pub trait EndpointIn: Endpoint { + /// Write a single packet of data to the endpoint. + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Event returned by [`Bus::poll`]. +pub enum Event { + /// The USB reset condition has been detected. + Reset, + + /// A USB suspend request has been detected or, in the case of self-powered devices, the device + /// has been disconnected from the USB bus. + Suspend, + + /// A USB resume request has been detected after being suspended or, in the case of self-powered + /// devices, the device has been connected to the USB bus. + Resume, + + /// The USB power has been detected. + PowerDetected, + + /// The USB power has been removed. Not supported by all devices. + PowerRemoved, +} + +/// Allocating an endpoint failed. +/// +/// This can be due to running out of endpoints, or out of endpoint memory, +/// or because the hardware doesn't support the requested combination of features. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAllocError; + +/// Operation is unsupported by the driver. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Unsupported; + +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointError { + /// Either the packet to be written is too long to fit in the transmission + /// buffer or the received packet is too long to fit in `buf`. + BufferOverflow, + + /// The endpoint is disabled. + Disabled, +} diff --git a/embassy-usb-logger/CHANGELOG.md b/embassy-usb-logger/CHANGELOG.md new file mode 100644 index 0000000..86a9fb0 --- /dev/null +++ b/embassy-usb-logger/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +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 + +## 0.4.0 - 2025-01-15 + +- Update `embassy-usb` to 0.4.0 + +(skipped v0.3.0 to align version numbers with `embassy-usb`) + +## 0.2.0 - 2024-05-20 + +- Update `embassy-usb` to 0.2.0 +- Add support for using an existing USB device ([#2414](https://github.com/embassy-rs/embassy/pull/2414), @JomerDev) +- Fix data loss at `Pipe` wraparound +- Messages that are exactly `MAX_PACKET_SIZE` long are no longer delayed ([#2414](https://github.com/embassy-rs/embassy/pull/2414), @JomerDev) + +## 0.1.0 - 2024-01-14 + +- Initial Release diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml new file mode 100644 index 0000000..c670b41 --- /dev/null +++ b/embassy-usb-logger/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "embassy-usb-logger" +version = "0.4.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "`log` implementation for USB serial using `embassy-usb`." +keywords = ["embedded", "log", "usb", "hal", "serial"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb-logger" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-logger-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-logger/src/" +target = "thumbv7em-none-eabi" + +[dependencies] +embassy-usb = { version = "0.4.0", path = "../embassy-usb" } +embassy-sync = { version = "0.6.2", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +log = "0.4" diff --git a/embassy-usb-logger/README.md b/embassy-usb-logger/README.md new file mode 100644 index 0000000..81b0dcd --- /dev/null +++ b/embassy-usb-logger/README.md @@ -0,0 +1,15 @@ +# embassy-usb-logger + +USB implementation of the `log` crate. This logger can be used by any device that implements `embassy-usb`. When running, +it will output all logging done through the `log` facade to the USB serial peripheral. + +## Usage + +Add the following embassy task to your application. The `Driver` type is different depending on which HAL you use. + + ```rust +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} +``` diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs new file mode 100644 index 0000000..de25abc --- /dev/null +++ b/embassy-usb-logger/src/lib.rs @@ -0,0 +1,318 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use core::fmt::Write as _; +use core::future::Future; + +use embassy_futures::join::join; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; +use embassy_usb::driver::Driver; +use embassy_usb::{Builder, Config}; +use log::{Metadata, Record}; + +type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; + +/// A trait that can be implemented and then passed to the +pub trait ReceiverHandler { + /// Data comes in from the serial port with each command and runs this function + fn handle_data(&self, data: &[u8]) -> impl Future + Send; + + /// Create a new instance of the Handler + fn new() -> Self; +} + +/// Use this Handler if you don't wish to use any handler +pub struct DummyHandler; + +impl ReceiverHandler for DummyHandler { + async fn handle_data(&self, _data: &[u8]) {} + fn new() -> Self { + Self {} + } +} + +/// The logger state containing buffers that must live as long as the USB peripheral. +pub struct LoggerState<'d> { + state: State<'d>, + config_descriptor: [u8; 128], + bos_descriptor: [u8; 16], + msos_descriptor: [u8; 256], + control_buf: [u8; 64], +} + +impl<'d> LoggerState<'d> { + /// Create a new instance of the logger state. + pub fn new() -> Self { + Self { + state: State::new(), + config_descriptor: [0; 128], + bos_descriptor: [0; 16], + msos_descriptor: [0; 256], + control_buf: [0; 64], + } + } +} + +/// The packet size used in the usb logger, to be used with `create_future_from_class` +pub const MAX_PACKET_SIZE: u8 = 64; + +/// The logger handle, which contains a pipe with configurable size for buffering log messages. +pub struct UsbLogger { + buffer: Pipe, + custom_style: Option) -> ()>, + recieve_handler: Option, +} + +impl UsbLogger { + /// Create a new logger instance. + pub const fn new() -> Self { + Self { + buffer: Pipe::new(), + custom_style: None, + recieve_handler: None, + } + } + + /// Create a new logger instance with a custom formatter. + pub const fn with_custom_style(custom_style: fn(&Record, &mut Writer<'_, N>) -> ()) -> Self { + Self { + buffer: Pipe::new(), + custom_style: Some(custom_style), + recieve_handler: None, + } + } + + /// Add a command handler to the logger + pub fn with_handler(&mut self, handler: T) { + self.recieve_handler = Some(handler); + } + + /// Run the USB logger using the state and USB driver. Never returns. + pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> ! + where + D: Driver<'d>, + Self: 'd, + { + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial logger"); + config.serial_number = None; + config.max_power = 100; + config.max_packet_size_0 = MAX_PACKET_SIZE; + + let mut builder = Builder::new( + driver, + config, + &mut state.config_descriptor, + &mut state.bos_descriptor, + &mut state.msos_descriptor, + &mut state.control_buf, + ); + + // Create classes on the builder. + let class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16); + let (mut sender, mut receiver) = class.split(); + + // Build the builder. + let mut device = builder.build(); + loop { + let run_fut = device.run(); + let class_fut = self.run_logger_class(&mut sender, &mut receiver); + join(run_fut, class_fut).await; + } + } + + async fn run_logger_class<'d, D>(&self, sender: &mut Sender<'d, D>, receiver: &mut Receiver<'d, D>) + where + D: Driver<'d>, + { + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + sender.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = sender.write_packet(&rx[..len]).await; + if len as u8 == MAX_PACKET_SIZE { + let _ = sender.write_packet(&[]).await; + } + } + }; + let reciever_fut = async { + let mut reciever_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + receiver.wait_connection().await; + loop { + let n = receiver.read_packet(&mut reciever_buf).await.unwrap(); + match &self.recieve_handler { + Some(handler) => { + let data = &reciever_buf[..n]; + handler.handle_data(data).await; + } + None => (), + } + } + }; + + join(log_fut, reciever_fut).await; + } + + /// Creates the futures needed for the logger from a given class + /// This can be used in cases where the usb device is already in use for another connection + pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>) + where + D: Driver<'d>, + { + let (mut sender, mut receiver) = class.split(); + loop { + self.run_logger_class(&mut sender, &mut receiver).await; + } + } +} + +impl log::Log for UsbLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + if let Some(custom_style) = self.custom_style { + custom_style(record, &mut Writer(&self.buffer)); + } else { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } + } + } + + fn flush(&self) {} +} + +/// A writer that writes to the USB logger buffer. +pub struct Writer<'d, const N: usize>(&'d Pipe); + +impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + // The Pipe is implemented in such way that we cannot + // write across the wraparound discontinuity. + let b = s.as_bytes(); + if let Ok(n) = self.0.try_write(b) { + if n < b.len() { + // We wrote some data but not all, attempt again + // as the reason might be a wraparound in the + // ring buffer, which resolves on second attempt. + let _ = self.0.try_write(&b[n..]); + } + } + Ok(()) + } +} + +/// Initialize and run the USB serial logger, never returns. +/// +/// Arguments specify the buffer size, log level and the USB driver, respectively. You can optionally add a RecieverHandler. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! run { + ( $x:expr, $l:expr, $p:ident ) => { + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; + }; + + ( $x:expr, $l:expr, $p:ident, $h:ty ) => { + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new(); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; + } + }; +} + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// +/// Arguments specify the buffer size, log level and the serial class, respectively. You can optionally add a RecieverHandler. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, class); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_class { + ( $x:expr, $l:expr, $p:ident ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; + + ( $x:expr, $l:expr, $p:ident, $h:ty ) => {{ + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = ::embassy_usb_logger::UsbLogger::new(); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + LOGGER.create_future_from_class($p) + } + }}; +} + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// This version of the macro allows for a custom style function to be passed in. +/// The custom style function will be called for each log record and is responsible for writing the log message to the buffer. +/// +/// Arguments specify the buffer size, log level, the serial class and the custom style function, respectively. You can optionally add a RecieverHandler. +/// +/// # Usage +/// +/// ``` +/// let log_fut = embassy_usb_logger::with_custom_style!(1024, log::LevelFilter::Info, logger_class, |record, writer| { +/// use core::fmt::Write; +/// let level = record.level().as_str(); +/// write!(writer, "[{level}] {}\r\n", record.args()).unwrap(); +/// }); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_custom_style { + ( $x:expr, $l:expr, $p:ident, $s:expr ) => {{ + static LOGGER: ::embassy_usb_logger::UsbLogger<$x, ::embassy_usb_logger::DummyHandler> = + ::embassy_usb_logger::UsbLogger::with_custom_style($s); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + LOGGER.create_future_from_class($p) + }}; + + ( $x:expr, $l:expr, $p:ident, $s:expr, $h:ty ) => {{ + unsafe { + static mut LOGGER: ::embassy_usb_logger::UsbLogger<$x, $h> = + ::embassy_usb_logger::UsbLogger::with_custom_style($s); + LOGGER.with_handler(<$h>::new()); + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + LOGGER.create_future_from_class($p) + } + }}; +} diff --git a/embassy-usb/CHANGELOG.md b/embassy-usb/CHANGELOG.md new file mode 100644 index 0000000..76fafed --- /dev/null +++ b/embassy-usb/CHANGELOG.md @@ -0,0 +1,36 @@ +# Changelog + +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 + +## 0.4.0 - 2025-01-15 + +- Change config defaults to to composite with IADs. This ensures embassy-usb Just Works in more cases when using classes with multiple interfaces, or multiple classes. (breaking change) + - `composite_with_iads` = `true` + - `device_class` = `0xEF` + - `device_sub_class` = `0x02` + - `device_protocol` = `0x01` +- Add support for USB Audio Class 1. +- Add support for isochronous endpoints. +- Add support for setting the USB version number. +- Add support for device qualifier descriptors. +- Allow `bos_descriptor_buf` to be a zero length if BOS descriptors aren't used. + +## 0.3.0 - 2024-08-05 + +- bump usbd-hid from 0.7.0 to 0.8.1 +- Add collapse_debuginfo to fmt.rs macros. +- update embassy-sync dependency + +## 0.2.0 - 2024-05-20 + +- [#2862](https://github.com/embassy-rs/embassy/pull/2862) WebUSB implementation by @chmanie +- Removed dynamically sized `device_descriptor` fields + +## 0.1.0 - 2024-01-11 + +- Initial Release diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml new file mode 100644 index 0000000..771190c --- /dev/null +++ b/embassy-usb/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "embassy-usb" +version = "0.4.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Async USB device stack for embedded devices in Rust." +keywords = ["embedded", "async", "usb", "hal", "embedded-hal"] +categories = ["embedded", "hardware-support", "no-std", "asynchronous"] +repository = "https://github.com/embassy-rs/embassy" +documentation = "https://docs.embassy.dev/embassy-usb" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb/src/" +features = ["defmt", "usbd-hid"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt", "usbd-hid"] + +[features] +defmt = ["dep:defmt", "embassy-usb-driver/defmt"] +usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] +default = ["usbd-hid"] + +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +max-interface-count-1 = [] +max-interface-count-2 = [] +max-interface-count-3 = [] +max-interface-count-4 = [] # Default +max-interface-count-5 = [] +max-interface-count-6 = [] +max-interface-count-7 = [] +max-interface-count-8 = [] + +max-handler-count-1 = [] +max-handler-count-2 = [] +max-handler-count-3 = [] +max-handler-count-4 = [] # Default +max-handler-count-5 = [] +max-handler-count-6 = [] +max-handler-count-7 = [] +max-handler-count-8 = [] + +# END AUTOGENERATED CONFIG FEATURES + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } +embassy-sync = { version = "0.6.2", path = "../embassy-sync" } +embassy-net-driver-channel = { version = "0.3.0", path = "../embassy-net-driver-channel" } + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } +heapless = "0.8" + +# for HID +usbd-hid = { version = "0.8.1", optional = true } +ssmarshal = { version = "1.0", default-features = false, optional = true } diff --git a/embassy-usb/README.md b/embassy-usb/README.md new file mode 100644 index 0000000..400fc66 --- /dev/null +++ b/embassy-usb/README.md @@ -0,0 +1,50 @@ +# embassy-usb + +Async USB device stack for embedded devices in Rust. + +## Features + +- Native async. +- Fully lock-free: endpoints are separate objects that can be used independently without needing a central mutex. If the driver supports it, they can even be used from different priority levels. +- Suspend/resume, remote wakeup. +- USB composite devices. +- Ergonomic descriptor builder. +- Ready-to-use implementations for a few USB classes (note you can still implement any class yourself outside the crate). + - Serial ports (CDC ACM) + - Ethernet (CDC NCM) + - Human Interface Devices (HID) + - MIDI + +## Adding support for new hardware + +To add `embassy-usb` support for new hardware (i.e. a new MCU chip), you have to write a driver that implements +the [`embassy-usb-driver`](https://crates.io/crates/embassy-usb-driver) traits. + +Driver crates should depend only on `embassy-usb-driver`, not on the main `embassy-usb` crate. +This allows existing drivers to continue working for newer `embassy-usb` major versions, without needing an update, if the driver +trait has not had breaking changes. + +## Configuration + +`embassy-usb` has some configuration settings that are set at compile time, affecting sizes +and counts of buffers. + +They can be set in two ways: + +- Via Cargo features: enable a feature like `-`. `name` must be in lowercase and +use dashes instead of underscores. For example. `max-interface-count-3`. Only a selection of values +is available, check `Cargo.toml` for the list. +- Via environment variables at build time: set the variable named `EMBASSY_USB_`. For example +`EMBASSY_USB_MAX_INTERFACE_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. +Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +### `MAX_INTERFACE_COUNT` + +Max amount of interfaces that can be created in one device. Default: 4. + +## Interoperability + +This crate can run on any executor. diff --git a/embassy-usb/build.rs b/embassy-usb/build.rs new file mode 100644 index 0000000..5e3bec4 --- /dev/null +++ b/embassy-usb/build.rs @@ -0,0 +1,96 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("MAX_INTERFACE_COUNT", 4), + ("MAX_HANDLER_COUNT", 4), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + assert!( + !cfg.seen_feature, + "multiple values set for feature {}: {} and {}", + name, cfg.value, value + ); + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); +} diff --git a/embassy-usb/gen_config.py b/embassy-usb/gen_config.py new file mode 100644 index 0000000..67ce359 --- /dev/null +++ b/embassy-usb/gen_config.py @@ -0,0 +1,74 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min, max, pow2=None): + vals = set() + val = min + while val <= max: + vals.add(val) + if pow2 == True or (isinstance(pow2, int) and val >= pow2): + val *= 2 + else: + val += 1 + vals.add(default) + + features.append( + { + "name": name, + "default": default, + "vals": sorted(list(vals)), + } + ) + + +feature("max_interface_count", default=4, min=1, max=8) +feature("max_handler_count", default=4, min=1, max=8) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"{name}-{val} = []" + if val == f["default"]: + things += " # Default" + things += "\n" + things += "\n" + +SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" +HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" +with open("Cargo.toml", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after +with open("Cargo.toml", "w") as f: + f.write(data) + + +# ========= Update build.rs + +things = "" +for f in features: + name = f["name"].upper() + things += f' ("{name}", {f["default"]}),\n' + +SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" +HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" +with open("build.rs", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + things + " " + SEPARATOR_END + after +with open("build.rs", "w") as f: + f.write(data) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs new file mode 100644 index 0000000..9a21b9a --- /dev/null +++ b/embassy-usb/src/builder.rs @@ -0,0 +1,623 @@ +use heapless::Vec; + +use crate::config::MAX_HANDLER_COUNT; +use crate::descriptor::{BosWriter, DescriptorWriter, SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointInfo, EndpointType}; +use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; +use crate::types::{InterfaceNumber, StringIndex}; +use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Allows Configuring the Bcd USB version below 2.1 +pub enum UsbVersion { + /// Usb version 2.0 + Two = 0x0200, + /// Usb version 2.1 + TwoOne = 0x0210, +} + +#[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +/// Configuration used when creating [`UsbDevice`]. +pub struct Config<'a> { + pub(crate) vendor_id: u16, + pub(crate) product_id: u16, + + /// Device BCD USB version. + /// + /// Default: `0x0210` ("2.1") + pub bcd_usb: UsbVersion, + + /// Device class code assigned by USB.org. Set to `0xff` for vendor-specific + /// devices that do not conform to any class. + /// + /// Default: `0xEF` + /// See also: `composite_with_iads` + pub device_class: u8, + + /// Device sub-class code. Depends on class. + /// + /// Default: `0x02` + /// See also: `composite_with_iads` + pub device_sub_class: u8, + + /// Device protocol code. Depends on class and sub-class. + /// + /// Default: `0x01` + /// See also: `composite_with_iads` + pub device_protocol: u8, + + /// Device release version in BCD. + /// + /// Default: `0x0010` ("0.1") + pub device_release: u16, + + /// Maximum packet size in bytes for the control endpoint 0. + /// + /// Valid values depend on the speed at which the bus is enumerated. + /// - low speed: 8 + /// - full speed: 8, 16, 32, or 64 + /// - high speed: 64 + /// + /// Default: 64 bytes + pub max_packet_size_0: u8, + + /// Manufacturer name string descriptor. + /// + /// Default: (none) + pub manufacturer: Option<&'a str>, + + /// Product name string descriptor. + /// + /// Default: (none) + pub product: Option<&'a str>, + + /// Serial number string descriptor. + /// + /// Default: (none) + pub serial_number: Option<&'a str>, + + /// Whether the device supports remotely waking up the host is requested. + /// + /// Default: `false` + pub supports_remote_wakeup: bool, + + /// Configures the device as a composite device with interface association descriptors. + /// + /// If set to `true` (default), the following fields should have the given values: + /// + /// - `device_class` = `0xEF` + /// - `device_sub_class` = `0x02` + /// - `device_protocol` = `0x01` + /// + /// If set to `false`, those fields must be set correctly for the classes that will be + /// installed on the USB device. + pub composite_with_iads: bool, + + /// Whether the device has its own power source. + /// + /// This should be set to `true` even if the device is sometimes self-powered and may not + /// always draw power from the USB bus. + /// + /// Default: `false` + /// + /// See also: `max_power` + pub self_powered: bool, + + /// Maximum current drawn from the USB bus by the device, in milliamps. + /// + /// The default is 100 mA. If your device always uses an external power source and never draws + /// power from the USB bus, this can be set to 0. + /// + /// See also: `self_powered` + /// + /// Default: 100mA + /// Max: 500mA + pub max_power: u16, +} + +impl<'a> Config<'a> { + /// Create default configuration with the provided vid and pid values. + pub const fn new(vid: u16, pid: u16) -> Self { + Self { + device_class: 0xEF, + device_sub_class: 0x02, + device_protocol: 0x01, + max_packet_size_0: 64, + vendor_id: vid, + product_id: pid, + device_release: 0x0010, + bcd_usb: UsbVersion::TwoOne, + manufacturer: None, + product: None, + serial_number: None, + self_powered: false, + supports_remote_wakeup: false, + composite_with_iads: true, + max_power: 100, + } + } +} + +/// [`UsbDevice`] builder. +pub struct Builder<'d, D: Driver<'d>> { + config: Config<'d>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + interfaces: Vec, + control_buf: &'d mut [u8], + + driver: D, + next_string_index: u8, + + config_descriptor: DescriptorWriter<'d>, + bos_descriptor: BosWriter<'d>, + + msos_descriptor: MsOsDescriptorWriter<'d>, +} + +impl<'d, D: Driver<'d>> Builder<'d, D> { + /// Creates a builder for constructing a new [`UsbDevice`]. + /// + /// `control_buf` is a buffer used for USB control request data. It should be sized + /// large enough for the length of the largest control request (in or out) + /// anticipated by any class added to the device. + pub fn new( + driver: D, + config: Config<'d>, + config_descriptor_buf: &'d mut [u8], + bos_descriptor_buf: &'d mut [u8], + msos_descriptor_buf: &'d mut [u8], + control_buf: &'d mut [u8], + ) -> Self { + // Magic values specified in USB-IF ECN on IADs. + if config.composite_with_iads + && (config.device_class != 0xEF || config.device_sub_class != 0x02 || config.device_protocol != 0x01) + { + panic!("if composite_with_iads is set, you must set device_class = 0xEF, device_sub_class = 0x02, device_protocol = 0x01"); + } + + assert!( + config.max_power <= 500, + "The maximum allowed value for `max_power` is 500mA" + ); + + match config.max_packet_size_0 { + 8 | 16 | 32 | 64 => {} + _ => panic!("invalid max_packet_size_0, the allowed values are 8, 16, 32 or 64"), + } + + let mut config_descriptor = DescriptorWriter::new(config_descriptor_buf); + let mut bos_descriptor = BosWriter::new(DescriptorWriter::new(bos_descriptor_buf)); + + config_descriptor.configuration(&config); + bos_descriptor.bos(); + + Builder { + driver, + config, + interfaces: Vec::new(), + handlers: Vec::new(), + control_buf, + next_string_index: STRING_INDEX_CUSTOM_START, + + config_descriptor, + bos_descriptor, + + msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf), + } + } + + /// Creates the [`UsbDevice`] instance with the configuration in this builder. + pub fn build(mut self) -> UsbDevice<'d, D> { + let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); + + self.config_descriptor.end_configuration(); + self.bos_descriptor.end_bos(); + + // Log the number of allocator bytes actually used in descriptor buffers + info!("USB: config_descriptor used: {}", self.config_descriptor.position()); + info!("USB: bos_descriptor used: {}", self.bos_descriptor.writer.position()); + info!("USB: msos_descriptor used: {}", msos_descriptor.len()); + info!("USB: control_buf size: {}", self.control_buf.len()); + + UsbDevice::build( + self.driver, + self.config, + self.handlers, + self.config_descriptor.into_buf(), + self.bos_descriptor.writer.into_buf(), + msos_descriptor, + self.interfaces, + self.control_buf, + ) + } + + /// Returns the size of the control request data buffer. Can be used by + /// classes to validate the buffer is large enough for their needs. + pub fn control_buf_len(&self) -> usize { + self.control_buf.len() + } + + /// Add an USB function. + /// + /// If [`Config::composite_with_iads`] is set, this will add an IAD descriptor + /// with the given class/subclass/protocol, associating all the child interfaces. + /// + /// If it's not set, no IAD descriptor is added. + pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { + let first_interface = InterfaceNumber::new(self.interfaces.len() as u8); + let iface_count_index = if self.config.composite_with_iads { + self.config_descriptor + .iad(first_interface, 0, class, subclass, protocol); + + Some(self.config_descriptor.position() - 5) + } else { + None + }; + + FunctionBuilder { + builder: self, + iface_count_index, + + first_interface, + } + } + + /// Add a Handler. + /// + /// The Handler is called on some USB bus events, and to handle all control requests not already + /// handled by the USB stack. + pub fn handler(&mut self, handler: &'d mut dyn Handler) { + assert!( + self.handlers.push(handler).is_ok(), + "embassy-usb: handler list full. Increase the `max_handler_count` compile-time setting. Current value: {}", + MAX_HANDLER_COUNT + ); + } + + /// Allocates a new string index. + pub fn string(&mut self) -> StringIndex { + let index = self.next_string_index; + self.next_string_index += 1; + StringIndex::new(index) + } + + /// Add an MS OS 2.0 Descriptor Set. + /// + /// Panics if called more than once. + pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) { + self.msos_descriptor.header(windows_version, vendor_code); + } + + /// Add an MS OS 2.0 Device Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + self.msos_descriptor.device_feature(desc); + } + + /// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that + /// do not add their own. + pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> { + &mut self.msos_descriptor + } +} + +/// Function builder. +/// +/// A function is a logical grouping of interfaces that perform a given USB function. +/// If [`Config::composite_with_iads`] is set, each function will have an IAD descriptor. +/// If not, functions will not be visible as descriptors. +pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { + builder: &'a mut Builder<'d, D>, + iface_count_index: Option, + + first_interface: InterfaceNumber, +} + +impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { + fn drop(&mut self) { + self.builder.msos_descriptor.end_function(); + } +} + +impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { + /// Add an interface to the function. + /// + /// Interface numbers are guaranteed to be allocated consecutively, starting from 0. + pub fn interface(&mut self) -> InterfaceBuilder<'_, 'd, D> { + if let Some(i) = self.iface_count_index { + self.builder.config_descriptor.buf[i] += 1; + } + + let number = self.builder.interfaces.len() as _; + let iface = Interface { + current_alt_setting: 0, + num_alt_settings: 0, + }; + + assert!(self.builder.interfaces.push(iface).is_ok(), + "embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}", + MAX_INTERFACE_COUNT + ); + + InterfaceBuilder { + builder: self.builder, + interface_number: InterfaceNumber::new(number), + next_alt_setting_number: 0, + } + } + + /// Add an MS OS 2.0 Function Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + if !self.builder.msos_descriptor.is_in_config_subset() { + self.builder.msos_descriptor.configuration(0); + } + + if !self.builder.msos_descriptor.is_in_function_subset() { + self.builder.msos_descriptor.function(self.first_interface); + } + + self.builder.msos_descriptor.function_feature(desc); + } +} + +/// Interface builder. +pub struct InterfaceBuilder<'a, 'd, D: Driver<'d>> { + builder: &'a mut Builder<'d, D>, + interface_number: InterfaceNumber, + next_alt_setting_number: u8, +} + +impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { + /// Get the interface number. + pub const fn interface_number(&self) -> InterfaceNumber { + self.interface_number + } + + /// Allocates a new string index. + pub fn string(&mut self) -> StringIndex { + self.builder.string() + } + + /// Add an alternate setting to the interface and write its descriptor. + /// + /// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0. + /// + /// The first alternate setting, with number 0, is the default one. + pub fn alt_setting( + &mut self, + class: u8, + subclass: u8, + protocol: u8, + interface_string: Option, + ) -> InterfaceAltBuilder<'_, 'd, D> { + let number = self.next_alt_setting_number; + self.next_alt_setting_number += 1; + self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1; + + self.builder.config_descriptor.interface_alt( + self.interface_number, + number, + class, + subclass, + protocol, + interface_string, + ); + + InterfaceAltBuilder { + builder: self.builder, + interface_number: self.interface_number, + alt_setting_number: number, + } + } +} + +/// Interface alternate setting builder. +pub struct InterfaceAltBuilder<'a, 'd, D: Driver<'d>> { + builder: &'a mut Builder<'d, D>, + interface_number: InterfaceNumber, + alt_setting_number: u8, +} + +impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { + /// Get the interface number. + pub const fn interface_number(&self) -> InterfaceNumber { + self.interface_number + } + + /// Get the alternate setting number. + pub const fn alt_setting_number(&self) -> u8 { + self.alt_setting_number + } + + /// Add a custom descriptor to this alternate setting. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn descriptor(&mut self, descriptor_type: u8, descriptor: &[u8]) { + self.builder.config_descriptor.write(descriptor_type, descriptor, &[]); + } + + /// Add a custom Binary Object Store (BOS) descriptor to this alternate setting. + pub fn bos_capability(&mut self, capability_type: u8, capability: &[u8]) { + self.builder.bos_descriptor.capability(capability_type, capability); + } + + /// Write a custom endpoint descriptor for a certain endpoint. + /// + /// This can be necessary, if the endpoint descriptors can only be written + /// after the endpoint was created. As an example, an endpoint descriptor + /// may contain the address of an endpoint that was allocated earlier. + pub fn endpoint_descriptor( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + self.builder + .config_descriptor + .endpoint(endpoint, synchronization_type, usage_type, extra_fields); + } + + /// Allocate an IN endpoint, without writing its descriptor. + /// + /// Used for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + let ep = self + .builder + .driver + .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) + .expect("alloc_endpoint_in failed"); + + ep + } + + fn endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + let ep = self.alloc_endpoint_in(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); + + ep + } + + /// Allocate an OUT endpoint, without writing its descriptor. + /// + /// Use for granular control over the order of endpoint and descriptor creation. + pub fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> D::EndpointOut { + let ep = self + .builder + .driver + .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) + .expect("alloc_endpoint_out failed"); + + ep + } + + fn endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + let ep = self.alloc_endpoint_out(ep_type, max_packet_size, interval_ms); + self.endpoint_descriptor(ep.info(), synchronization_type, usage_type, extra_fields); + + ep + } + + /// Allocate a BULK IN endpoint and write its descriptor. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn endpoint_bulk_in(&mut self, max_packet_size: u16) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a BULK OUT endpoint and write its descriptor. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn endpoint_bulk_out(&mut self, max_packet_size: u16) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Bulk, + max_packet_size, + 0, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a INTERRUPT IN endpoint and write its descriptor. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a INTERRUPT OUT endpoint and write its descriptor. + pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Interrupt, + max_packet_size, + interval_ms, + SynchronizationType::NoSynchronization, + UsageType::DataEndpoint, + &[], + ) + } + + /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn endpoint_isochronous_in( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointIn { + self.endpoint_in( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) + } + + /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. + pub fn endpoint_isochronous_out( + &mut self, + max_packet_size: u16, + interval_ms: u8, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) -> D::EndpointOut { + self.endpoint_out( + EndpointType::Isochronous, + max_packet_size, + interval_ms, + synchronization_type, + usage_type, + extra_fields, + ) + } +} diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs new file mode 100644 index 0000000..c5b1a56 --- /dev/null +++ b/embassy-usb/src/class/cdc_acm.rs @@ -0,0 +1,546 @@ +//! CDC-ACM class implementation, aka Serial over USB. + +use core::cell::{Cell, RefCell}; +use core::future::{poll_fn, Future}; +use core::mem::{self, MaybeUninit}; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; + +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_CLASS_CDC: u8 = 0x02; + +const USB_CLASS_CDC_DATA: u8 = 0x0a; +const CDC_SUBCLASS_ACM: u8 = 0x02; +const CDC_PROTOCOL_NONE: u8 = 0x00; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_ACM: u8 = 0x02; +const CDC_TYPE_UNION: u8 = 0x06; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +#[allow(unused)] +const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +const REQ_SET_LINE_CODING: u8 = 0x20; +const REQ_GET_LINE_CODING: u8 = 0x21; +const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; + +/// Internal state for CDC-ACM +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, +} + +impl<'a> Default for State<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> State<'a> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + shared: ControlShared::default(), + } + } +} + +/// Packet level implementation of a CDC-ACM serial port. +/// +/// This class can be used directly and it has the least overhead due to directly reading and +/// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial +/// port. The following constraints must be followed if you use this class directly: +/// +/// - `read_packet` must be called with a buffer large enough to hold `max_packet_size` bytes. +/// - `write_packet` must not be called with a buffer larger than `max_packet_size` bytes. +/// - If you write a packet that is exactly `max_packet_size` bytes long, it won't be processed by the +/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) +/// can be sent if there is no other data to send. This is because USB bulk transactions must be +/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. +pub struct CdcAcmClass<'d, D: Driver<'d>> { + _comm_ep: D::EndpointIn, + _data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +struct Control<'a> { + comm_if: InterfaceNumber, + shared: &'a ControlShared, +} + +/// Shared data between Control and CdcAcmClass +struct ControlShared { + line_coding: CriticalSectionMutex>, + dtr: AtomicBool, + rts: AtomicBool, + + waker: RefCell, + changed: AtomicBool, +} + +impl Default for ControlShared { + fn default() -> Self { + ControlShared { + dtr: AtomicBool::new(false), + rts: AtomicBool::new(false), + line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + })), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), + } + } +} + +impl ControlShared { + fn changed(&self) -> impl Future + '_ { + poll_fn(|cx| { + if self.changed.load(Ordering::Relaxed) { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } else { + self.waker.borrow_mut().register(cx.waker()); + Poll::Pending + } + }) + } +} + +impl<'a> Control<'a> { + fn shared(&mut self) -> &'a ControlShared { + self.shared + } +} + +impl<'d> Handler for Control<'d> { + fn reset(&mut self) { + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(LineCoding::default())); + shared.dtr.store(false, Ordering::Relaxed); + shared.rts.store(false, Ordering::Relaxed); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + } + + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + Some(OutResponse::Accepted) + } + REQ_SET_LINE_CODING if data.len() >= 7 => { + let coding = LineCoding { + data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), + stop_bits: data[4].into(), + parity_type: data[5].into(), + data_bits: data[6], + }; + let shared = self.shared(); + shared.line_coding.lock(|x| x.set(coding)); + debug!("Set line coding to: {:?}", coding); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + + Some(OutResponse::Accepted) + } + REQ_SET_CONTROL_LINE_STATE => { + let dtr = (req.value & 0x0001) != 0; + let rts = (req.value & 0x0002) != 0; + + let shared = self.shared(); + shared.dtr.store(dtr, Ordering::Relaxed); + shared.rts.store(rts, Ordering::Relaxed); + debug!("Set dtr {}, rts {}", dtr, rts); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + + Some(OutResponse::Accepted) + } + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + + match req.request { + // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. + REQ_GET_LINE_CODING if req.length == 7 => { + debug!("Sending line coding"); + let coding = self.shared().line_coding.lock(Cell::get); + assert!(buf.len() >= 7); + buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); + buf[4] = coding.stop_bits as u8; + buf[5] = coding.parity_type as u8; + buf[6] = coding.data_bits; + Some(InResponse::Accepted(&buf[0..7])) + } + _ => Some(InResponse::Rejected), + } + } +} + +impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { + /// Creates a new CdcAcmClass with the provided UsbBus and `max_packet_size` in bytes. For + /// full-speed devices, `max_packet_size` has to be one of 8, 16, 32 or 64. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { + assert!(builder.control_buf_len() >= 7); + + let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); + + // Control interface + let mut iface = func.interface(); + let comm_if = iface.interface_number(); + let data_if = u8::from(comm_if) + 1; + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_ACM, // bDescriptorSubtype + 0x02, // bmCapabilities: + // D1: Device supports the request combination of + // Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, + // and the Notification Serial_State. + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + data_if, // bSubordinateInterface + ], + ); + + let comm_ep = alt.endpoint_interrupt_in(8, 255); + + // Data interface + let mut iface = func.interface(); + let data_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None); + let read_ep = alt.endpoint_bulk_out(max_packet_size); + let write_ep = alt.endpoint_bulk_in(max_packet_size); + + drop(func); + + let control = state.control.write(Control { + shared: &state.shared, + comm_if, + }); + builder.handler(control); + + let control_shared = &state.shared; + + CdcAcmClass { + _comm_ep: comm_ep, + _data_if: data_if, + read_ep, + write_ep, + control: control_shared, + } + } + + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(Cell::get) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } + + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. + pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { + ( + Sender { + write_ep: self.write_ep, + control: self.control, + }, + Receiver { + read_ep: self.read_ep, + control: self.control, + }, + ) + } + + /// Split the class into sender, receiver and control + /// + /// Allows concurrently sending and receiving packets whilst monitoring for + /// control changes (dtr, rts) + pub fn split_with_control(self) -> (Sender<'d, D>, Receiver<'d, D>, ControlChanged<'d>) { + ( + Sender { + write_ep: self.write_ep, + control: self.control, + }, + Receiver { + read_ep: self.read_ep, + control: self.control, + }, + ControlChanged { control: self.control }, + ) + } +} + +/// CDC ACM Control status change monitor +/// +/// You can obtain a `ControlChanged` with [`CdcAcmClass::split_with_control`] +pub struct ControlChanged<'d> { + control: &'d ControlShared, +} + +impl<'d> ControlChanged<'d> { + /// Return a future for when the control settings change + pub async fn control_changed(&self) { + self.control.changed().await; + } +} + +/// CDC ACM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcAcmClass::split`] +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.write_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(Cell::get) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.write_ep.wait_enabled().await; + } +} + +/// CDC ACM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcAcmClass::split`] +pub struct Receiver<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(Cell::get) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Reads a single packet from the OUT endpoint. + /// Must be called with a buffer large enough to hold max_packet_size bytes. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } +} + +/// Number of stop bits for LineCoding +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum StopBits { + /// 1 stop bit + One = 0, + + /// 1.5 stop bits + OnePointFive = 1, + + /// 2 stop bits + Two = 2, +} + +impl From for StopBits { + fn from(value: u8) -> Self { + if value <= 2 { + unsafe { mem::transmute(value) } + } else { + StopBits::One + } + } +} + +/// Parity for LineCoding +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ParityType { + /// No parity bit. + None = 0, + /// Parity bit is 1 if the amount of `1` bits in the data byte is odd. + Odd = 1, + /// Parity bit is 1 if the amount of `1` bits in the data byte is even. + Even = 2, + /// Parity bit is always 1 + Mark = 3, + /// Parity bit is always 0 + Space = 4, +} + +impl From for ParityType { + fn from(value: u8) -> Self { + if value <= 4 { + unsafe { mem::transmute(value) } + } else { + ParityType::None + } + } +} + +/// Line coding parameters +/// +/// This is provided by the host for specifying the standard UART parameters such as baud rate. Can +/// be ignored if you don't plan to interface with a physical UART. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct LineCoding { + stop_bits: StopBits, + data_bits: u8, + parity_type: ParityType, + data_rate: u32, +} + +impl LineCoding { + /// Gets the number of stop bits for UART communication. + pub fn stop_bits(&self) -> StopBits { + self.stop_bits + } + + /// Gets the number of data bits for UART communication. + pub const fn data_bits(&self) -> u8 { + self.data_bits + } + + /// Gets the parity type for UART communication. + pub const fn parity_type(&self) -> ParityType { + self.parity_type + } + + /// Gets the data rate in bits per second for UART communication. + pub const fn data_rate(&self) -> u32 { + self.data_rate + } +} + +impl Default for LineCoding { + fn default() -> Self { + LineCoding { + stop_bits: StopBits::One, + data_bits: 8, + parity_type: ParityType::None, + data_rate: 8_000, + } + } +} diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs new file mode 100644 index 0000000..57d3229 --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -0,0 +1,104 @@ +//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the CDC-NCM class. + +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_usb_driver::Driver; + +use super::{CdcNcmClass, Receiver, Sender}; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the CDC-NCM class. +/// +/// You must call `.run()` in a background task for the class to operate. +pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { + tx_usb: Sender<'d, D>, + rx_usb: Receiver<'d, D>, + ch: ch::Runner<'d, MTU>, +} + +impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + /// Run the CDC-NCM class. + /// + /// You must call this in a background task for the class to operate. + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let rx_fut = async move { + loop { + trace!("WAITING for connection"); + state_chan.set_link_state(LinkState::Down); + + self.rx_usb.wait_connection().await.unwrap(); + + trace!("Connected"); + state_chan.set_link_state(LinkState::Up); + + loop { + let p = rx_chan.rx_buf().await; + match self.rx_usb.read_packet(p).await { + Ok(n) => rx_chan.rx_done(n), + Err(e) => { + warn!("error reading packet: {:?}", e); + break; + } + }; + } + } + }; + let tx_fut = async move { + loop { + let p = tx_chan.tx_buf().await; + if let Err(e) = self.tx_usb.write_packet(p).await { + warn!("Failed to TX packet: {:?}", e); + } + tx_chan.tx_done(); + } + }; + match select(rx_fut, tx_fut).await { + Either::First(x) => x, + Either::Second(x) => x, + } + } +} + +// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug? +//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd; +/// Type alias for the embassy-net driver for CDC-NCM. +pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>; + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Obtain a driver for using the CDC-NCM class with [`embassy-net`](https://crates.io/crates/embassy-net). + pub fn into_embassy_net_device( + self, + state: &'d mut State, + ethernet_address: [u8; 6], + ) -> (Runner<'d, D, MTU>, Device<'d, MTU>) { + let (tx_usb, rx_usb) = self.split(); + let (runner, device) = ch::new( + &mut state.ch_state, + ch::driver::HardwareAddress::Ethernet(ethernet_address), + ); + + ( + Runner { + tx_usb, + rx_usb, + ch: runner, + }, + device, + ) + } +} diff --git a/embassy-usb/src/class/cdc_ncm/mod.rs b/embassy-usb/src/class/cdc_ncm/mod.rs new file mode 100644 index 0000000..09d923d --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -0,0 +1,528 @@ +//! CDC-NCM class implementation, aka Ethernet over USB. +//! +//! # Compatibility +//! +//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11. +//! +//! Linux: Well-supported since forever. +//! +//! Android: Support for CDC-NCM is spotty and varies across manufacturers. +//! +//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +//! This is due to regex spaghetti: +//! and this nonsense in the linux kernel: + +use core::mem::{size_of, MaybeUninit}; +use core::ptr::{addr_of, copy_nonoverlapping}; + +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::{InterfaceNumber, StringIndex}; +use crate::{Builder, Handler}; + +pub mod embassy_net; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_CLASS_CDC: u8 = 0x02; + +const USB_CLASS_CDC_DATA: u8 = 0x0a; +const CDC_SUBCLASS_NCM: u8 = 0x0d; + +const CDC_PROTOCOL_NONE: u8 = 0x00; +const CDC_PROTOCOL_NTB: u8 = 0x01; + +const CS_INTERFACE: u8 = 0x24; +const CDC_TYPE_HEADER: u8 = 0x00; +const CDC_TYPE_UNION: u8 = 0x06; +const CDC_TYPE_ETHERNET: u8 = 0x0F; +const CDC_TYPE_NCM: u8 = 0x1A; + +const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; +//const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; +//const REQ_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40; +//const REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x41; +//const REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x42; +//const REQ_SET_ETHERNET_PACKET_FILTER: u8 = 0x43; +//const REQ_GET_ETHERNET_STATISTIC: u8 = 0x44; +const REQ_GET_NTB_PARAMETERS: u8 = 0x80; +//const REQ_GET_NET_ADDRESS: u8 = 0x81; +//const REQ_SET_NET_ADDRESS: u8 = 0x82; +//const REQ_GET_NTB_FORMAT: u8 = 0x83; +//const REQ_SET_NTB_FORMAT: u8 = 0x84; +//const REQ_GET_NTB_INPUT_SIZE: u8 = 0x85; +const REQ_SET_NTB_INPUT_SIZE: u8 = 0x86; +//const REQ_GET_MAX_DATAGRAM_SIZE: u8 = 0x87; +//const REQ_SET_MAX_DATAGRAM_SIZE: u8 = 0x88; +//const REQ_GET_CRC_MODE: u8 = 0x89; +//const REQ_SET_CRC_MODE: u8 = 0x8A; + +//const NOTIF_MAX_PACKET_SIZE: u16 = 8; +//const NOTIF_POLL_INTERVAL: u8 = 20; + +const NTB_MAX_SIZE: usize = 2048; +const SIG_NTH: u32 = 0x484d_434e; +const SIG_NDP_NO_FCS: u32 = 0x304d_434e; +const SIG_NDP_WITH_FCS: u32 = 0x314d_434e; + +const ALTERNATE_SETTING_DISABLED: u8 = 0x00; +const ALTERNATE_SETTING_ENABLED: u8 = 0x01; + +/// Simple NTB header (NTH+NDP all in one) for sending packets +#[repr(packed)] +#[allow(unused)] +struct NtbOutHeader { + // NTH + nth_sig: u32, + nth_len: u16, + nth_seq: u16, + nth_total_len: u16, + nth_first_index: u16, + + // NDP + ndp_sig: u32, + ndp_len: u16, + ndp_next_index: u16, + ndp_datagram_index: u16, + ndp_datagram_len: u16, + ndp_term1: u16, + ndp_term2: u16, +} + +#[repr(packed)] +#[allow(unused)] +struct NtbParameters { + length: u16, + formats_supported: u16, + in_params: NtbParametersDir, + out_params: NtbParametersDir, +} + +#[repr(packed)] +#[allow(unused)] +struct NtbParametersDir { + max_size: u32, + divisor: u16, + payload_remainder: u16, + out_alignment: u16, + max_datagram_count: u16, +} + +fn byteify(buf: &mut [u8], data: T) -> &[u8] { + let len = size_of::(); + unsafe { copy_nonoverlapping(addr_of!(data).cast(), buf.as_mut_ptr(), len) } + &buf[..len] +} + +/// Internal state for the CDC-NCM class. +pub struct State<'a> { + control: MaybeUninit>, + shared: ControlShared, +} + +impl<'a> Default for State<'a> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> State<'a> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + shared: ControlShared::default(), + } + } +} + +/// Shared data between Control and `CdcAcmClass` +#[derive(Default)] +struct ControlShared { + mac_addr: [u8; 6], +} + +struct Control<'a> { + mac_addr_string: StringIndex, + shared: &'a ControlShared, + mac_addr_str: [u8; 12], + comm_if: InterfaceNumber, + data_if: InterfaceNumber, +} + +impl<'d> Handler for Control<'d> { + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + if iface != self.data_if { + return; + } + + match alternate_setting { + ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), + ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), + _ => unreachable!(), + } + } + + fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + + match req.request { + REQ_SEND_ENCAPSULATED_COMMAND => { + // We don't actually support encapsulated commands but pretend we do for standards + // compatibility. + Some(OutResponse::Accepted) + } + REQ_SET_NTB_INPUT_SIZE => { + // TODO + Some(OutResponse::Accepted) + } + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + + match req.request { + REQ_GET_NTB_PARAMETERS => { + let res = NtbParameters { + length: size_of::() as _, + formats_supported: 1, // only 16bit, + in_params: NtbParametersDir { + max_size: NTB_MAX_SIZE as _, + divisor: 4, + payload_remainder: 0, + out_alignment: 4, + max_datagram_count: 0, // not used + }, + out_params: NtbParametersDir { + max_size: NTB_MAX_SIZE as _, + divisor: 4, + payload_remainder: 0, + out_alignment: 4, + max_datagram_count: 1, // We only decode 1 packet per NTB + }, + }; + Some(InResponse::Accepted(byteify(buf, res))) + } + _ => Some(InResponse::Rejected), + } + } + + fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { + if index == self.mac_addr_string { + let mac_addr = self.shared.mac_addr; + let s = &mut self.mac_addr_str; + for i in 0..12 { + let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF; + s[i] = match n { + 0x0..=0x9 => b'0' + n, + 0xA..=0xF => b'A' + n - 0xA, + _ => unreachable!(), + } + } + + Some(unsafe { core::str::from_utf8_unchecked(s) }) + } else { + warn!("unknown string index requested"); + None + } + } +} + +/// CDC-NCM class +pub struct CdcNcmClass<'d, D: Driver<'d>> { + _comm_if: InterfaceNumber, + comm_ep: D::EndpointIn, + + data_if: InterfaceNumber, + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + + _control: &'d ControlShared, + + max_packet_size: usize, +} + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Create a new CDC NCM class. + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + mac_address: [u8; 6], + max_packet_size: u16, + ) -> Self { + state.shared.mac_addr = mac_address; + + let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + + // Control interface + let mut iface = func.interface(); + let mac_addr_string = iface.string(); + let comm_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_HEADER, // bDescriptorSubtype + 0x10, + 0x01, // bcdCDC (1.10) + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_UNION, // bDescriptorSubtype + comm_if.into(), // bControlInterface + u8::from(comm_if) + 1, // bSubordinateInterface + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_ETHERNET, // bDescriptorSubtype + mac_addr_string.into(), // iMACAddress + 0, // bmEthernetStatistics + 0, // | + 0, // | + 0, // | + 0xea, // wMaxSegmentSize = 1514 + 0x05, // | + 0, // wNumberMCFilters + 0, // | + 0, // bNumberPowerFilters + ], + ); + alt.descriptor( + CS_INTERFACE, + &[ + CDC_TYPE_NCM, // bDescriptorSubtype + 0x00, // bcdNCMVersion + 0x01, // | + 0, // bmNetworkCapabilities + ], + ); + + let comm_ep = alt.endpoint_interrupt_in(8, 255); + + // Data interface + let mut iface = func.interface(); + let data_if = iface.interface_number(); + let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); + let read_ep = alt.endpoint_bulk_out(max_packet_size); + let write_ep = alt.endpoint_bulk_in(max_packet_size); + + drop(func); + + let control = state.control.write(Control { + mac_addr_string, + shared: &state.shared, + mac_addr_str: [0; 12], + comm_if, + data_if, + }); + builder.handler(control); + + CdcNcmClass { + _comm_if: comm_if, + comm_ep, + data_if, + read_ep, + write_ep, + _control: &state.shared, + max_packet_size: max_packet_size as usize, + } + } + + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. + pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { + ( + Sender { + write_ep: self.write_ep, + seq: 0, + max_packet_size: self.max_packet_size, + }, + Receiver { + data_if: self.data_if, + comm_ep: self.comm_ep, + read_ep: self.read_ep, + }, + ) + } +} + +/// CDC NCM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcNcmClass::split`] +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + seq: u16, + max_packet_size: usize, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Write a packet. + /// + /// This waits until the packet is successfully stored in the CDC-NCM endpoint buffers. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + const OUT_HEADER_LEN: usize = 28; + const ABS_MAX_PACKET_SIZE: usize = 512; + + let seq = self.seq; + self.seq = self.seq.wrapping_add(1); + + let header = NtbOutHeader { + nth_sig: SIG_NTH, + nth_len: 0x0c, + nth_seq: seq, + nth_total_len: (data.len() + OUT_HEADER_LEN) as u16, + nth_first_index: 0x0c, + + ndp_sig: SIG_NDP_NO_FCS, + ndp_len: 0x10, + ndp_next_index: 0x00, + ndp_datagram_index: OUT_HEADER_LEN as u16, + ndp_datagram_len: data.len() as u16, + ndp_term1: 0x00, + ndp_term2: 0x00, + }; + + // Build first packet on a buffer, send next packets straight from `data`. + let mut buf = [0; ABS_MAX_PACKET_SIZE]; + let n = byteify(&mut buf, header); + assert_eq!(n.len(), OUT_HEADER_LEN); + + if OUT_HEADER_LEN + data.len() < self.max_packet_size { + // First packet is not full, just send it. + // No need to send ZLP because it's short for sure. + buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data); + self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?; + } else { + let (d1, d2) = data.split_at(self.max_packet_size - OUT_HEADER_LEN); + + buf[OUT_HEADER_LEN..self.max_packet_size].copy_from_slice(d1); + self.write_ep.write(&buf[..self.max_packet_size]).await?; + + for chunk in d2.chunks(self.max_packet_size) { + self.write_ep.write(chunk).await?; + } + + // Send ZLP if needed. + if d2.len() % self.max_packet_size == 0 { + self.write_ep.write(&[]).await?; + } + } + + Ok(()) + } +} + +/// CDC NCM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcNcmClass::split`] +pub struct Receiver<'d, D: Driver<'d>> { + data_if: InterfaceNumber, + comm_ep: D::EndpointIn, + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Write a network packet. + /// + /// This waits until a packet is successfully received from the endpoint buffers. + pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { + // Retry loop + loop { + // read NTB + let mut ntb = [0u8; NTB_MAX_SIZE]; + let mut pos = 0; + loop { + let n = self.read_ep.read(&mut ntb[pos..]).await?; + pos += n; + if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE { + break; + } + } + + let ntb = &ntb[..pos]; + + // Process NTB header (NTH) + let Some(nth) = ntb.get(..12) else { + warn!("Received too short NTB"); + continue; + }; + let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap()); + if sig != SIG_NTH { + warn!("Received bad NTH sig."); + continue; + } + let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize; + + // Process NTB Datagram Pointer (NDP) + let Some(ndp) = ntb.get(ndp_idx..ndp_idx + 12) else { + warn!("NTH has an NDP pointer out of range."); + continue; + }; + let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap()); + if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS { + warn!("Received bad NDP sig."); + continue; + } + let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize; + let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize; + + if datagram_index == 0 || datagram_len == 0 { + // empty, ignore. This is allowed by the spec, so don't warn. + continue; + } + + // Process actual datagram, finally. + let Some(datagram) = ntb.get(datagram_index..datagram_index + datagram_len) else { + warn!("NDP has a datagram pointer out of range."); + continue; + }; + buf[..datagram_len].copy_from_slice(datagram); + + return Ok(datagram_len); + } + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) -> Result<(), EndpointError> { + loop { + self.read_ep.wait_enabled().await; + self.comm_ep.wait_enabled().await; + + let buf = [ + 0xA1, //bmRequestType + 0x00, //bNotificationType = NETWORK_CONNECTION + 0x01, // wValue = connected + 0x00, + self.data_if.into(), // wIndex = interface + 0x00, + 0x00, // wLength + 0x00, + ]; + match self.comm_ep.write(&buf).await { + Ok(()) => break, // Done! + Err(EndpointError::Disabled) => {} // Got disabled again, wait again. + Err(e) => return Err(e), + } + } + + Ok(()) + } +} diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs new file mode 100644 index 0000000..6d9e0ac --- /dev/null +++ b/embassy-usb/src/class/hid.rs @@ -0,0 +1,552 @@ +//! USB HID (Human Interface Device) class implementation. + +use core::mem::MaybeUninit; +use core::ops::Range; +use core::sync::atomic::{AtomicUsize, Ordering}; + +#[cfg(feature = "usbd-hid")] +use ssmarshal::serialize; +#[cfg(feature = "usbd-hid")] +use usbd_hid::descriptor::AsInputReport; + +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +const USB_CLASS_HID: u8 = 0x03; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +// HID +const HID_DESC_DESCTYPE_HID: u8 = 0x21; +const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; +const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; +const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; + +const HID_REQ_SET_IDLE: u8 = 0x0a; +const HID_REQ_GET_IDLE: u8 = 0x02; +const HID_REQ_GET_REPORT: u8 = 0x01; +const HID_REQ_SET_REPORT: u8 = 0x09; +const HID_REQ_GET_PROTOCOL: u8 = 0x03; +const HID_REQ_SET_PROTOCOL: u8 = 0x0b; + +/// Configuration for the HID class. +pub struct Config<'d> { + /// HID report descriptor. + pub report_descriptor: &'d [u8], + + /// Handler for control requests. + pub request_handler: Option<&'d mut dyn RequestHandler>, + + /// Configures how frequently the host should poll for reading/writing HID reports. + /// + /// A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub poll_ms: u8, + + /// Max packet size for both the IN and OUT endpoints. + pub max_packet_size: u16, +} + +/// Report ID +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReportId { + /// IN report + In(u8), + /// OUT report + Out(u8), + /// Feature report + Feature(u8), +} + +impl ReportId { + const fn try_from(value: u16) -> Result { + match value >> 8 { + 1 => Ok(ReportId::In(value as u8)), + 2 => Ok(ReportId::Out(value as u8)), + 3 => Ok(ReportId::Feature(value as u8)), + _ => Err(()), + } + } +} + +/// Internal state for USB HID. +pub struct State<'d> { + control: MaybeUninit>, + out_report_offset: AtomicUsize, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub const fn new() -> Self { + State { + control: MaybeUninit::uninit(), + out_report_offset: AtomicUsize::new(0), + } + } +} + +/// USB HID reader/writer. +pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { + reader: HidReader<'d, D, READ_N>, + writer: HidWriter<'d, D, WRITE_N>, +} + +fn build<'d, D: Driver<'d>>( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + config: Config<'d>, + with_out_endpoint: bool, +) -> (Option, D::EndpointIn, &'d AtomicUsize) { + let len = config.report_descriptor.len(); + + let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let mut iface = func.interface(); + let if_num = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); + + // HID descriptor + alt.descriptor( + HID_DESC_DESCTYPE_HID, + &[ + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (len & 0xFF) as u8, + (len >> 8 & 0xFF) as u8, + ], + ); + + let ep_in = alt.endpoint_interrupt_in(config.max_packet_size, config.poll_ms); + let ep_out = if with_out_endpoint { + Some(alt.endpoint_interrupt_out(config.max_packet_size, config.poll_ms)) + } else { + None + }; + + drop(func); + + let control = state.control.write(Control::new( + if_num, + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); + builder.handler(control); + + (ep_out, ep_in, &state.out_report_offset) +} + +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWriter<'d, D, READ_N, WRITE_N> { + /// Creates a new `HidReaderWriter`. + /// + /// This will allocate one IN and one OUT endpoints. If you only need writing (sending) + /// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only. + /// + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { + let (ep_out, ep_in, offset) = build(builder, state, config, true); + + Self { + reader: HidReader { + ep_out: ep_out.unwrap(), + offset, + }, + writer: HidWriter { ep_in }, + } + } + + /// Splits into separate readers/writers for input and output reports. + pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { + (self.reader, self.writer) + } + + /// Waits for both IN and OUT endpoints to be enabled. + pub async fn ready(&mut self) { + self.reader.ready().await; + self.writer.ready().await; + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + self.writer.write_serialize(r).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + self.writer.write(report).await + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// See [`HidReader::read`]. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.reader.read(buf).await + } +} + +/// USB HID writer. +/// +/// You can obtain a `HidWriter` using [`HidReaderWriter::split`]. +pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { + ep_in: D::EndpointIn, +} + +/// USB HID reader. +/// +/// You can obtain a `HidReader` using [`HidReaderWriter::split`]. +pub struct HidReader<'d, D: Driver<'d>, const N: usize> { + ep_out: D::EndpointOut, + offset: &'d AtomicUsize, +} + +/// Error when reading a HID report. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ReadError { + /// The given buffer was too small to read the received report. + BufferOverflow, + /// The endpoint is disabled. + Disabled, + /// The report was only partially read. See [`HidReader::read`] for details. + Sync(Range), +} + +impl From for ReadError { + fn from(val: EndpointError) -> Self { + use EndpointError::{BufferOverflow, Disabled}; + match val { + BufferOverflow => ReadError::BufferOverflow, + Disabled => ReadError::Disabled, + } + } +} + +impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> { + /// Creates a new HidWriter. + /// + /// This will allocate one IN endpoint only, so the host won't be able to send + /// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { + let (ep_out, ep_in, _offset) = build(builder, state, config, false); + + assert!(ep_out.is_none()); + + Self { ep_in } + } + + /// Waits for the interrupt in endpoint to be enabled. + pub async fn ready(&mut self) { + self.ep_in.wait_enabled().await; + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + let mut buf: [u8; N] = [0; N]; + let Ok(size) = serialize(&mut buf, r) else { + return Err(EndpointError::BufferOverflow); + }; + self.write(&buf[0..size]).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + assert!(report.len() <= N); + + let max_packet_size = usize::from(self.ep_in.info().max_packet_size); + let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); + for chunk in report.chunks(max_packet_size) { + self.ep_in.write(chunk).await?; + } + + if zlp_needed { + self.ep_in.write(&[]).await?; + } + + Ok(()) + } +} + +impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { + /// Waits for the interrupt out endpoint to be enabled. + pub async fn ready(&mut self) { + self.ep_out.wait_enabled().await; + } + + /// Delivers output reports from the Interrupt Out pipe to `handler`. + /// + /// If `use_report_ids` is true, the first byte of the report will be used as + /// the `ReportId` value. Otherwise the `ReportId` value will be 0. + pub async fn run(mut self, use_report_ids: bool, handler: &mut T) -> ! { + let offset = self.offset.load(Ordering::Acquire); + assert!(offset == 0); + let mut buf = [0; N]; + loop { + match self.read(&mut buf).await { + Ok(len) => { + let id = if use_report_ids { buf[0] } else { 0 }; + handler.set_report(ReportId::Out(id), &buf[..len]); + } + Err(ReadError::BufferOverflow) => warn!( + "Host sent output report larger than the configured maximum output report length ({})", + N + ), + Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, + Err(ReadError::Sync(_)) => unreachable!(), + } + } + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// **Note:** Any reports sent from the host over the control pipe will be + /// passed to [`RequestHandler::set_report()`] for handling. The application + /// is responsible for ensuring output reports from both pipes are handled + /// correctly. + /// + /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output + /// reports may be split across multiple packets) and this method's future + /// is dropped after some packets have been read, the next call to `read()` + /// will return a [`ReadError::Sync`]. The range in the sync error + /// indicates the portion `buf` that was filled by the current call to + /// `read()`. If the dropped future used the same `buf`, then `buf` will + /// contain the full report. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + assert!(N != 0); + assert!(buf.len() >= N); + + // Read packets from the endpoint + let max_packet_size = usize::from(self.ep_out.info().max_packet_size); + let starting_offset = self.offset.load(Ordering::Acquire); + let mut total = starting_offset; + loop { + for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { + match self.ep_out.read(chunk).await { + Ok(size) => { + total += size; + if size < max_packet_size || total == N { + self.offset.store(0, Ordering::Release); + break; + } + self.offset.store(total, Ordering::Release); + } + Err(err) => { + self.offset.store(0, Ordering::Release); + return Err(err.into()); + } + } + } + + // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. + if total > 0 { + break; + } + } + + if starting_offset > 0 { + Err(ReadError::Sync(starting_offset..total)) + } else { + Ok(total) + } + } +} + +/// Handler for HID-related control requests. +pub trait RequestHandler { + /// Reads the value of report `id` into `buf` returning the size. + /// + /// Returns `None` if `id` is invalid or no data is available. + fn get_report(&mut self, id: ReportId, buf: &mut [u8]) -> Option { + let _ = (id, buf); + None + } + + /// Sets the value of report `id` to `data`. + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + let _ = (id, data); + OutResponse::Rejected + } + + /// Get the idle rate for `id`. + /// + /// If `id` is `None`, get the idle rate for all reports. Returning `None` + /// will reject the control request. Any duration at or above 1.024 seconds + /// or below 4ms will be returned as an indefinite idle rate. + fn get_idle_ms(&mut self, id: Option) -> Option { + let _ = id; + None + } + + /// Set the idle rate for `id` to `dur`. + /// + /// If `id` is `None`, set the idle rate of all input reports to `dur`. If + /// an indefinite duration is requested, `dur` will be set to `u32::MAX`. + fn set_idle_ms(&mut self, id: Option, duration_ms: u32) { + let _ = (id, duration_ms); + } +} + +struct Control<'d> { + if_num: InterfaceNumber, + report_descriptor: &'d [u8], + request_handler: Option<&'d mut dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + hid_descriptor: [u8; 9], +} + +impl<'d> Control<'d> { + fn new( + if_num: InterfaceNumber, + report_descriptor: &'d [u8], + request_handler: Option<&'d mut dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, + ) -> Self { + Control { + if_num, + report_descriptor, + request_handler, + out_report_offset, + hid_descriptor: [ + // Length of buf inclusive of size prefix + 9, + // Descriptor type + HID_DESC_DESCTYPE_HID, + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (report_descriptor.len() & 0xFF) as u8, + (report_descriptor.len() >> 8 & 0xFF) as u8, + ], + } + } +} + +impl<'d> Handler for Control<'d> { + fn reset(&mut self) { + self.out_report_offset.store(0, Ordering::Release); + } + + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.if_num.0 as u16) + { + return None; + } + + // This uses a defmt-specific formatter that causes use of the `log` + // feature to fail to build, so leave it defmt-specific for now. + #[cfg(feature = "defmt")] + trace!("HID control_out {:?} {=[u8]:x}", req, data); + match req.request { + HID_REQ_SET_IDLE => { + if let Some(handler) = self.request_handler.as_mut() { + let id = req.value as u8; + let id = (id != 0).then_some(ReportId::In(id)); + let dur = u32::from(req.value >> 8); + let dur = if dur == 0 { u32::MAX } else { 4 * dur }; + handler.set_idle_ms(id, dur); + } + Some(OutResponse::Accepted) + } + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler.as_mut()) { + (Ok(id), Some(handler)) => Some(handler.set_report(id, data)), + _ => Some(OutResponse::Rejected), + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + Some(OutResponse::Accepted) + } else { + warn!("HID Boot Protocol is unsupported."); + Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol + } + } + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if req.index != self.if_num.0 as u16 { + return None; + } + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Interface) => match req.request { + Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => Some(InResponse::Accepted(self.report_descriptor)), + HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)), + _ => Some(InResponse::Rejected), + }, + + _ => Some(InResponse::Rejected), + }, + (RequestType::Class, Recipient::Interface) => { + trace!("HID control_in {:?}", req); + match req.request { + HID_REQ_GET_REPORT => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self.request_handler.as_mut().and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + Some(InResponse::Accepted(&buf[0..size])) + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_IDLE => { + if let Some(handler) = self.request_handler.as_mut() { + let id = req.value as u8; + let id = (id != 0).then_some(ReportId::In(id)); + if let Some(dur) = handler.get_idle_ms(id) { + let dur = u8::try_from(dur / 4).unwrap_or(0); + buf[0] = dur; + Some(InResponse::Accepted(&buf[0..1])) + } else { + Some(InResponse::Rejected) + } + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_PROTOCOL => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + Some(InResponse::Accepted(&buf[0..1])) + } + _ => Some(InResponse::Rejected), + } + } + _ => None, + } + } +} diff --git a/embassy-usb/src/class/midi.rs b/embassy-usb/src/class/midi.rs new file mode 100644 index 0000000..52a96f2 --- /dev/null +++ b/embassy-usb/src/class/midi.rs @@ -0,0 +1,227 @@ +//! MIDI class implementation. + +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::Builder; + +/// This should be used as `device_class` when building the `UsbDevice`. +pub const USB_AUDIO_CLASS: u8 = 0x01; + +const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; +const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; +const MIDI_IN_JACK_SUBTYPE: u8 = 0x02; +const MIDI_OUT_JACK_SUBTYPE: u8 = 0x03; +const EMBEDDED: u8 = 0x01; +const EXTERNAL: u8 = 0x02; +const CS_INTERFACE: u8 = 0x24; +const CS_ENDPOINT: u8 = 0x25; +const HEADER_SUBTYPE: u8 = 0x01; +const MS_HEADER_SUBTYPE: u8 = 0x01; +const MS_GENERAL: u8 = 0x01; +const PROTOCOL_NONE: u8 = 0x00; +const MIDI_IN_SIZE: u8 = 0x06; +const MIDI_OUT_SIZE: u8 = 0x09; + +/// Packet level implementation of a USB MIDI device. +/// +/// This class can be used directly and it has the least overhead due to directly reading and +/// writing USB packets with no intermediate buffers, but it will not act like a stream-like port. +/// The following constraints must be followed if you use this class directly: +/// +/// - `read_packet` must be called with a buffer large enough to hold `max_packet_size` bytes. +/// - `write_packet` must not be called with a buffer larger than `max_packet_size` bytes. +/// - If you write a packet that is exactly `max_packet_size` bytes long, it won't be processed by the +/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) +/// can be sent if there is no other data to send. This is because USB bulk transactions must be +/// terminated with a short packet, even if the bulk endpoint is used for stream-like data. +pub struct MidiClass<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> MidiClass<'d, D> { + /// Creates a new `MidiClass` with the provided UsbBus, number of input and output jacks and `max_packet_size` in bytes. + /// For full-speed devices, `max_packet_size` has to be one of 8, 16, 32 or 64. + pub fn new(builder: &mut Builder<'d, D>, n_in_jacks: u8, n_out_jacks: u8, max_packet_size: u16) -> Self { + let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); + + // Audio control interface + let mut iface = func.interface(); + let audio_if = iface.interface_number(); + let midi_if = u8::from(audio_if) + 1; + let mut alt = iface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); + alt.descriptor(CS_INTERFACE, &[HEADER_SUBTYPE, 0x00, 0x01, 0x09, 0x00, 0x01, midi_if]); + + // MIDIStreaming interface + let mut iface = func.interface(); + let _midi_if = iface.interface_number(); + let mut alt = iface.alt_setting(USB_AUDIO_CLASS, USB_MIDISTREAMING_SUBCLASS, PROTOCOL_NONE, None); + + let midi_streaming_total_length = 7 + + (n_in_jacks + n_out_jacks) as usize * (MIDI_IN_SIZE + MIDI_OUT_SIZE) as usize + + 7 + + (4 + n_out_jacks as usize) + + 7 + + (4 + n_in_jacks as usize); + + alt.descriptor( + CS_INTERFACE, + &[ + MS_HEADER_SUBTYPE, + 0x00, + 0x01, + (midi_streaming_total_length & 0xFF) as u8, + ((midi_streaming_total_length >> 8) & 0xFF) as u8, + ], + ); + + // Calculates the index'th external midi in jack id + let in_jack_id_ext = |index| 2 * index + 1; + // Calculates the index'th embedded midi out jack id + let out_jack_id_emb = |index| 2 * index + 2; + // Calculates the index'th external midi out jack id + let out_jack_id_ext = |index| 2 * n_in_jacks + 2 * index + 1; + // Calculates the index'th embedded midi in jack id + let in_jack_id_emb = |index| 2 * n_in_jacks + 2 * index + 2; + + for i in 0..n_in_jacks { + alt.descriptor(CS_INTERFACE, &[MIDI_IN_JACK_SUBTYPE, EXTERNAL, in_jack_id_ext(i), 0x00]); + } + + for i in 0..n_out_jacks { + alt.descriptor(CS_INTERFACE, &[MIDI_IN_JACK_SUBTYPE, EMBEDDED, in_jack_id_emb(i), 0x00]); + } + + for i in 0..n_out_jacks { + alt.descriptor( + CS_INTERFACE, + &[ + MIDI_OUT_JACK_SUBTYPE, + EXTERNAL, + out_jack_id_ext(i), + 0x01, + in_jack_id_emb(i), + 0x01, + 0x00, + ], + ); + } + + for i in 0..n_in_jacks { + alt.descriptor( + CS_INTERFACE, + &[ + MIDI_OUT_JACK_SUBTYPE, + EMBEDDED, + out_jack_id_emb(i), + 0x01, + in_jack_id_ext(i), + 0x01, + 0x00, + ], + ); + } + + let mut endpoint_data = [ + MS_GENERAL, 0, // Number of jacks + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Jack mappings + ]; + endpoint_data[1] = n_out_jacks; + for i in 0..n_out_jacks { + endpoint_data[2 + i as usize] = in_jack_id_emb(i); + } + let read_ep = alt.endpoint_bulk_out(max_packet_size); + alt.descriptor(CS_ENDPOINT, &endpoint_data[0..2 + n_out_jacks as usize]); + + endpoint_data[1] = n_in_jacks; + for i in 0..n_in_jacks { + endpoint_data[2 + i as usize] = out_jack_id_emb(i); + } + let write_ep = alt.endpoint_bulk_in(max_packet_size); + alt.descriptor(CS_ENDPOINT, &endpoint_data[0..2 + n_in_jacks as usize]); + + MidiClass { read_ep, write_ep } + } + + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } + + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. + pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { + ( + Sender { + write_ep: self.write_ep, + }, + Receiver { read_ep: self.read_ep }, + ) + } +} + +/// Midi class packet sender. +/// +/// You can obtain a `Sender` with [`MidiClass::split`] +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.write_ep.info().max_packet_size + } + + /// Writes a single packet. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.write_ep.wait_enabled().await; + } +} + +/// Midi class packet receiver. +/// +/// You can obtain a `Receiver` with [`MidiClass::split`] +pub struct Receiver<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Reads a single packet. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } +} diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs new file mode 100644 index 0000000..4bd89eb --- /dev/null +++ b/embassy-usb/src/class/mod.rs @@ -0,0 +1,7 @@ +//! Implementations of well-known USB classes. +pub mod cdc_acm; +pub mod cdc_ncm; +pub mod hid; +pub mod midi; +pub mod uac1; +pub mod web_usb; diff --git a/embassy-usb/src/class/uac1/class_codes.rs b/embassy-usb/src/class/uac1/class_codes.rs new file mode 100644 index 0000000..3f69567 --- /dev/null +++ b/embassy-usb/src/class/uac1/class_codes.rs @@ -0,0 +1,151 @@ +//! Audio Device Class Codes as defined in Universal Serial Bus Device Class +//! Definition for Audio Devices, Release 1.0, Appendix A and Universal Serial +//! Bus Device Class Definition for Audio Data Formats, Release 1.0, Appendix +//! A.1.1 (Audio Data Format Type I Codes) +#![allow(dead_code)] + +/// The current version of the ADC specification (1.0) +pub const ADC_VERSION: u16 = 0x0100; + +/// The current version of the USB device (1.0) +pub const DEVICE_VERSION: u16 = 0x0100; + +/// Audio Interface Class Code +pub const USB_AUDIO_CLASS: u8 = 0x01; + +// Audio Interface Subclass Codes +pub const USB_UNDEFINED_SUBCLASS: u8 = 0x00; +pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; +pub const USB_AUDIOSTREAMING_SUBCLASS: u8 = 0x02; +pub const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; + +// Audio Protocol Code +pub const PROTOCOL_NONE: u8 = 0x00; + +// Audio Class-Specific Descriptor Types +pub const CS_UNDEFINED: u8 = 0x20; +pub const CS_DEVICE: u8 = 0x21; +pub const CS_CONFIGURATION: u8 = 0x22; +pub const CS_STRING: u8 = 0x23; +pub const CS_INTERFACE: u8 = 0x24; +pub const CS_ENDPOINT: u8 = 0x25; + +// Descriptor Subtype +pub const AC_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const HEADER_SUBTYPE: u8 = 0x01; +pub const INPUT_TERMINAL: u8 = 0x02; +pub const OUTPUT_TERMINAL: u8 = 0x03; +pub const MIXER_UNIT: u8 = 0x04; +pub const SELECTOR_UNIT: u8 = 0x05; +pub const FEATURE_UNIT: u8 = 0x06; +pub const PROCESSING_UNIT: u8 = 0x07; +pub const EXTENSION_UNIT: u8 = 0x08; + +// Audio Class-Specific AS Interface Descriptor Subtypes +pub const AS_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const AS_GENERAL: u8 = 0x01; +pub const FORMAT_TYPE: u8 = 0x02; +pub const FORMAT_SPECIFIC: u8 = 0x03; + +// Processing Unit Process Types +pub const PROCESS_UNDEFINED: u16 = 0x00; +pub const UP_DOWNMIX_PROCESS: u16 = 0x01; +pub const DOLBY_PROLOGIC_PROCESS: u16 = 0x02; +pub const DDD_STEREO_EXTENDER_PROCESS: u16 = 0x03; +pub const REVERBERATION_PROCESS: u16 = 0x04; +pub const CHORUS_PROCESS: u16 = 0x05; +pub const DYN_RANGE_COMP_PROCESS: u16 = 0x06; + +// Audio Class-Specific Endpoint Descriptor Subtypes +pub const EP_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const EP_GENERAL: u8 = 0x01; + +// Audio Class-Specific Request Codes +pub const REQUEST_CODE_UNDEFINED: u8 = 0x00; +pub const SET_CUR: u8 = 0x01; +pub const GET_CUR: u8 = 0x81; +pub const SET_MIN: u8 = 0x02; +pub const GET_MIN: u8 = 0x82; +pub const SET_MAX: u8 = 0x03; +pub const GET_MAX: u8 = 0x83; +pub const SET_RES: u8 = 0x04; +pub const GET_RES: u8 = 0x84; +pub const SET_MEM: u8 = 0x05; +pub const GET_MEM: u8 = 0x85; +pub const GET_STAT: u8 = 0xFF; + +// Terminal Control Selectors +pub const TE_CONTROL_UNDEFINED: u8 = 0x00; +pub const COPY_PROTECT_CONTROL: u8 = 0x01; + +// Feature Unit Control Selectors +pub const FU_CONTROL_UNDEFINED: u8 = 0x00; +pub const MUTE_CONTROL: u8 = 0x01; +pub const VOLUME_CONTROL: u8 = 0x02; +pub const BASS_CONTROL: u8 = 0x03; +pub const MID_CONTROL: u8 = 0x04; +pub const TREBLE_CONTROL: u8 = 0x05; +pub const GRAPHIC_EQUALIZER_CONTROL: u8 = 0x06; +pub const AUTOMATIC_GAIN_CONTROL: u8 = 0x07; +pub const DELAY_CONTROL: u8 = 0x08; +pub const BASS_BOOST_CONTROL: u8 = 0x09; +pub const LOUDNESS_CONTROL: u8 = 0x0A; + +// Up/Down-mix Processing Unit Control Selectors +pub const UD_CONTROL_UNDEFINED: u8 = 0x00; +pub const UD_ENABLE_CONTROL: u8 = 0x01; +pub const UD_MODE_SELECT_CONTROL: u8 = 0x02; + +// Dolby Prologic Processing Unit Control Selectors +pub const DP_CONTROL_UNDEFINED: u8 = 0x00; +pub const DP_ENABLE_CONTROL: u8 = 0x01; +pub const DP_MODE_SELECT_CONTROL: u8 = 0x2; + +// 3D Stereo Extender Processing Unit Control Selectors +pub const DDD_CONTROL_UNDEFINED: u8 = 0x00; +pub const DDD_ENABLE_CONTROL: u8 = 0x01; +pub const DDD_SPACIOUSNESS_CONTROL: u8 = 0x03; + +// Reverberation Processing Unit Control Selectors +pub const RV_CONTROL_UNDEFINED: u8 = 0x00; +pub const RV_ENABLE_CONTROL: u8 = 0x01; +pub const REVERB_LEVEL_CONTROL: u8 = 0x02; +pub const REVERB_TIME_CONTROL: u8 = 0x03; +pub const REVERB_FEEDBACK_CONTROL: u8 = 0x04; + +// Chorus Processing Unit Control Selectors +pub const CH_CONTROL_UNDEFINED: u8 = 0x00; +pub const CH_ENABLE_CONTROL: u8 = 0x01; +pub const CHORUS_LEVEL_CONTROL: u8 = 0x02; +pub const CHORUS_RATE_CONTROL: u8 = 0x03; +pub const CHORUS_DEPTH_CONTROL: u8 = 0x04; + +// Dynamic Range Compressor Processing Unit Control Selectors +pub const DR_CONTROL_UNDEFINED: u8 = 0x00; +pub const DR_ENABLE_CONTROL: u8 = 0x01; +pub const COMPRESSION_RATE_CONTROL: u8 = 0x02; +pub const MAXAMPL_CONTROL: u8 = 0x03; +pub const THRESHOLD_CONTROL: u8 = 0x04; +pub const ATTACK_TIME: u8 = 0x05; +pub const RELEASE_TIME: u8 = 0x06; + +// Extension Unit Control Selectors +pub const XU_CONTROL_UNDEFINED: u16 = 0x00; +pub const XU_ENABLE_CONTROL: u16 = 0x01; + +// Endpoint Control Selectors +pub const EP_CONTROL_UNDEFINED: u8 = 0x00; +pub const SAMPLING_FREQ_CONTROL: u8 = 0x01; +pub const PITCH_CONTROL: u8 = 0x02; + +// Format Type Codes +pub const FORMAT_TYPE_UNDEFINED: u8 = 0x00; +pub const FORMAT_TYPE_I: u8 = 0x01; + +// Audio Data Format Type I Codes +pub const TYPE_I_UNDEFINED: u16 = 0x0000; +pub const PCM: u16 = 0x0001; +pub const PCM8: u16 = 0x0002; +pub const IEEE_FLOAT: u16 = 0x0003; +pub const ALAW: u16 = 0x0004; +pub const MULAW: u16 = 0x0005; diff --git a/embassy-usb/src/class/uac1/mod.rs b/embassy-usb/src/class/uac1/mod.rs new file mode 100644 index 0000000..3d5f4e5 --- /dev/null +++ b/embassy-usb/src/class/uac1/mod.rs @@ -0,0 +1,134 @@ +//! USB Audio Class 1.0 implementations for different applications. +//! +//! Contains: +//! - The `speaker` class with a single audio streaming interface (host to device) + +pub mod speaker; + +mod class_codes; +mod terminal_type; + +/// The maximum supported audio channel index (corresponds to `Top`). +/// FIXME: Use `core::mem::variant_count(...)` when stabilized. +const MAX_AUDIO_CHANNEL_INDEX: usize = 12; + +/// The maximum number of supported audio channels. +/// +/// Includes all twelve channels from `Channel`, plus the Master channel. +const MAX_AUDIO_CHANNEL_COUNT: usize = MAX_AUDIO_CHANNEL_INDEX + 1; + +/// USB Audio Channel +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Channel { + LeftFront, + RightFront, + CenterFront, + Lfe, + LeftSurround, + RightSurround, + LeftOfCenter, + RightOfCenter, + Surround, + SideLeft, + SideRight, + Top, +} + +impl Channel { + /// Map a `Channel` to its corresponding USB Audio `ChannelConfig`. + fn get_channel_config(&self) -> ChannelConfig { + match self { + Channel::LeftFront => ChannelConfig::LeftFront, + Channel::RightFront => ChannelConfig::RightFront, + Channel::CenterFront => ChannelConfig::CenterFront, + Channel::Lfe => ChannelConfig::Lfe, + Channel::LeftSurround => ChannelConfig::LeftSurround, + Channel::RightSurround => ChannelConfig::RightSurround, + Channel::LeftOfCenter => ChannelConfig::LeftOfCenter, + Channel::RightOfCenter => ChannelConfig::RightOfCenter, + Channel::Surround => ChannelConfig::Surround, + Channel::SideLeft => ChannelConfig::SideLeft, + Channel::SideRight => ChannelConfig::SideRight, + Channel::Top => ChannelConfig::Top, + } + } +} + +/// USB Audio Channel configuration +#[repr(u16)] +#[non_exhaustive] +// #[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum ChannelConfig { + None = 0x0000, + LeftFront = 0x0001, + RightFront = 0x0002, + CenterFront = 0x0004, + Lfe = 0x0008, + LeftSurround = 0x0010, + RightSurround = 0x0020, + LeftOfCenter = 0x0040, + RightOfCenter = 0x0080, + Surround = 0x0100, + SideLeft = 0x0200, + SideRight = 0x0400, + Top = 0x0800, +} + +impl From for u16 { + fn from(t: ChannelConfig) -> u16 { + t as u16 + } +} + +/// Feedback period adjustment `bRefresh` [UAC 3.7.2.2] +/// +/// From the specification: "A new Ff value is available every 2^(10 – P) frames with P ranging from 1 to 9. The +/// bRefresh field of the synch standard endpoint descriptor is used to report the exponent (10-P) to the Host." +/// +/// This means: +/// - 512 ms (2^9 frames) to 2 ms (2^1 frames) for USB full-speed +/// - 64 ms (2^9 microframes) to 0.25 ms (2^1 microframes) for USB high-speed +#[repr(u8)] +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum FeedbackRefresh { + Period2Frames = 1, + Period4Frames = 2, + Period8Frames = 3, + Period16Frames = 4, + Period32Frames = 5, + Period64Frames = 6, + Period128Frames = 7, + Period256Frames = 8, + Period512Frames = 9, +} + +impl FeedbackRefresh { + /// Gets the number of frames, after which a new feedback frame is returned. + pub const fn frame_count(&self) -> usize { + 1 << (*self as usize) + } +} + +/// Audio sample width. +/// +/// Stored in number of bytes per sample. +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum SampleWidth { + /// 16 bit audio + Width2Byte = 2, + /// 24 bit audio + Width3Byte = 3, + /// 32 bit audio + Width4Byte = 4, +} + +impl SampleWidth { + /// Get the audio sample resolution in number of bit. + pub const fn in_bit(self) -> usize { + 8 * self as usize + } +} diff --git a/embassy-usb/src/class/uac1/speaker.rs b/embassy-usb/src/class/uac1/speaker.rs new file mode 100644 index 0000000..25de25d --- /dev/null +++ b/embassy-usb/src/class/uac1/speaker.rs @@ -0,0 +1,777 @@ +//! USB Audio Class 1.0 - Speaker device +//! +//! Provides a class with a single audio streaming interface (host to device), +//! that advertises itself as a speaker. Includes explicit sample rate feedback. +//! +//! Various aspects of the audio stream can be configured, for example: +//! - sample rate +//! - sample resolution +//! - audio channel count and assignment +//! +//! The class provides volume and mute controls for each channel. + +use core::cell::{Cell, RefCell}; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use core::task::Poll; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; +use heapless::Vec; + +use super::class_codes::*; +use super::terminal_type::TerminalType; +use super::{Channel, ChannelConfig, FeedbackRefresh, SampleWidth, MAX_AUDIO_CHANNEL_COUNT, MAX_AUDIO_CHANNEL_INDEX}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::descriptor::{SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut, EndpointType}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +/// Maximum allowed sampling rate (3 bytes) in Hz. +const MAX_SAMPLE_RATE_HZ: u32 = 0x7FFFFF; + +/// Arbitrary unique identifier for the input unit. +const INPUT_UNIT_ID: u8 = 0x01; + +/// Arbitrary unique identifier for the feature unit. +const FEATURE_UNIT_ID: u8 = 0x02; + +/// Arbitrary unique identifier for the output unit. +const OUTPUT_UNIT_ID: u8 = 0x03; + +// Volume settings go from -25600 to 0, in steps of 256. +// Therefore, the volume settings are 8q8 values in units of dB. +const VOLUME_STEPS_PER_DB: i16 = 256; +const MIN_VOLUME_DB: i16 = -100; +const MAX_VOLUME_DB: i16 = 0; + +// Maximum number of supported discrete sample rates. +const MAX_SAMPLE_RATE_COUNT: usize = 10; + +/// The volume of an audio channel. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Volume { + /// The channel is muted. + Muted, + /// The channel volume in dB. Ranges from `MIN_VOLUME_DB` (quietest) to `MAX_VOLUME_DB` (loudest). + DeciBel(f32), +} + +/// Internal state for the USB Audio Class. +pub struct State<'d> { + control: Option>, + shared: SharedControl<'d>, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: None, + shared: SharedControl::default(), + } + } +} + +/// Implementation of the USB audio class 1.0. +pub struct Speaker<'d, D: Driver<'d>> { + phantom: PhantomData<&'d D>, +} + +impl<'d, D: Driver<'d>> Speaker<'d, D> { + /// Creates a new [`Speaker`] device, split into a stream, feedback, and a control change notifier. + /// + /// The packet size should be chosen, based on the expected transfer size of samples per (micro)frame. + /// For example, a stereo stream at 32 bit resolution and 48 kHz sample rate yields packets of 384 byte for + /// full-speed USB (1 ms frame interval) or 48 byte for high-speed USB (125 us microframe interval). + /// When using feedback, the packet size varies and thus, the `max_packet_size` should be increased (e.g. to double). + /// + /// # Arguments + /// + /// * `builder` - The builder for the class. + /// * `state` - The internal state of the class. + /// * `max_packet_size` - The maximum packet size per (micro)frame. + /// * `resolution` - The audio sample resolution. + /// * `sample_rates_hz` - The supported sample rates in Hz. + /// * `channels` - The advertised audio channels (up to 12). Entries must be unique, or this function panics. + /// * `feedback_refresh_period` - The refresh period for the feedback value. + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + max_packet_size: u16, + resolution: SampleWidth, + sample_rates_hz: &[u32], + channels: &'d [Channel], + feedback_refresh_period: FeedbackRefresh, + ) -> (Stream<'d, D>, Feedback<'d, D>, ControlMonitor<'d>) { + // The class and subclass fields of the IAD aren't required to match the class and subclass fields of + // the interfaces in the interface collection that the IAD describes. Microsoft recommends that + // the first interface of the collection has class and subclass fields that match the class and + // subclass fields of the IAD. + let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); + + // Audio control interface (mandatory) [UAC 4.3.1] + let mut interface = func.interface(); + let control_interface = interface.interface_number().into(); + let streaming_interface = u8::from(control_interface) + 1; + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); + + // Terminal topology: + // Input terminal (receives audio stream) -> Feature Unit (mute and volume) -> Output terminal (e.g. towards speaker) + + // ======================================= + // Input Terminal Descriptor [UAC 3.3.2.1] + // Audio input + let terminal_type: u16 = TerminalType::UsbStreaming.into(); + + // Assemble channel configuration field + let mut channel_config: u16 = ChannelConfig::None.into(); + for channel in channels { + let channel: u16 = channel.get_channel_config().into(); + + if channel_config & channel != 0 { + panic!("Invalid channel config, duplicate channel {}.", channel); + } + channel_config |= channel; + } + + let input_terminal_descriptor = [ + INPUT_TERMINAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + channels.len() as u8, // bNrChannels + channel_config as u8, + (channel_config >> 8) as u8, // wChannelConfig + 0x00, // iChannelNames (none) + 0x00, // iTerminal (none) + ]; + + // ======================================== + // Output Terminal Descriptor [UAC 4.3.2.2] + // Speaker output + let terminal_type: u16 = TerminalType::OutSpeaker.into(); + let output_terminal_descriptor = [ + OUTPUT_TERMINAL, // bDescriptorSubtype + OUTPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + FEATURE_UNIT_ID, // bSourceID (the feature unit) + 0x00, // iTerminal (none) + ]; + + // ===================================== + // Feature Unit Descriptor [UAC 4.3.2.5] + // Mute and volume control + let controls = MUTE_CONTROL | VOLUME_CONTROL; + + const FEATURE_UNIT_DESCRIPTOR_SIZE: usize = 5; + let mut feature_unit_descriptor: Vec = + Vec::from_slice(&[ + FEATURE_UNIT, // bDescriptorSubtype (Feature Unit) + FEATURE_UNIT_ID, // bUnitID + INPUT_UNIT_ID, // bSourceID + 1, // bControlSize (one byte per control) + FU_CONTROL_UNDEFINED, // Master controls (disabled, use only per-channel control) + ]) + .unwrap(); + + // Add per-channel controls + for _channel in channels { + feature_unit_descriptor.push(controls).unwrap(); + } + feature_unit_descriptor.push(0x00).unwrap(); // iFeature (none) + + // =============================================== + // Format desciptor [UAC 4.5.3] + // Used later, for operational streaming interface + let mut format_descriptor: Vec = Vec::from_slice(&[ + FORMAT_TYPE, // bDescriptorSubtype + FORMAT_TYPE_I, // bFormatType + channels.len() as u8, // bNrChannels + resolution as u8, // bSubframeSize + resolution.in_bit() as u8, // bBitResolution + ]) + .unwrap(); + + format_descriptor.push(sample_rates_hz.len() as u8).unwrap(); + + for sample_rate_hz in sample_rates_hz { + assert!(*sample_rate_hz <= MAX_SAMPLE_RATE_HZ); + format_descriptor.push((sample_rate_hz & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 8) & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 16) & 0xFF) as u8).unwrap(); + } + + // ================================================== + // Class-specific AC Interface Descriptor [UAC 4.3.2] + const DESCRIPTOR_HEADER_SIZE: usize = 2; + const INTERFACE_DESCRIPTOR_SIZE: usize = 7; + + let mut total_descriptor_length = 0; + + for size in [ + INTERFACE_DESCRIPTOR_SIZE, + input_terminal_descriptor.len(), + feature_unit_descriptor.len(), + output_terminal_descriptor.len(), + ] { + total_descriptor_length += size + DESCRIPTOR_HEADER_SIZE; + } + + let interface_descriptor: [u8; INTERFACE_DESCRIPTOR_SIZE] = [ + HEADER_SUBTYPE, // bDescriptorSubtype (Header) + ADC_VERSION as u8, + (ADC_VERSION >> 8) as u8, // bcdADC + total_descriptor_length as u8, + (total_descriptor_length >> 8) as u8, // wTotalLength + 0x01, // bInCollection (1 streaming interface) + streaming_interface, // baInterfaceNr + ]; + + alt.descriptor(CS_INTERFACE, &interface_descriptor); + alt.descriptor(CS_INTERFACE, &input_terminal_descriptor); + alt.descriptor(CS_INTERFACE, &feature_unit_descriptor); + alt.descriptor(CS_INTERFACE, &output_terminal_descriptor); + + // ===================================================== + // Audio streaming interface, zero-bandwidth [UAC 4.5.1] + let mut interface = func.interface(); + let alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + drop(alt); + + // ================================================== + // Audio streaming interface, operational [UAC 4.5.1] + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + AS_GENERAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalLink + 0x00, // bDelay (none) + PCM as u8, + (PCM >> 8) as u8, // wFormatTag (PCM format) + ], + ); + + alt.descriptor(CS_INTERFACE, &format_descriptor); + + let streaming_endpoint = alt.alloc_endpoint_out(EndpointType::Isochronous, max_packet_size, 1); + let feedback_endpoint = alt.alloc_endpoint_in( + EndpointType::Isochronous, + 4, // Feedback packets are 24 bit (10.14 format). + 1, + ); + + // Write the descriptor for the streaming endpoint, after knowing the address of the feedback endpoint. + alt.endpoint_descriptor( + streaming_endpoint.info(), + SynchronizationType::Asynchronous, + UsageType::DataEndpoint, + &[ + 0x00, // bRefresh (0) + feedback_endpoint.info().addr.into(), // bSynchAddress (the feedback endpoint) + ], + ); + + alt.descriptor( + CS_ENDPOINT, + &[ + AS_GENERAL, // bDescriptorSubtype (General) + SAMPLING_FREQ_CONTROL, // bmAttributes (support sampling frequency control) + 0x02, // bLockDelayUnits (PCM) + 0x0000 as u8, + (0x0000 >> 8) as u8, // wLockDelay (0) + ], + ); + + // Write the feedback endpoint descriptor after the streaming endpoint descriptor + // This is demanded by the USB audio class specification. + alt.endpoint_descriptor( + feedback_endpoint.info(), + SynchronizationType::NoSynchronization, + UsageType::FeedbackEndpoint, + &[ + feedback_refresh_period as u8, // bRefresh + 0x00, // bSynchAddress (none) + ], + ); + + // Free up the builder. + drop(func); + + // Store channel information + state.shared.channels = channels; + + state.control = Some(Control { + shared: &state.shared, + streaming_endpoint_address: streaming_endpoint.info().addr.into(), + control_interface_number: control_interface, + }); + + builder.handler(state.control.as_mut().unwrap()); + + let control = &state.shared; + + ( + Stream { streaming_endpoint }, + Feedback { feedback_endpoint }, + ControlMonitor { shared: control }, + ) + } +} + +/// Audio settings for the feature unit. +/// +/// Contains volume and mute control. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AudioSettings { + /// Channel mute states. + muted: [bool; MAX_AUDIO_CHANNEL_COUNT], + /// Channel volume levels in 8.8 format (in dB). + volume_8q8_db: [i16; MAX_AUDIO_CHANNEL_COUNT], +} + +impl Default for AudioSettings { + fn default() -> Self { + AudioSettings { + muted: [true; MAX_AUDIO_CHANNEL_COUNT], + volume_8q8_db: [MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; MAX_AUDIO_CHANNEL_COUNT], + } + } +} + +struct Control<'d> { + control_interface_number: InterfaceNumber, + streaming_endpoint_address: u8, + shared: &'d SharedControl<'d>, +} + +/// Shared data between [`Control`] and the [`Speaker`] class. +struct SharedControl<'d> { + /// The collection of audio settings (volumes, mute states). + audio_settings: CriticalSectionMutex>, + + /// Channel assignments. + channels: &'d [Channel], + + /// The audio sample rate in Hz. + sample_rate_hz: AtomicU32, + + // Notification mechanism. + waker: RefCell, + changed: AtomicBool, +} + +impl<'d> Default for SharedControl<'d> { + fn default() -> Self { + SharedControl { + audio_settings: CriticalSectionMutex::new(Cell::new(AudioSettings::default())), + channels: &[], + sample_rate_hz: AtomicU32::new(0), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), + } + } +} + +impl<'d> SharedControl<'d> { + fn changed(&self) -> impl Future + '_ { + poll_fn(|context| { + if self.changed.load(Ordering::Relaxed) { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } else { + self.waker.borrow_mut().register(context.waker()); + Poll::Pending + } + }) + } +} + +/// Used for reading audio frames. +pub struct Stream<'d, D: Driver<'d>> { + streaming_endpoint: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Stream<'d, D> { + /// Reads a single packet from the OUT endpoint + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.streaming_endpoint.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.streaming_endpoint.wait_enabled().await; + } +} + +/// Used for writing sample rate information over the feedback endpoint. +pub struct Feedback<'d, D: Driver<'d>> { + feedback_endpoint: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> Feedback<'d, D> { + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.feedback_endpoint.write(data).await + } + + /// Waits for the USB host to enable this interface. + pub async fn wait_connection(&mut self) { + self.feedback_endpoint.wait_enabled().await; + } +} + +/// Control status change monitor +/// +/// Await [`ControlMonitor::changed`] for being notified of configuration changes. Afterwards, the updated +/// configuration settings can be read with [`ControlMonitor::volume`] and [`ControlMonitor::sample_rate_hz`]. +pub struct ControlMonitor<'d> { + shared: &'d SharedControl<'d>, +} + +impl<'d> ControlMonitor<'d> { + fn audio_settings(&self) -> AudioSettings { + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + audio_settings + } + + fn get_logical_channel(&self, search_channel: Channel) -> Option { + let index = self.shared.channels.iter().position(|&c| c == search_channel)?; + + // The logical channels start at one (zero is the master channel). + Some(index + 1) + } + + /// Get the volume of a selected channel. + pub fn volume(&self, channel: Channel) -> Option { + let channel_index = self.get_logical_channel(channel)?; + + if self.audio_settings().muted[channel_index] { + return Some(Volume::Muted); + } + + Some(Volume::DeciBel( + (self.audio_settings().volume_8q8_db[channel_index] as f32) / 256.0f32, + )) + } + + /// Get the streaming endpoint's sample rate in Hz. + pub fn sample_rate_hz(&self) -> u32 { + self.shared.sample_rate_hz.load(Ordering::Relaxed) + } + + /// Return a future for when the control settings change. + pub async fn changed(&self) { + self.shared.changed().await; + } +} + +impl<'d> Control<'d> { + fn changed(&mut self) { + self.shared.changed.store(true, Ordering::Relaxed); + self.shared.waker.borrow_mut().wake(); + } + + fn interface_set_mute_state( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let mute_state = data[0] != 0; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.muted[channel_index as usize] = mute_state; + } + _ => { + debug!("Failed to set channel {} mute state: {}", channel_index, mute_state); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} mute state: {}", channel_index, mute_state); + OutResponse::Accepted + } + + fn interface_set_volume( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let volume = i16::from_ne_bytes(data[..2].try_into().expect("Failed to read volume.")); + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.volume_8q8_db[channel_index as usize] = volume; + } + _ => { + debug!("Failed to set channel {} volume: {}", channel_index, volume); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} volume: {}", channel_index, volume); + OutResponse::Accepted + } + + fn interface_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface set request for interface {}", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + debug!("Unsupported interface set request for entity {}", entity_index); + return Some(OutResponse::Rejected); + } + + if req.request != SET_CUR { + debug!("Unsupported interface set request type {}", req.request); + return Some(OutResponse::Rejected); + } + + let mut audio_settings = self.shared.audio_settings.lock(|x| x.get()); + let response = match control_unit { + MUTE_CONTROL => self.interface_set_mute_state(&mut audio_settings, channel_index, data), + VOLUME_CONTROL => self.interface_set_volume(&mut audio_settings, channel_index, data), + _ => OutResponse::Rejected, + }; + + if response == OutResponse::Rejected { + return Some(response); + } + + // Store updated settings + self.shared.audio_settings.lock(|x| x.set(audio_settings)); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn endpoint_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!( + "Unhandled endpoint set request for endpoint {} and control {} with data {:?}", + endpoint_address, control_selector, data + ); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL { + debug!( + "Unsupported endpoint set request for control selector {}", + control_selector + ); + return Some(OutResponse::Rejected); + } + + let sample_rate_hz: u32 = (data[0] as u32) | (data[1] as u32) << 8 | (data[2] as u32) << 16; + self.shared.sample_rate_hz.store(sample_rate_hz, Ordering::Relaxed); + + debug!("Set endpoint {} sample rate to {} Hz", endpoint_address, sample_rate_hz); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn interface_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface get request for interface {}.", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + // Only this function unit can be handled at the moment. + debug!("Unsupported interface get request for entity {}.", entity_index); + return Some(InResponse::Rejected); + } + + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + match req.request { + GET_CUR => match control_unit { + VOLUME_CONTROL => { + let volume: i16; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => volume = audio_settings.volume_8q8_db[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = volume as u8; + buf[1] = (volume >> 8) as u8; + + debug!("Got channel {} volume: {}.", channel_index, volume); + return Some(InResponse::Accepted(&buf[..2])); + } + MUTE_CONTROL => { + let mute_state: bool; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => mute_state = audio_settings.muted[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = mute_state.into(); + debug!("Got channel {} mute state: {}.", channel_index, mute_state); + return Some(InResponse::Accepted(&buf[..1])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MIN => match control_unit { + VOLUME_CONTROL => { + let min_volume = MIN_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = min_volume as u8; + buf[1] = (min_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MAX => match control_unit { + VOLUME_CONTROL => { + let max_volume = MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = max_volume as u8; + buf[1] = (max_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_RES => match control_unit { + VOLUME_CONTROL => { + buf[0] = VOLUME_STEPS_PER_DB as u8; + buf[1] = (VOLUME_STEPS_PER_DB >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + _ => return Some(InResponse::Rejected), + } + } + + fn endpoint_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!("Unhandled endpoint get request for endpoint {}.", endpoint_address); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL as u8 { + debug!( + "Unsupported endpoint get request for control selector {}.", + control_selector + ); + return Some(InResponse::Rejected); + } + + let sample_rate_hz = self.shared.sample_rate_hz.load(Ordering::Relaxed); + + buf[0] = (sample_rate_hz & 0xFF) as u8; + buf[1] = ((sample_rate_hz >> 8) & 0xFF) as u8; + buf[2] = ((sample_rate_hz >> 16) & 0xFF) as u8; + + Some(InResponse::Accepted(&buf[..3])) + } +} + +impl<'d> Handler for Control<'d> { + /// Called when the USB device has been enabled or disabled. + fn enabled(&mut self, enabled: bool) { + debug!("USB device enabled: {}", enabled); + } + + /// Called when the host has set the address of the device to `addr`. + fn addressed(&mut self, addr: u8) { + debug!("Host set address to: {}", addr); + } + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&mut self, configured: bool) { + debug!("USB device configured: {}", configured); + } + + /// Called when remote wakeup feature is enabled or disabled. + fn remote_wakeup_enabled(&mut self, enabled: bool) { + debug!("USB remote wakeup enabled: {}", enabled); + } + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + debug!( + "USB set interface number {} to alt setting {}.", + iface, alternate_setting + ); + } + + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) { + let shared = self.shared; + shared.audio_settings.lock(|x| x.set(AudioSettings::default())); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + } + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&mut self, suspended: bool) { + debug!("USB device suspended: {}", suspended); + } + + // Handle control set requests. + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_set_request(req, data), + Recipient::Endpoint => self.endpoint_set_request(req, data), + _ => Some(OutResponse::Rejected), + }, + _ => None, + } + } + + // Handle control get requests. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_get_request(req, buf), + Recipient::Endpoint => self.endpoint_get_request(req, buf), + _ => None, + }, + _ => None, + } + } +} diff --git a/embassy-usb/src/class/uac1/terminal_type.rs b/embassy-usb/src/class/uac1/terminal_type.rs new file mode 100644 index 0000000..65474a7 --- /dev/null +++ b/embassy-usb/src/class/uac1/terminal_type.rs @@ -0,0 +1,50 @@ +//! USB Audio Terminal Types from Universal Serial Bus Device Class Definition +//! for Terminal Types, Release 1.0 + +/// USB Audio Terminal Types from "Universal Serial Bus Device Class Definition +/// for Terminal Types, Release 1.0" +#[repr(u16)] +#[non_exhaustive] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum TerminalType { + // USB Terminal Types + UsbUndefined = 0x0100, + UsbStreaming = 0x0101, + UsbVendor = 0x01ff, + + // Input Terminal Types + InUndefined = 0x0200, + InMicrophone = 0x0201, + InDesktopMicrophone = 0x0202, + InPersonalMicrophone = 0x0203, + InOmniDirectionalMicrophone = 0x0204, + InMicrophoneArray = 0x0205, + InProcessingMicrophoneArray = 0x0206, + + // Output Terminal Types + OutUndefined = 0x0300, + OutSpeaker = 0x0301, + OutHeadphones = 0x0302, + OutHeadMountedDisplayAudio = 0x0303, + OutDesktopSpeaker = 0x0304, + OutRoomSpeaker = 0x0305, + OutCommunicationSpeaker = 0x0306, + OutLowFrequencyEffectsSpeaker = 0x0307, + + // External Terminal Types + ExtUndefined = 0x0600, + ExtAnalogConnector = 0x0601, + ExtDigitalAudioInterface = 0x0602, + ExtLineConnector = 0x0603, + ExtLegacyAudioConnector = 0x0604, + ExtSpdifConnector = 0x0605, + Ext1394DaStream = 0x0606, + Ext1394DvStreamSoundtrack = 0x0607, +} + +impl From for u16 { + fn from(t: TerminalType) -> u16 { + t as u16 + } +} diff --git a/embassy-usb/src/class/web_usb.rs b/embassy-usb/src/class/web_usb.rs new file mode 100644 index 0000000..405944f --- /dev/null +++ b/embassy-usb/src/class/web_usb.rs @@ -0,0 +1,186 @@ +//! WebUSB API capability implementation. +//! +//! See https://wicg.github.io/webusb + +use core::mem::MaybeUninit; + +use crate::control::{InResponse, Recipient, Request, RequestType}; +use crate::descriptor::capability_type; +use crate::driver::Driver; +use crate::{Builder, Handler}; + +const USB_CLASS_VENDOR: u8 = 0xff; +const USB_SUBCLASS_NONE: u8 = 0x00; +const USB_PROTOCOL_NONE: u8 = 0x00; + +const WEB_USB_REQUEST_GET_URL: u16 = 0x02; +const WEB_USB_DESCRIPTOR_TYPE_URL: u8 = 0x03; + +/// URL descriptor for WebUSB landing page. +/// +/// An ecoded URL descriptor to point to a website that is suggested to the user when the device is connected. +pub struct Url<'d>(&'d str, u8); + +impl<'d> Url<'d> { + /// Create a new WebUSB URL descriptor. + pub fn new(url: &'d str) -> Self { + let (prefix, stripped_url) = if let Some(stripped) = url.strip_prefix("https://") { + (1, stripped) + } else if let Some(stripped) = url.strip_prefix("http://") { + (0, stripped) + } else { + (255, url) + }; + assert!( + stripped_url.len() <= 252, + "URL too long. ({} bytes). Maximum length is 252 bytes.", + stripped_url.len() + ); + Self(stripped_url, prefix) + } + + fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + + fn scheme(&self) -> u8 { + self.1 + } +} + +/// Configuration for WebUSB. +pub struct Config<'d> { + /// Maximum packet size in bytes for the data endpoints. + /// + /// Valid values depend on the speed at which the bus is enumerated. + /// - low speed: 8 + /// - full speed: 8, 16, 32, or 64 + /// - high speed: 64 + pub max_packet_size: u16, + /// URL to navigate to when the device is connected. + /// + /// If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. + pub landing_url: Option>, + /// Vendor code for the WebUSB request. + /// + /// This value defines the request id (bRequest) the device expects the host to use when issuing control transfers these requests. This can be an arbitrary u8 and is not to be confused with the USB Vendor ID. + pub vendor_code: u8, +} + +struct Control<'d> { + ep_buf: [u8; 128], + vendor_code: u8, + landing_url: Option<&'d Url<'d>>, +} + +impl<'d> Control<'d> { + fn new(config: &'d Config<'d>) -> Self { + Control { + ep_buf: [0u8; 128], + vendor_code: config.vendor_code, + landing_url: config.landing_url.as_ref(), + } + } +} + +impl<'d> Handler for Control<'d> { + fn control_in(&mut self, req: Request, _data: &mut [u8]) -> Option { + let landing_value = if self.landing_url.is_some() { 1 } else { 0 }; + if req.request_type == RequestType::Vendor + && req.recipient == Recipient::Device + && req.request == self.vendor_code + && req.value == landing_value + && req.index == WEB_USB_REQUEST_GET_URL + { + if let Some(url) = self.landing_url { + let url_bytes = url.as_bytes(); + let len = url_bytes.len(); + + self.ep_buf[0] = len as u8 + 3; + self.ep_buf[1] = WEB_USB_DESCRIPTOR_TYPE_URL; + self.ep_buf[2] = url.scheme(); + self.ep_buf[3..3 + len].copy_from_slice(url_bytes); + + return Some(InResponse::Accepted(&self.ep_buf[..3 + len])); + } + } + None + } +} + +/// Internal state for WebUSB +pub struct State<'d> { + control: MaybeUninit>, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub const fn new() -> Self { + State { + control: MaybeUninit::uninit(), + } + } +} + +/// WebUSB capability implementation. +/// +/// WebUSB is a W3C standard that allows a web page to communicate with USB devices. +/// See See https://wicg.github.io/webusb for more information and the browser API. +/// This implementation provides one read and one write endpoint. +pub struct WebUsb<'d, D: Driver<'d>> { + _driver: core::marker::PhantomData<&'d D>, +} + +impl<'d, D: Driver<'d>> WebUsb<'d, D> { + /// Builder for the WebUSB capability implementation. + /// + /// Pass in a USB `Builder`, a `State`, which holds the control endpoint state, and a `Config` for the WebUSB configuration. + pub fn configure(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: &'d Config<'d>) { + let mut func = builder.function(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let mut iface = func.interface(); + let mut alt = iface.alt_setting(USB_CLASS_VENDOR, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); + + alt.bos_capability( + capability_type::PLATFORM, + &[ + // PlatformCapabilityUUID (3408b638-09a9-47a0-8bfd-a0768815b665) + 0x0, + 0x38, + 0xb6, + 0x08, + 0x34, + 0xa9, + 0x09, + 0xa0, + 0x47, + 0x8b, + 0xfd, + 0xa0, + 0x76, + 0x88, + 0x15, + 0xb6, + 0x65, + // bcdVersion of WebUSB (1.0) + 0x00, + 0x01, + // bVendorCode + config.vendor_code, + // iLandingPage + if config.landing_url.is_some() { 1 } else { 0 }, + ], + ); + + let control = state.control.write(Control::new(config)); + + drop(func); + + builder.handler(control); + } +} diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs new file mode 100644 index 0000000..79f7363 --- /dev/null +++ b/embassy-usb/src/control.rs @@ -0,0 +1,146 @@ +//! USB control data types. +use core::mem; + +use crate::driver::Direction; + +/// Control request type. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RequestType { + /// Request is a USB standard request. Usually handled by + /// [`UsbDevice`](crate::UsbDevice). + Standard = 0, + /// Request is intended for a USB class. + Class = 1, + /// Request is vendor-specific. + Vendor = 2, + /// Reserved. + Reserved = 3, +} + +/// Control request recipient. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Recipient { + /// Request is intended for the entire device. + Device = 0, + /// Request is intended for an interface. Generally, the `index` field of the request specifies + /// the interface number. + Interface = 1, + /// Request is intended for an endpoint. Generally, the `index` field of the request specifies + /// the endpoint address. + Endpoint = 2, + /// None of the above. + Other = 3, + /// Reserved. + Reserved = 4, +} + +/// A control request read from a SETUP packet. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Request { + /// Direction of the request. + pub direction: Direction, + /// Type of the request. + pub request_type: RequestType, + /// Recipient of the request. + pub recipient: Recipient, + /// Request code. The meaning of the value depends on the previous fields. + pub request: u8, + /// Request value. The meaning of the value depends on the previous fields. + pub value: u16, + /// Request index. The meaning of the value depends on the previous fields. + pub index: u16, + /// Length of the DATA stage. For control OUT transfers this is the exact length of the data the + /// host sent. For control IN transfers this is the maximum length of data the device should + /// return. + pub length: u16, +} + +impl Request { + /// Standard USB control request Get Status + pub const GET_STATUS: u8 = 0; + + /// Standard USB control request Clear Feature + pub const CLEAR_FEATURE: u8 = 1; + + /// Standard USB control request Set Feature + pub const SET_FEATURE: u8 = 3; + + /// Standard USB control request Set Address + pub const SET_ADDRESS: u8 = 5; + + /// Standard USB control request Get Descriptor + pub const GET_DESCRIPTOR: u8 = 6; + + /// Standard USB control request Set Descriptor + pub const SET_DESCRIPTOR: u8 = 7; + + /// Standard USB control request Get Configuration + pub const GET_CONFIGURATION: u8 = 8; + + /// Standard USB control request Set Configuration + pub const SET_CONFIGURATION: u8 = 9; + + /// Standard USB control request Get Interface + pub const GET_INTERFACE: u8 = 10; + + /// Standard USB control request Set Interface + pub const SET_INTERFACE: u8 = 11; + + /// Standard USB control request Synch Frame + pub const SYNCH_FRAME: u8 = 12; + + /// Standard USB feature Endpoint Halt for Set/Clear Feature + pub const FEATURE_ENDPOINT_HALT: u16 = 0; + + /// Standard USB feature Device Remote Wakeup for Set/Clear Feature + pub const FEATURE_DEVICE_REMOTE_WAKEUP: u16 = 1; + + /// Parses a USB control request from a byte array. + pub fn parse(buf: &[u8; 8]) -> Request { + let rt = buf[0]; + let recipient = rt & 0b11111; + + Request { + direction: if rt & 0x80 == 0 { Direction::Out } else { Direction::In }, + request_type: unsafe { mem::transmute((rt >> 5) & 0b11) }, + recipient: if recipient <= 3 { + unsafe { mem::transmute(recipient) } + } else { + Recipient::Reserved + }, + request: buf[1], + value: (buf[2] as u16) | ((buf[3] as u16) << 8), + index: (buf[4] as u16) | ((buf[5] as u16) << 8), + length: (buf[6] as u16) | ((buf[7] as u16) << 8), + } + } + + /// Gets the descriptor type and index from the value field of a GET_DESCRIPTOR request. + pub const fn descriptor_type_index(&self) -> (u8, u8) { + ((self.value >> 8) as u8, self.value as u8) + } +} + +/// Response for a CONTROL OUT request. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OutResponse { + /// The request was accepted. + Accepted, + /// The request was rejected. + Rejected, +} + +/// Response for a CONTROL IN request. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum InResponse<'a> { + /// The request was accepted. The buffer contains the response data. + Accepted(&'a [u8]), + /// The request was rejected. + Rejected, +} diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs new file mode 100644 index 0000000..e9a6fd7 --- /dev/null +++ b/embassy-usb/src/descriptor.rs @@ -0,0 +1,434 @@ +//! Utilities for writing USB descriptors. +use embassy_usb_driver::EndpointType; + +use crate::builder::Config; +use crate::driver::EndpointInfo; +use crate::types::{InterfaceNumber, StringIndex}; +use crate::CONFIGURATION_VALUE; + +/// Standard descriptor types +#[allow(missing_docs)] +pub mod descriptor_type { + pub const DEVICE: u8 = 1; + pub const CONFIGURATION: u8 = 2; + pub const STRING: u8 = 3; + pub const INTERFACE: u8 = 4; + pub const ENDPOINT: u8 = 5; + pub const DEVICE_QUALIFIER: u8 = 6; + pub const OTHER_SPEED_CONFIGURATION: u8 = 7; + pub const IAD: u8 = 11; + pub const BOS: u8 = 15; + pub const CAPABILITY: u8 = 16; +} + +/// String descriptor language IDs. +pub mod lang_id { + /// English (US) + /// + /// Recommended for use as the first language ID for compatibility. + pub const ENGLISH_US: u16 = 0x0409; +} + +/// Standard capability descriptor types +#[allow(missing_docs)] +pub mod capability_type { + pub const WIRELESS_USB: u8 = 1; + pub const USB_2_0_EXTENSION: u8 = 2; + pub const SS_USB_DEVICE: u8 = 3; + pub const CONTAINER_ID: u8 = 4; + pub const PLATFORM: u8 = 5; +} + +/// USB endpoint synchronization type. The values of this enum can be directly +/// cast into `u8` to get the bmAttributes synchronization type bits. +/// Values other than `NoSynchronization` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SynchronizationType { + /// No synchronization is used. + NoSynchronization = 0b00, + /// Unsynchronized, although sinks provide data rate feedback. + Asynchronous = 0b01, + /// Synchronized using feedback or feedforward data rate information. + Adaptive = 0b10, + /// Synchronized to the USB’s SOF. + Synchronous = 0b11, +} + +/// USB endpoint usage type. The values of this enum can be directly cast into +/// `u8` to get the bmAttributes usage type bits. +/// Values other than `DataEndpoint` are only allowed on isochronous endpoints. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsageType { + /// Use the endpoint for regular data transfer. + DataEndpoint = 0b00, + /// Endpoint conveys explicit feedback information for one or more data endpoints. + FeedbackEndpoint = 0b01, + /// A data endpoint that also serves as an implicit feedback endpoint for one or more data endpoints. + ImplicitFeedbackDataEndpoint = 0b10, + /// Reserved usage type. + Reserved = 0b11, +} + +/// A writer for USB descriptors. +pub(crate) struct DescriptorWriter<'a> { + pub buf: &'a mut [u8], + position: usize, + num_interfaces_mark: Option, + num_endpoints_mark: Option, +} + +impl<'a> DescriptorWriter<'a> { + pub(crate) fn new(buf: &'a mut [u8]) -> Self { + DescriptorWriter { + buf, + position: 0, + num_interfaces_mark: None, + num_endpoints_mark: None, + } + } + + pub fn into_buf(self) -> &'a mut [u8] { + &mut self.buf[..self.position] + } + + /// Gets the current position in the buffer, i.e. the number of bytes written so far. + pub const fn position(&self) -> usize { + self.position + } + + /// Writes an arbitrary (usually class-specific) descriptor with optional extra fields. + pub fn write(&mut self, descriptor_type: u8, descriptor: &[u8], extra_fields: &[u8]) { + let descriptor_length = descriptor.len(); + let extra_fields_length = extra_fields.len(); + let total_length = descriptor_length + extra_fields_length; + + assert!( + (self.position + 2 + total_length) <= self.buf.len() && (total_length + 2) <= 255, + "Descriptor buffer full" + ); + + self.buf[self.position] = (total_length + 2) as u8; + self.buf[self.position + 1] = descriptor_type; + + let start = self.position + 2; + + self.buf[start..start + descriptor_length].copy_from_slice(descriptor); + self.buf[start + descriptor_length..start + total_length].copy_from_slice(extra_fields); + + self.position = start + total_length; + } + + pub(crate) fn configuration(&mut self, config: &Config) { + self.num_interfaces_mark = Some(self.position + 4); + + self.write( + descriptor_type::CONFIGURATION, + &[ + 0, + 0, // wTotalLength + 0, // bNumInterfaces + CONFIGURATION_VALUE, // bConfigurationValue + 0, // iConfiguration + 0x80 | if config.self_powered { 0x40 } else { 0x00 } + | if config.supports_remote_wakeup { 0x20 } else { 0x00 }, // bmAttributes + (config.max_power / 2) as u8, // bMaxPower + ], + &[], + ); + } + + #[allow(unused)] + pub(crate) fn end_class(&mut self) { + self.num_endpoints_mark = None; + } + + pub(crate) fn end_configuration(&mut self) { + let position = self.position as u16; + self.buf[2..4].copy_from_slice(&position.to_le_bytes()); + } + + /// Writes a interface association descriptor. Call from `UsbClass::get_configuration_descriptors` + /// before writing the USB class or function's interface descriptors if your class has more than + /// one interface and wants to play nicely with composite devices on Windows. If the USB device + /// hosting the class was not configured as composite with IADs enabled, calling this function + /// does nothing, so it is safe to call from libraries. + /// + /// # Arguments + /// + /// * `first_interface` - Number of the function's first interface, previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `interface_count` - Number of interfaces in the function. + /// * `function_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `function_sub_class` - Sub-class code. Depends on class. + /// * `function_protocol` - Protocol code. Depends on class and sub-class. + pub fn iad( + &mut self, + first_interface: InterfaceNumber, + interface_count: u8, + function_class: u8, + function_sub_class: u8, + function_protocol: u8, + ) { + self.write( + descriptor_type::IAD, + &[ + first_interface.into(), // bFirstInterface + interface_count, // bInterfaceCount + function_class, + function_sub_class, + function_protocol, + 0, + ], + &[], + ); + } + + /// Writes a interface descriptor with a specific alternate setting and + /// interface string identifier. + /// + /// # Arguments + /// + /// * `number` - Interface number previously allocated with + /// [`UsbDeviceBuilder::interface`](crate::bus::UsbDeviceBuilder::interface). + /// * `alternate_setting` - Number of the alternate setting + /// * `interface_class` - Class code assigned by USB.org. Use `0xff` for vendor-specific devices + /// that do not conform to any class. + /// * `interface_sub_class` - Sub-class code. Depends on class. + /// * `interface_protocol` - Protocol code. Depends on class and sub-class. + /// * `interface_string` - Index of string descriptor describing this interface + + pub fn interface_alt( + &mut self, + number: InterfaceNumber, + alternate_setting: u8, + interface_class: u8, + interface_sub_class: u8, + interface_protocol: u8, + interface_string: Option, + ) { + if alternate_setting == 0 { + match self.num_interfaces_mark { + Some(mark) => self.buf[mark] += 1, + None => { + panic!("you can only call `interface/interface_alt` after `configuration`.") + } + }; + } + + let str_index = interface_string.map_or(0, Into::into); + + self.num_endpoints_mark = Some(self.position + 4); + + self.write( + descriptor_type::INTERFACE, + &[ + number.into(), // bInterfaceNumber + alternate_setting, // bAlternateSetting + 0, // bNumEndpoints + interface_class, // bInterfaceClass + interface_sub_class, // bInterfaceSubClass + interface_protocol, // bInterfaceProtocol + str_index, // iInterface + ], + &[], + ); + } + + /// Writes an endpoint descriptor. + /// + /// # Arguments + /// + /// * `endpoint` - Endpoint previously allocated with + /// [`UsbDeviceBuilder`](crate::bus::UsbDeviceBuilder). + /// * `synchronization_type` - The synchronization type of the endpoint. + /// * `usage_type` - The usage type of the endpoint. + /// * `extra_fields` - Additional, class-specific entries at the end of the endpoint descriptor. + pub fn endpoint( + &mut self, + endpoint: &EndpointInfo, + synchronization_type: SynchronizationType, + usage_type: UsageType, + extra_fields: &[u8], + ) { + match self.num_endpoints_mark { + Some(mark) => self.buf[mark] += 1, + None => panic!("you can only call `endpoint` after `interface/interface_alt`."), + }; + + let mut bm_attributes = endpoint.ep_type as u8; + + // Synchronization types other than `NoSynchronization`, + // and usage types other than `DataEndpoint` + // are only allowed for isochronous endpoints. + if endpoint.ep_type != EndpointType::Isochronous { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization); + assert_eq!(usage_type, UsageType::DataEndpoint); + } else { + if usage_type == UsageType::FeedbackEndpoint { + assert_eq!(synchronization_type, SynchronizationType::NoSynchronization) + } + + let synchronization_bm_attibutes: u8 = (synchronization_type as u8) << 2; + let usage_bm_attibutes: u8 = (usage_type as u8) << 4; + + bm_attributes |= usage_bm_attibutes | synchronization_bm_attibutes; + } + + self.write( + descriptor_type::ENDPOINT, + &[ + endpoint.addr.into(), // bEndpointAddress + bm_attributes, // bmAttributes + endpoint.max_packet_size as u8, + (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize + endpoint.interval_ms, // bInterval + ], + extra_fields, + ); + } + + /// Writes a string descriptor. + #[allow(unused)] + pub(crate) fn string(&mut self, string: &str) { + let mut pos = self.position; + + assert!(pos + 2 <= self.buf.len(), "Descriptor buffer full"); + + self.buf[pos] = 0; // length placeholder + self.buf[pos + 1] = descriptor_type::STRING; + + pos += 2; + + for c in string.encode_utf16() { + assert!(pos < self.buf.len(), "Descriptor buffer full"); + + self.buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes()); + pos += 2; + } + + self.buf[self.position] = (pos - self.position) as u8; + + self.position = pos; + } +} + +/// Create a new Device Descriptor array. +/// +/// All device descriptors are always 18 bytes, so there's no need for +/// a variable-length buffer or DescriptorWriter. +pub(crate) fn device_descriptor(config: &Config) -> [u8; 18] { + [ + 18, // bLength + 0x01, // bDescriptorType + config.bcd_usb as u8, + (config.bcd_usb as u16 >> 8) as u8, // bcdUSB + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + config.vendor_id as u8, + (config.vendor_id >> 8) as u8, // idVendor + config.product_id as u8, + (config.product_id >> 8) as u8, // idProduct + config.device_release as u8, + (config.device_release >> 8) as u8, // bcdDevice + config.manufacturer.map_or(0, |_| 1), // iManufacturer + config.product.map_or(0, |_| 2), // iProduct + config.serial_number.map_or(0, |_| 3), // iSerialNumber + 1, // bNumConfigurations + ] +} + +/// Create a new Device Qualifier Descriptor array. +/// +/// All device qualifier descriptors are always 10 bytes, so there's no need for +/// a variable-length buffer or DescriptorWriter. +pub(crate) fn device_qualifier_descriptor(config: &Config) -> [u8; 10] { + [ + 10, // bLength + 0x06, // bDescriptorType + config.bcd_usb as u8, + (config.bcd_usb as u16 >> 8) as u8, // bcdUSB + config.device_class, // bDeviceClass + config.device_sub_class, // bDeviceSubClass + config.device_protocol, // bDeviceProtocol + config.max_packet_size_0, // bMaxPacketSize0 + 1, // bNumConfigurations + 0, // Reserved + ] +} + +/// A writer for Binary Object Store descriptor. +pub struct BosWriter<'a> { + pub(crate) writer: DescriptorWriter<'a>, + num_caps_mark: Option, +} + +impl<'a> BosWriter<'a> { + pub(crate) const fn new(writer: DescriptorWriter<'a>) -> Self { + Self { + writer, + num_caps_mark: None, + } + } + + pub(crate) fn bos(&mut self) { + if (self.writer.buf.len() - self.writer.position) < 5 { + return; + } + self.num_caps_mark = Some(self.writer.position + 4); + self.writer.write( + descriptor_type::BOS, + &[ + 0x00, 0x00, // wTotalLength + 0x00, // bNumDeviceCaps + ], + &[], + ); + + self.capability(capability_type::USB_2_0_EXTENSION, &[0; 4]); + } + + /// Writes capability descriptor to a BOS + /// + /// # Arguments + /// + /// * `capability_type` - Type of a capability + /// * `data` - Binary data of the descriptor + pub fn capability(&mut self, capability_type: u8, data: &[u8]) { + match self.num_caps_mark { + Some(mark) => self.writer.buf[mark] += 1, + None => panic!("called `capability` not between `bos` and `end_bos`."), + } + + let mut start = self.writer.position; + let blen = data.len(); + + assert!( + (start + blen + 3) <= self.writer.buf.len() && (blen + 3) <= 255, + "Descriptor buffer full" + ); + + self.writer.buf[start] = (blen + 3) as u8; + self.writer.buf[start + 1] = descriptor_type::CAPABILITY; + self.writer.buf[start + 2] = capability_type; + + start += 3; + self.writer.buf[start..start + blen].copy_from_slice(data); + self.writer.position = start + blen; + } + + pub(crate) fn end_bos(&mut self) { + if self.writer.position == 0 { + return; + } + self.num_caps_mark = None; + let position = self.writer.position as u16; + self.writer.buf[2..4].copy_from_slice(&position.to_le_bytes()); + } +} diff --git a/embassy-usb/src/descriptor_reader.rs b/embassy-usb/src/descriptor_reader.rs new file mode 100644 index 0000000..abb4b37 --- /dev/null +++ b/embassy-usb/src/descriptor_reader.rs @@ -0,0 +1,111 @@ +use crate::descriptor::descriptor_type; +use crate::driver::EndpointAddress; +use crate::types::InterfaceNumber; + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ReadError; + +pub struct Reader<'a> { + data: &'a [u8], +} + +impl<'a> Reader<'a> { + pub const fn new(data: &'a [u8]) -> Self { + Self { data } + } + + pub const fn eof(&self) -> bool { + self.data.is_empty() + } + + pub fn read(&mut self) -> Result<[u8; N], ReadError> { + let n = self.data.get(0..N).ok_or(ReadError)?; + self.data = &self.data[N..]; + Ok(n.try_into().unwrap()) + } + + pub fn read_u8(&mut self) -> Result { + Ok(u8::from_le_bytes(self.read()?)) + } + pub fn read_u16(&mut self) -> Result { + Ok(u16::from_le_bytes(self.read()?)) + } + + pub fn read_slice(&mut self, len: usize) -> Result<&'a [u8], ReadError> { + let res = self.data.get(0..len).ok_or(ReadError)?; + self.data = &self.data[len..]; + Ok(res) + } + + pub fn read_descriptors(&mut self) -> DescriptorIter<'_, 'a> { + DescriptorIter { r: self } + } +} + +pub struct DescriptorIter<'a, 'b> { + r: &'a mut Reader<'b>, +} + +impl<'a, 'b> Iterator for DescriptorIter<'a, 'b> { + type Item = Result<(u8, Reader<'a>), ReadError>; + + fn next(&mut self) -> Option { + if self.r.eof() { + return None; + } + + let len = match self.r.read_u8() { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + let type_ = match self.r.read_u8() { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + let data = match self.r.read_slice(len as usize - 2) { + Ok(x) => x, + Err(e) => return Some(Err(e)), + }; + + Some(Ok((type_, Reader::new(data)))) + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointInfo { + pub configuration: u8, + pub interface: InterfaceNumber, + pub interface_alt: u8, + pub ep_address: EndpointAddress, +} + +pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<(), ReadError> { + let mut ep = EndpointInfo { + configuration: 0, + interface: InterfaceNumber(0), + interface_alt: 0, + ep_address: EndpointAddress::from(0), + }; + for res in Reader::new(data).read_descriptors() { + let (kind, mut r) = res?; + match kind { + descriptor_type::CONFIGURATION => { + let _total_length = r.read_u16()?; + let _total_length = r.read_u8()?; + ep.configuration = r.read_u8()?; + } + descriptor_type::INTERFACE => { + ep.interface = InterfaceNumber(r.read_u8()?); + ep.interface_alt = r.read_u8()?; + } + descriptor_type::ENDPOINT => { + ep.ep_address = EndpointAddress::from(r.read_u8()?); + f(ep); + } + _ => {} + } + } + Ok(()) +} diff --git a/embassy-usb/src/fmt.rs b/embassy-usb/src/fmt.rs new file mode 100644 index 0000000..8ca61bc --- /dev/null +++ b/embassy-usb/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-usb/src/lib.rs b/embassy-usb/src/lib.rs new file mode 100644 index 0000000..0638fd0 --- /dev/null +++ b/embassy-usb/src/lib.rs @@ -0,0 +1,786 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +pub use embassy_usb_driver as driver; + +mod builder; +pub mod class; +pub mod control; +pub mod descriptor; +mod descriptor_reader; +pub mod msos; +pub mod types; + +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +use embassy_futures::select::{select, Either}; +use heapless::Vec; + +pub use crate::builder::{Builder, Config, FunctionBuilder, InterfaceAltBuilder, InterfaceBuilder, UsbVersion}; +use crate::config::{MAX_HANDLER_COUNT, MAX_INTERFACE_COUNT}; +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::descriptor::{descriptor_type, lang_id}; +use crate::descriptor_reader::foreach_endpoint; +use crate::driver::{Bus, ControlPipe, Direction, Driver, EndpointAddress, Event}; +use crate::types::{InterfaceNumber, StringIndex}; + +/// The global state of the USB device. +/// +/// In general class traffic is only possible in the `Configured` state. +#[repr(u8)] +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum UsbDeviceState { + /// The USB device has no power. + Unpowered, + + /// The USB device is disabled. + Disabled, + + /// The USB device has just been enabled or reset. + Default, + + /// The USB device has received an address from the host. + Addressed, + + /// The USB device has been configured and is fully functional. + Configured, +} + +/// Error returned by [`UsbDevice::remote_wakeup`]. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum RemoteWakeupError { + /// The USB device is not suspended, or remote wakeup was not enabled. + InvalidState, + /// The underlying driver doesn't support remote wakeup. + Unsupported, +} + +impl From for RemoteWakeupError { + fn from(_: driver::Unsupported) -> Self { + RemoteWakeupError::Unsupported + } +} + +/// The bConfiguration value for the not configured state. +pub const CONFIGURATION_NONE: u8 = 0; + +/// The bConfiguration value for the single configuration supported by this device. +pub const CONFIGURATION_VALUE: u8 = 1; + +const STRING_INDEX_MANUFACTURER: u8 = 1; +const STRING_INDEX_PRODUCT: u8 = 2; +const STRING_INDEX_SERIAL_NUMBER: u8 = 3; +const STRING_INDEX_CUSTOM_START: u8 = 4; + +/// Handler for device events and control requests. +/// +/// All methods are optional callbacks that will be called by +/// [`UsbDevice::run()`](crate::UsbDevice::run) +pub trait Handler { + /// Called when the USB device has been enabled or disabled. + fn enabled(&mut self, _enabled: bool) {} + + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) {} + + /// Called when the host has set the address of the device to `addr`. + fn addressed(&mut self, _addr: u8) {} + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&mut self, _configured: bool) {} + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&mut self, _suspended: bool) {} + + /// Called when remote wakeup feature is enabled or disabled. + fn remote_wakeup_enabled(&mut self, _enabled: bool) {} + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + let _ = iface; + let _ = alternate_setting; + } + + /// Called when a control request is received with direction HostToDevice. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `data` - The data from the request. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + let _ = (req, data); + None + } + + /// Called when a control request is received with direction DeviceToHost. + /// + /// You should write the response somewhere (usually to `buf`, but you may use another buffer + /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + let _ = (req, buf); + None + } + + /// Called when a GET_DESCRIPTOR STRING control request is received. + fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { + let _ = (index, lang_id); + None + } +} + +struct Interface { + current_alt_setting: u8, + num_alt_settings: u8, +} + +/// A report of the used size of the runtime allocated buffers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UsbBufferReport { + /// Number of config descriptor bytes used + pub config_descriptor_used: usize, + /// Number of bos descriptor bytes used + pub bos_descriptor_used: usize, + /// Number of msos descriptor bytes used + pub msos_descriptor_used: usize, + /// Size of the control buffer + pub control_buffer_size: usize, +} + +/// Main struct for the USB device stack. +pub struct UsbDevice<'d, D: Driver<'d>> { + control_buf: &'d mut [u8], + control: D::ControlPipe, + inner: Inner<'d, D>, +} + +struct Inner<'d, D: Driver<'d>> { + bus: D::Bus, + + config: Config<'d>, + device_descriptor: [u8; 18], + device_qualifier_descriptor: [u8; 10], + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, + + device_state: UsbDeviceState, + suspended: bool, + remote_wakeup_enabled: bool, + self_powered: bool, + + /// Our device address, or 0 if none. + address: u8, + /// SET_ADDRESS requests have special handling depending on the driver. + /// This flag indicates that requests must be handled by `ControlPipe::accept_set_address()` + /// instead of regular `accept()`. + set_address_pending: bool, + + interfaces: Vec, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, +} + +impl<'d, D: Driver<'d>> UsbDevice<'d, D> { + pub(crate) fn build( + driver: D, + config: Config<'d>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + config_descriptor: &'d [u8], + bos_descriptor: &'d [u8], + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, + interfaces: Vec, + control_buf: &'d mut [u8], + ) -> UsbDevice<'d, D> { + // Start the USB bus. + // This prevent further allocation by consuming the driver. + let (bus, control) = driver.start(config.max_packet_size_0 as u16); + let device_descriptor = descriptor::device_descriptor(&config); + let device_qualifier_descriptor = descriptor::device_qualifier_descriptor(&config); + + Self { + control_buf, + control, + inner: Inner { + bus, + config, + device_descriptor, + device_qualifier_descriptor, + config_descriptor, + bos_descriptor, + msos_descriptor, + + device_state: UsbDeviceState::Unpowered, + suspended: false, + remote_wakeup_enabled: false, + self_powered: false, + address: 0, + set_address_pending: false, + interfaces, + handlers, + }, + } + } + + /// Returns a report of the consumed buffers + /// + /// Useful for tuning buffer sizes for actual usage + pub fn buffer_usage(&self) -> UsbBufferReport { + UsbBufferReport { + config_descriptor_used: self.inner.config_descriptor.len(), + bos_descriptor_used: self.inner.bos_descriptor.len(), + msos_descriptor_used: self.inner.msos_descriptor.len(), + control_buffer_size: self.control_buf.len(), + } + } + + /// Runs the `UsbDevice` forever. + /// + /// This future may leave the bus in an invalid state if it is dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the + /// peripheral. + pub async fn run(&mut self) -> ! { + loop { + self.run_until_suspend().await; + self.wait_resume().await; + } + } + + /// Runs the `UsbDevice` until the bus is suspended. + /// + /// This future may leave the bus in an invalid state if it is dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the + /// peripheral. + pub async fn run_until_suspend(&mut self) { + while !self.inner.suspended { + let control_fut = self.control.setup(); + let bus_fut = self.inner.bus.poll(); + match select(bus_fut, control_fut).await { + Either::First(evt) => self.inner.handle_bus_event(evt).await, + Either::Second(req) => self.handle_control(req).await, + } + } + } + + /// Disables the USB peripheral. + pub async fn disable(&mut self) { + if self.inner.device_state != UsbDeviceState::Disabled { + self.inner.bus.disable().await; + self.inner.device_state = UsbDeviceState::Disabled; + self.inner.suspended = false; + self.inner.remote_wakeup_enabled = false; + + for h in &mut self.inner.handlers { + h.enabled(false); + } + } + } + + /// Waits for a resume condition on the USB bus. + /// + /// This future is cancel-safe. + pub async fn wait_resume(&mut self) { + while self.inner.suspended { + let evt = self.inner.bus.poll().await; + self.inner.handle_bus_event(evt).await; + } + } + + /// Initiates a device remote wakeup on the USB bus. + /// + /// If the bus is not suspended or remote wakeup is not enabled, an error + /// will be returned. + /// + /// This future may leave the bus in an inconsistent state if dropped. + /// After dropping the future, [`UsbDevice::disable()`] should be called + /// before calling any other `UsbDevice` methods to fully reset the peripheral. + pub async fn remote_wakeup(&mut self) -> Result<(), RemoteWakeupError> { + if self.inner.suspended && self.inner.remote_wakeup_enabled { + self.inner.bus.remote_wakeup().await?; + self.inner.suspended = false; + + for h in &mut self.inner.handlers { + h.suspended(false); + } + + Ok(()) + } else { + Err(RemoteWakeupError::InvalidState) + } + } + + async fn handle_control(&mut self, req: [u8; 8]) { + let req = Request::parse(&req); + + trace!("control request: {:?}", req); + + match req.direction { + Direction::In => self.handle_control_in(req).await, + Direction::Out => self.handle_control_out(req).await, + } + } + + async fn handle_control_in(&mut self, req: Request) { + const DEVICE_DESCRIPTOR_LEN: usize = 18; + + let mut resp_length = req.length as usize; + let max_packet_size = self.control.max_packet_size(); + + // If we don't have an address yet, respond with max 1 packet. + // The host doesn't know our EP0 max packet size yet, and might assume + // a full-length packet is a short packet, thinking we're done sending data. + // See https://github.com/hathach/tinyusb/issues/184 + if self.inner.address == 0 && max_packet_size < DEVICE_DESCRIPTOR_LEN && max_packet_size < resp_length { + trace!("received control req while not addressed: capping response to 1 packet."); + resp_length = max_packet_size; + } + + match self.inner.handle_control_in(req, self.control_buf) { + InResponse::Accepted(data) => { + let len = data.len().min(resp_length); + let need_zlp = len != resp_length && (len % max_packet_size) == 0; + + let chunks = data[0..len] + .chunks(max_packet_size) + .chain(need_zlp.then(|| -> &[u8] { &[] })); + + for (first, last, chunk) in first_last(chunks) { + match self.control.data_in(chunk, first, last).await { + Ok(()) => {} + Err(e) => { + warn!("control accept_in failed: {:?}", e); + return; + } + } + } + } + InResponse::Rejected => self.control.reject().await, + } + } + + async fn handle_control_out(&mut self, req: Request) { + let req_length = req.length as usize; + let max_packet_size = self.control.max_packet_size(); + let mut total = 0; + + if req_length > self.control_buf.len() { + warn!( + "got CONTROL OUT with length {} higher than the control_buf len {}, rejecting.", + req_length, + self.control_buf.len() + ); + self.control.reject().await; + return; + } + + let chunks = self.control_buf[..req_length].chunks_mut(max_packet_size); + for (first, last, chunk) in first_last(chunks) { + let size = match self.control.data_out(chunk, first, last).await { + Ok(x) => x, + Err(e) => { + warn!("usb: failed to read CONTROL OUT data stage: {:?}", e); + return; + } + }; + total += size; + if size < max_packet_size || total == req_length { + break; + } + } + + let data = &self.control_buf[0..total]; + #[cfg(feature = "defmt")] + trace!(" control out data: {:02x}", data); + #[cfg(not(feature = "defmt"))] + trace!(" control out data: {:02x?}", data); + + match self.inner.handle_control_out(req, data) { + OutResponse::Accepted => { + if self.inner.set_address_pending { + self.control.accept_set_address(self.inner.address).await; + self.inner.set_address_pending = false; + } else { + self.control.accept().await; + } + } + OutResponse::Rejected => self.control.reject().await, + } + } +} + +impl<'d, D: Driver<'d>> Inner<'d, D> { + async fn handle_bus_event(&mut self, evt: Event) { + match evt { + Event::Reset => { + trace!("usb: reset"); + self.device_state = UsbDeviceState::Default; + self.suspended = false; + self.remote_wakeup_enabled = false; + self.address = 0; + + for h in &mut self.handlers { + h.reset(); + } + + for (i, iface) in self.interfaces.iter_mut().enumerate() { + iface.current_alt_setting = 0; + + for h in &mut self.handlers { + h.set_alternate_setting(InterfaceNumber::new(i as _), 0); + } + } + } + Event::Resume => { + trace!("usb: resume"); + self.suspended = false; + for h in &mut self.handlers { + h.suspended(false); + } + } + Event::Suspend => { + trace!("usb: suspend"); + self.suspended = true; + for h in &mut self.handlers { + h.suspended(true); + } + } + Event::PowerDetected => { + trace!("usb: power detected"); + self.bus.enable().await; + self.device_state = UsbDeviceState::Default; + + for h in &mut self.handlers { + h.enabled(true); + } + } + Event::PowerRemoved => { + trace!("usb: power removed"); + self.bus.disable().await; + self.device_state = UsbDeviceState::Unpowered; + + for h in &mut self.handlers { + h.enabled(false); + } + } + } + } + + fn handle_control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { + const CONFIGURATION_NONE_U16: u16 = CONFIGURATION_NONE as u16; + const CONFIGURATION_VALUE_U16: u16 = CONFIGURATION_VALUE as u16; + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { + (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { + self.remote_wakeup_enabled = false; + for h in &mut self.handlers { + h.remote_wakeup_enabled(false); + } + OutResponse::Accepted + } + (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { + self.remote_wakeup_enabled = true; + for h in &mut self.handlers { + h.remote_wakeup_enabled(true); + } + OutResponse::Accepted + } + (Request::SET_ADDRESS, addr @ 1..=127) => { + self.address = addr as u8; + self.set_address_pending = true; + self.device_state = UsbDeviceState::Addressed; + for h in &mut self.handlers { + h.addressed(self.address); + } + OutResponse::Accepted + } + (Request::SET_CONFIGURATION, CONFIGURATION_VALUE_U16) => { + debug!("SET_CONFIGURATION: configured"); + self.device_state = UsbDeviceState::Configured; + + // Enable all endpoints of selected alt settings. + foreach_endpoint(self.config_descriptor, |ep| { + let iface = &self.interfaces[ep.interface.0 as usize]; + self.bus + .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); + }) + .unwrap(); + + // Notify handlers. + for h in &mut self.handlers { + h.configured(true); + } + + OutResponse::Accepted + } + (Request::SET_CONFIGURATION, CONFIGURATION_NONE_U16) => { + if self.device_state != UsbDeviceState::Default { + debug!("SET_CONFIGURATION: unconfigured"); + self.device_state = UsbDeviceState::Addressed; + + // Disable all endpoints. + foreach_endpoint(self.config_descriptor, |ep| { + self.bus.endpoint_set_enabled(ep.ep_address, false); + }) + .unwrap(); + + // Notify handlers. + for h in &mut self.handlers { + h.configured(false); + } + } + OutResponse::Accepted + } + _ => OutResponse::Rejected, + }, + (RequestType::Standard, Recipient::Interface) => { + let iface_num = InterfaceNumber::new(req.index as _); + let Some(iface) = self.interfaces.get_mut(iface_num.0 as usize) else { + return OutResponse::Rejected; + }; + + match req.request { + Request::SET_INTERFACE => { + let new_altsetting = req.value as u8; + + if new_altsetting >= iface.num_alt_settings { + warn!("SET_INTERFACE: trying to select alt setting out of range."); + return OutResponse::Rejected; + } + + iface.current_alt_setting = new_altsetting; + + // Enable/disable EPs of this interface as needed. + foreach_endpoint(self.config_descriptor, |ep| { + if ep.interface == iface_num { + self.bus + .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); + } + }) + .unwrap(); + + // TODO check it is valid (not out of range) + + for h in &mut self.handlers { + h.set_alternate_setting(iface_num, new_altsetting); + } + OutResponse::Accepted + } + _ => OutResponse::Rejected, + } + } + (RequestType::Standard, Recipient::Endpoint) => match (req.request, req.value) { + (Request::SET_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + let ep_addr = ((req.index as u8) & 0x8f).into(); + self.bus.endpoint_set_stalled(ep_addr, true); + OutResponse::Accepted + } + (Request::CLEAR_FEATURE, Request::FEATURE_ENDPOINT_HALT) => { + let ep_addr = ((req.index as u8) & 0x8f).into(); + self.bus.endpoint_set_stalled(ep_addr, false); + OutResponse::Accepted + } + _ => OutResponse::Rejected, + }, + _ => self.handle_control_out_delegated(req, data), + } + } + + fn handle_control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Device) => match req.request { + Request::GET_STATUS => { + let mut status: u16 = 0x0000; + if self.self_powered { + status |= 0x0001; + } + if self.remote_wakeup_enabled { + status |= 0x0002; + } + buf[..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[..2]) + } + Request::GET_DESCRIPTOR => self.handle_get_descriptor(req, buf), + Request::GET_CONFIGURATION => { + let status = match self.device_state { + UsbDeviceState::Configured => CONFIGURATION_VALUE, + _ => CONFIGURATION_NONE, + }; + buf[0] = status; + InResponse::Accepted(&buf[..1]) + } + _ => InResponse::Rejected, + }, + (RequestType::Standard, Recipient::Interface) => { + let Some(iface) = self.interfaces.get_mut(req.index as usize) else { + return InResponse::Rejected; + }; + + match req.request { + Request::GET_STATUS => { + let status: u16 = 0; + buf[..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[..2]) + } + Request::GET_INTERFACE => { + buf[0] = iface.current_alt_setting; + InResponse::Accepted(&buf[..1]) + } + _ => self.handle_control_in_delegated(req, buf), + } + } + (RequestType::Standard, Recipient::Endpoint) => match req.request { + Request::GET_STATUS => { + let ep_addr: EndpointAddress = ((req.index as u8) & 0x8f).into(); + let mut status: u16 = 0x0000; + if self.bus.endpoint_is_stalled(ep_addr) { + status |= 0x0001; + } + buf[..2].copy_from_slice(&status.to_le_bytes()); + InResponse::Accepted(&buf[..2]) + } + _ => InResponse::Rejected, + }, + + (RequestType::Vendor, Recipient::Device) => { + if !self.msos_descriptor.is_empty() + && req.request == self.msos_descriptor.vendor_code() + && req.index == 7 + { + // Index 7 retrieves the MS OS Descriptor Set + InResponse::Accepted(self.msos_descriptor.descriptor()) + } else { + self.handle_control_in_delegated(req, buf) + } + } + _ => self.handle_control_in_delegated(req, buf), + } + } + + fn handle_control_out_delegated(&mut self, req: Request, data: &[u8]) -> OutResponse { + for h in &mut self.handlers { + if let Some(res) = h.control_out(req, data) { + return res; + } + } + OutResponse::Rejected + } + + fn handle_control_in_delegated<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + unsafe fn extend_lifetime<'y>(r: InResponse<'_>) -> InResponse<'y> { + core::mem::transmute(r) + } + + for h in &mut self.handlers { + if let Some(res) = h.control_in(req, buf) { + // safety: the borrow checker isn't smart enough to know this pattern (returning a + // borrowed value from inside the loop) is sound. Workaround by unsafely extending lifetime. + // Also, Polonius (the WIP new borrow checker) does accept it. + + return unsafe { extend_lifetime(res) }; + } + } + InResponse::Rejected + } + + fn handle_get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + let (dtype, index) = req.descriptor_type_index(); + + match dtype { + descriptor_type::BOS => InResponse::Accepted(self.bos_descriptor), + descriptor_type::DEVICE => InResponse::Accepted(&self.device_descriptor), + descriptor_type::CONFIGURATION => InResponse::Accepted(self.config_descriptor), + descriptor_type::STRING => { + if index == 0 { + buf[0] = 4; // len + buf[1] = descriptor_type::STRING; + buf[2] = lang_id::ENGLISH_US as u8; + buf[3] = (lang_id::ENGLISH_US >> 8) as u8; + InResponse::Accepted(&buf[..4]) + } else { + let s = match index { + STRING_INDEX_MANUFACTURER => self.config.manufacturer, + STRING_INDEX_PRODUCT => self.config.product, + STRING_INDEX_SERIAL_NUMBER => self.config.serial_number, + _ => { + let mut s = None; + for handler in &mut self.handlers { + let index = StringIndex::new(index); + let lang_id = req.index; + if let Some(res) = handler.get_string(index, lang_id) { + s = Some(res); + break; + } + } + s + } + }; + + if let Some(s) = s { + assert!(buf.len() >= 2, "control buffer too small"); + + buf[1] = descriptor_type::STRING; + let mut pos = 2; + for c in s.encode_utf16() { + assert!(pos + 2 < buf.len(), "control buffer too small"); + + buf[pos..pos + 2].copy_from_slice(&c.to_le_bytes()); + pos += 2; + } + + buf[0] = pos as u8; + InResponse::Accepted(&buf[..pos]) + } else { + InResponse::Rejected + } + } + } + descriptor_type::DEVICE_QUALIFIER => InResponse::Accepted(&self.device_qualifier_descriptor), + _ => InResponse::Rejected, + } + } +} + +fn first_last(iter: T) -> impl Iterator { + let mut iter = iter.peekable(); + let mut first = true; + core::iter::from_fn(move || { + let val = iter.next()?; + let is_first = first; + first = false; + let is_last = iter.peek().is_none(); + Some((is_first, is_last, val)) + }) +} diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs new file mode 100644 index 0000000..9f4e1a5 --- /dev/null +++ b/embassy-usb/src/msos.rs @@ -0,0 +1,728 @@ +//! Microsoft OS Descriptors +//! +//! + +use core::mem::size_of; + +use crate::descriptor::{capability_type, BosWriter}; +use crate::types::InterfaceNumber; + +/// A serialized Microsoft OS 2.0 Descriptor set. +/// +/// Create with [`DeviceDescriptorSetBuilder`]. +pub struct MsOsDescriptorSet<'d> { + descriptor: &'d [u8], + vendor_code: u8, +} + +impl<'d> MsOsDescriptorSet<'d> { + /// Gets the raw bytes of the MS OS descriptor + pub fn descriptor(&self) -> &[u8] { + self.descriptor + } + + /// Gets the vendor code used by the host to retrieve the MS OS descriptor + pub fn vendor_code(&self) -> u8 { + self.vendor_code + } + + /// Returns `true` if no MS OS descriptor data is available + pub fn is_empty(&self) -> bool { + self.descriptor.is_empty() + } + + /// Returns the length of the descriptor field + pub fn len(&self) -> usize { + self.descriptor.len() + } +} + +/// Writes a Microsoft OS 2.0 Descriptor set into a buffer. +pub struct MsOsDescriptorWriter<'d> { + buf: &'d mut [u8], + + position: usize, + config_mark: Option, + function_mark: Option, + vendor_code: u8, +} + +impl<'d> MsOsDescriptorWriter<'d> { + pub(crate) fn new(buf: &'d mut [u8]) -> Self { + MsOsDescriptorWriter { + buf, + position: 0, + config_mark: None, + function_mark: None, + vendor_code: 0, + } + } + + pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> { + self.end(); + + if self.is_empty() { + MsOsDescriptorSet { + descriptor: &[], + vendor_code: 0, + } + } else { + self.write_bos(bos); + MsOsDescriptorSet { + descriptor: &self.buf[..self.position], + vendor_code: self.vendor_code, + } + } + } + + /// Returns `true` if the MS OS descriptor header has not yet been written + pub fn is_empty(&self) -> bool { + self.position == 0 + } + + /// Returns `true` if a configuration subset header has been started + pub fn is_in_config_subset(&self) -> bool { + self.config_mark.is_some() + } + + /// Returns `true` if a function subset header has been started and not yet ended + pub fn is_in_function_subset(&self) -> bool { + self.function_mark.is_some() + } + + /// Write the MS OS descriptor set header. + /// + /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] + /// module. + /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. + pub fn header(&mut self, windows_version: u32, vendor_code: u8) { + assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once"); + self.write(DescriptorSetHeader::new(windows_version)); + self.vendor_code = vendor_code; + } + + /// Add a device level feature descriptor. + /// + /// Note that some feature descriptors may only be used at the device level in non-composite devices. + /// Those features must be written before the first call to [`Self::configuration`]. + pub fn device_feature(&mut self, desc: T) { + assert!( + !self.is_empty(), + "device features may only be added after the header is written" + ); + assert!( + self.config_mark.is_none(), + "device features must be added before the first configuration subset" + ); + self.write(desc); + } + + /// Add a configuration subset. + pub fn configuration(&mut self, config: u8) { + assert!( + !self.is_empty(), + "MsOsDescriptorWriter: configuration must be called after header" + ); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + self.config_mark = Some(self.position); + self.write(ConfigurationSubsetHeader::new(config)); + } + + /// Add a function subset. + pub fn function(&mut self, first_interface: InterfaceNumber) { + assert!( + self.config_mark.is_some(), + "MsOsDescriptorWriter: function subset requires a configuration subset" + ); + self.end_function(); + self.function_mark = Some(self.position); + self.write(FunctionSubsetHeader::new(first_interface)); + } + + /// Add a function level feature descriptor. + /// + /// Note that some features may only be used at the function level. Those features must be written after a call + /// to [`Self::function`]. + pub fn function_feature(&mut self, desc: T) { + assert!( + self.function_mark.is_some(), + "function features may only be added to a function subset" + ); + self.write(desc); + } + + /// Ends the current function subset (if any) + pub fn end_function(&mut self) { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + } + + fn write(&mut self, desc: T) { + desc.write_to(&mut self.buf[self.position..]); + self.position += desc.size(); + } + + fn end_subset(buf: &mut [u8], position: usize, mark: &mut Option) { + if let Some(mark) = mark.take() { + let len = position - mark; + let p = mark + T::LENGTH_OFFSET; + buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes()); + } + } + + fn end(&mut self) { + if self.position > 0 { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + Self::end_subset::(self.buf, self.position, &mut Some(0)); + } + } + + fn write_bos(&mut self, bos: &mut BosWriter) { + let windows_version = &self.buf[4..8]; + let len = (self.position as u16).to_le_bytes(); + bos.capability( + capability_type::PLATFORM, + &[ + 0, // reserved + // platform capability UUID, Microsoft OS 2.0 platform compatibility + 0xdf, + 0x60, + 0xdd, + 0xd8, + 0x89, + 0x45, + 0xc7, + 0x4c, + 0x9c, + 0xd2, + 0x65, + 0x9d, + 0x9e, + 0x64, + 0x8a, + 0x9f, + // Minimum compatible Windows version + windows_version[0], + windows_version[1], + windows_version[2], + windows_version[3], + // Descriptor set length + len[0], + len[1], + self.vendor_code, + 0x0, // Device does not support alternate enumeration + ], + ); + } +} + +/// Microsoft Windows version codes +/// +/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors. +pub mod windows_version { + /// Windows 8.1 (aka `NTDDI_WINBLUE`) + pub const WIN8_1: u32 = 0x06030000; + /// Windows 10 + pub const WIN10: u32 = 0x0A000000; +} + +/// A trait for descriptors +trait Descriptor: Sized { + const TYPE: DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() + } + + fn write_to(&self, buf: &mut [u8]); +} + +trait DescriptorSet: Descriptor { + const LENGTH_OFFSET: usize; +} + +/// Copies the data of `t` into `buf`. +/// +/// # Safety +/// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) +unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { + let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); + assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full"); + buf[..bytes.len()].copy_from_slice(bytes); +} + +/// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DescriptorType { + /// MS OS descriptor set header + SetHeaderDescriptor = 0, + /// Configuration subset header + SubsetHeaderConfiguration = 1, + /// Function subset header + SubsetHeaderFunction = 2, + /// Compatible device ID feature descriptor + FeatureCompatibleId = 3, + /// Registry property feature descriptor + FeatureRegProperty = 4, + /// Minimum USB resume time feature descriptor + FeatureMinResumeTime = 5, + /// Vendor revision feature descriptor + FeatureModelId = 6, + /// CCGP device descriptor feature descriptor + FeatureCcgpDevice = 7, + /// Vendor revision feature descriptor + FeatureVendorRevision = 8, +} + +/// Table 5. Descriptor set information structure. +#[allow(non_snake_case)] +#[allow(unused)] +#[repr(C, packed(1))] +pub struct DescriptorSetInformation { + dwWindowsVersion: u32, + wMSOSDescriptorSetTotalLength: u16, + bMS_VendorCode: u8, + bAltEnumCode: u8, +} + +/// Table 4. Microsoft OS 2.0 platform capability descriptor header. +#[allow(non_snake_case)] +#[allow(unused)] +#[repr(C, packed(1))] +pub struct PlatformDescriptor { + bLength: u8, + bDescriptorType: u8, + bDevCapabilityType: u8, + bReserved: u8, + platformCapabilityUUID: [u8; 16], + descriptor_set_information: DescriptorSetInformation, +} + +/// Table 10. Microsoft OS 2.0 descriptor set header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetHeader { + wLength: u16, + wDescriptorType: u16, + dwWindowsVersion: u32, + wTotalLength: u16, +} + +impl DescriptorSetHeader { + /// Creates a MS OS descriptor set header. + /// + /// `windows_version` is the minimum Windows version the descriptor set can apply to. + pub fn new(windows_version: u32) -> Self { + DescriptorSetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + dwWindowsVersion: windows_version.to_le(), + wTotalLength: 0, + } + } +} + +impl Descriptor for DescriptorSetHeader { + const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for DescriptorSetHeader { + const LENGTH_OFFSET: usize = 8; +} + +/// Table 11. Configuration subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ConfigurationSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bConfigurationValue: u8, + bReserved: u8, + wTotalLength: u16, +} + +impl ConfigurationSubsetHeader { + /// Creates a configuration subset header + pub fn new(config: u8) -> Self { + ConfigurationSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bConfigurationValue: config, + bReserved: 0, + wTotalLength: 0, + } + } +} + +impl Descriptor for ConfigurationSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for ConfigurationSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + +/// Table 12. Function subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct FunctionSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bFirstInterface: InterfaceNumber, + bReserved: u8, + wSubsetLength: u16, +} + +impl FunctionSubsetHeader { + /// Creates a function subset header + pub fn new(first_interface: InterfaceNumber) -> Self { + FunctionSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bFirstInterface: first_interface, + bReserved: 0, + wSubsetLength: 0, + } + } +} + +impl Descriptor for FunctionSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for FunctionSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + +// Feature Descriptors + +/// A marker trait for feature descriptors that are valid at the device level. +#[allow(private_bounds)] +pub trait DeviceLevelDescriptor: Descriptor {} + +/// A marker trait for feature descriptors that are valid at the function level. +#[allow(private_bounds)] +pub trait FunctionLevelDescriptor: Descriptor {} + +/// Table 13. Microsoft OS 2.0 compatible ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CompatibleIdFeatureDescriptor { + wLength: u16, + wDescriptorType: u16, + compatibleId: [u8; 8], + subCompatibleId: [u8; 8], +} + +impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} +impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {} + +impl Descriptor for CompatibleIdFeatureDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CompatibleIdFeatureDescriptor { + /// Creates a compatible ID feature descriptor + /// + /// The ids must be 8 ASCII bytes or fewer. + pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { + assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); + let mut cid = [0u8; 8]; + cid[..compatible_id.len()].copy_from_slice(compatible_id.as_bytes()); + let mut scid = [0u8; 8]; + scid[..sub_compatible_id.len()].copy_from_slice(sub_compatible_id.as_bytes()); + Self::new_raw(cid, scid) + } + + fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + compatibleId: compatible_id, + subCompatibleId: sub_compatible_id, + } + } +} + +/// Table 14. Microsoft OS 2.0 registry property descriptor +#[allow(non_snake_case)] +pub struct RegistryPropertyFeatureDescriptor<'a> { + name: &'a str, + data: PropertyData<'a>, +} + +/// Data values that can be encoded into a registry property descriptor +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PropertyData<'a> { + /// A registry property containing a string. + Sz(&'a str), + /// A registry property containing a string that expands environment variables. + ExpandSz(&'a str), + /// A registry property containing binary data. + Binary(&'a [u8]), + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian(u32), + /// A registry property containing a big-endian 32-bit integer. + DwordBigEndian(u32), + /// A registry property containing a string that contains a symbolic link. + Link(&'a str), + /// A registry property containing multiple strings. + RegMultiSz(&'a [&'a str]), +} + +fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize { + assert!(buf.len() >= val.len()); + buf[..val.len()].copy_from_slice(val); + val.len() +} + +fn write_utf16(val: &str, buf: &mut [u8]) -> usize { + let mut pos = 0; + for c in val.encode_utf16() { + pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) +} + +impl<'a> PropertyData<'a> { + /// Gets the `PropertyDataType` for this property value + pub fn kind(&self) -> PropertyDataType { + match self { + PropertyData::Sz(_) => PropertyDataType::Sz, + PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz, + PropertyData::Binary(_) => PropertyDataType::Binary, + PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian, + PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian, + PropertyData::Link(_) => PropertyDataType::Link, + PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz, + } + } + + /// Gets the size (in bytes) of this property value when encoded. + pub fn size(&self) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => { + core::mem::size_of::() * (val.encode_utf16().count() + 1) + } + PropertyData::Binary(val) => val.len(), + PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), + PropertyData::RegMultiSz(val) => { + core::mem::size_of::() * (val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1) + } + } + } + + /// Encodes the data for this property value and writes it to `buf`. + pub fn write(&self, buf: &mut [u8]) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf), + PropertyData::Binary(val) => write_bytes(val, buf), + PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf), + PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf), + PropertyData::RegMultiSz(val) => { + let mut pos = 0; + for s in *val { + pos += write_utf16(s, &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) + } + } + } +} + +/// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum PropertyDataType { + /// A registry property containing a string. + Sz = 1, + /// A registry property containing a string that expands environment variables. + ExpandSz = 2, + /// A registry property containing binary data. + Binary = 3, + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian = 4, + /// A registry property containing a big-endian 32-bit integer. + DwordBigEndian = 5, + /// A registry property containing a string that contains a symbolic link. + Link = 6, + /// A registry property containing multiple strings. + RegMultiSz = 7, +} + +impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} + +impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { + const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; + + fn size(&self) -> usize { + 10 + self.name_size() + self.data.size() + } + + fn write_to(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.size(), "MS OS descriptor buffer full"); + + let mut pos = 0; + pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_utf16(self.name, &mut buf[pos..]); + pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]); + self.data.write(&mut buf[pos..]); + } +} + +impl<'a> RegistryPropertyFeatureDescriptor<'a> { + /// A registry property. + pub fn new(name: &'a str, data: PropertyData<'a>) -> Self { + Self { name, data } + } + + fn name_size(&self) -> usize { + core::mem::size_of::() * (self.name.encode_utf16().count() + 1) + } +} + +/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct MinimumRecoveryTimeDescriptor { + wLength: u16, + wDescriptorType: u16, + bResumeRecoveryTime: u8, + bResumeSignalingTime: u8, +} + +impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {} + +impl Descriptor for MinimumRecoveryTimeDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl MinimumRecoveryTimeDescriptor { + /// Times are in milliseconds. + /// + /// `resume_recovery_time` must be >= 0 and <= 10. + /// `resume_signaling_time` must be >= 1 and <= 20. + pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self { + assert!(resume_recovery_time <= 10); + assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bResumeRecoveryTime: resume_recovery_time, + bResumeSignalingTime: resume_signaling_time, + } + } +} + +/// Table 17. Microsoft OS 2.0 model ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ModelIdDescriptor { + wLength: u16, + wDescriptorType: u16, + modelId: [u8; 16], +} + +impl DeviceLevelDescriptor for ModelIdDescriptor {} + +impl Descriptor for ModelIdDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureModelId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl ModelIdDescriptor { + /// Creates a new model ID descriptor + /// + /// `model_id` should be a uuid that uniquely identifies a physical device. + pub fn new(model_id: u128) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + modelId: model_id.to_le_bytes(), + } + } +} + +/// Table 18. Microsoft OS 2.0 CCGP device descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CcgpDeviceDescriptor { + wLength: u16, + wDescriptorType: u16, +} + +impl DeviceLevelDescriptor for CcgpDeviceDescriptor {} + +impl Descriptor for CcgpDeviceDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CcgpDeviceDescriptor { + /// Creates a new CCGP device descriptor + pub fn new() -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + } + } +} + +/// Table 19. Microsoft OS 2.0 vendor revision descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct VendorRevisionDescriptor { + wLength: u16, + wDescriptorType: u16, + /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or + /// other MS OS descriptor. Shell set to greater than or equal to 1. + VendorRevision: u16, +} + +impl DeviceLevelDescriptor for VendorRevisionDescriptor {} +impl FunctionLevelDescriptor for VendorRevisionDescriptor {} + +impl Descriptor for VendorRevisionDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl VendorRevisionDescriptor { + /// Creates a new vendor revision descriptor + pub fn new(revision: u16) -> Self { + assert!(revision >= 1); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + VendorRevision: revision.to_le(), + } + } +} diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs new file mode 100644 index 0000000..ea6347c --- /dev/null +++ b/embassy-usb/src/types.rs @@ -0,0 +1,43 @@ +//! USB types. + +/// A handle for a USB interface that contains its number. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct InterfaceNumber(pub u8); + +impl InterfaceNumber { + pub(crate) const fn new(index: u8) -> InterfaceNumber { + InterfaceNumber(index) + } +} + +impl From for u8 { + fn from(n: InterfaceNumber) -> u8 { + n.0 + } +} + +impl core::fmt::Display for InterfaceNumber { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// A handle for a USB string descriptor that contains its index. +#[derive(Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(transparent)] +pub struct StringIndex(pub u8); + +impl StringIndex { + pub(crate) const fn new(index: u8) -> StringIndex { + StringIndex(index) + } +} + +impl From for u8 { + fn from(i: StringIndex) -> u8 { + i.0 + } +}

+ 'd, + scl: PeripheralRef<'d, AnyPin>, + sda: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!(_peri); + + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + + // Configure SCL & SDA pins + set_up_i2c_pin(&scl); + set_up_i2c_pin(&sda); + + 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 { + /// 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/src/i2c_slave.rs b/embassy-rp/src/i2c_slave.rs new file mode 100644 index 0000000..d17b11d --- /dev/null +++ b/embassy-rp/src/i2c_slave.rs @@ -0,0 +1,395 @@ +//! I2C slave driver. +use core::future; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::into_ref; +use pac::i2c; + +use crate::i2c::{set_up_i2c_pin, AbortReason, Instance, InterruptHandler, SclPin, SdaPin, FIFO_SIZE}; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{pac, Peripheral}; + +/// 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 Complere, 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, +} + +impl Default for Config { + fn default() -> Self { + Self { + addr: 0x55, + general_call: 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: impl Peripheral