From 306aa47170fd59369b7a184924e287dc3706d64d Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 23 Jan 2023 20:05:47 +0100 Subject: Add rtic-timer (timerqueue + monotonic) and rtic-monotonics (systick-monotonic) --- .cargo/config.toml | 13 - CHANGELOG.md | 603 --------------------- Cargo.toml | 80 --- build.rs | 25 - ci/expected/async-delay.run | 7 - ci/expected/async-infinite-loop.run | 6 - ci/expected/async-task-multiple-prios.run | 6 - ci/expected/async-task.run | 5 - ci/expected/async-timeout.run | 5 - ci/expected/big-struct-opt.run | 3 - ci/expected/binds.run | 4 - ci/expected/cancel-reschedule.run | 3 - ci/expected/capacity.run | 5 - ci/expected/cfg-monotonic.run | 0 ci/expected/cfg-whole-task.run | 0 ci/expected/common.run | 0 ci/expected/complex.run | 47 -- ci/expected/declared_locals.run | 0 ci/expected/destructure.run | 2 - ci/expected/extern_binds.run | 4 - ci/expected/extern_spawn.run | 1 - ci/expected/generics.run | 6 - ci/expected/hardware.run | 4 - ci/expected/idle-wfi.run | 2 - ci/expected/idle.run | 2 - ci/expected/init.run | 1 - ci/expected/locals.run | 3 - ci/expected/lock-free.run | 2 - ci/expected/lock.run | 5 - ci/expected/message.run | 6 - ci/expected/message_passing.run | 3 - ci/expected/multilock.run | 1 - ci/expected/not-sync.run | 3 - ci/expected/only-shared-access.run | 2 - ci/expected/periodic-at.run | 4 - ci/expected/periodic-at2.run | 7 - ci/expected/periodic.run | 4 - ci/expected/peripherals-taken.run | 0 ci/expected/pool.run | 0 ci/expected/preempt.run | 5 - ci/expected/ramfunc.run | 1 - ci/expected/ramfunc.run.grep.bar | 1 - ci/expected/ramfunc.run.grep.foo | 1 - ci/expected/resource-user-struct.run | 2 - ci/expected/schedule.run | 4 - ci/expected/shared.run | 1 - ci/expected/smallest.run | 0 ci/expected/spawn.run | 2 - ci/expected/static.run | 3 - ci/expected/t-binds.run | 0 ci/expected/t-cfg-resources.run | 0 ci/expected/t-htask-main.run | 0 ci/expected/t-idle-main.run | 0 ci/expected/t-late-not-send.run | 0 ci/expected/t-schedule.run | 0 ci/expected/t-spawn.run | 0 ci/expected/task.run | 5 - ci/expected/zero-prio-task.run | 3 - examples/async-delay.no_rs | 63 --- examples/async-infinite-loop.no_rs | 53 -- examples/async-task-multiple-prios.rs | 92 ---- examples/async-task.rs | 70 --- examples/async-timeout.no_rs | 87 --- examples/big-struct-opt.rs | 80 --- examples/binds.rs | 54 -- examples/cancel-reschedule.no_rs | 73 --- examples/capacity.no_rs | 49 -- examples/cfg-monotonic.rs | 121 ----- examples/cfg-whole-task.no_rs | 94 ---- examples/common.no_rs | 102 ---- examples/complex.rs | 129 ----- examples/declared_locals.rs | 47 -- examples/destructure.rs | 57 -- examples/extern_binds.rs | 54 -- examples/extern_spawn.rs | 41 -- examples/generics.rs | 67 --- examples/hardware.rs | 58 -- examples/idle-wfi.rs | 48 -- examples/idle.rs | 41 -- examples/init.rs | 42 -- examples/locals.rs | 87 --- examples/lock-free.no_rs | 50 -- examples/lock.rs | 73 --- examples/message.no_rs | 52 -- examples/message_passing.no_rs | 37 -- examples/multilock.rs | 57 -- examples/not-sync.rs | 69 --- examples/only-shared-access.rs | 44 -- examples/periodic-at.no_rs | 49 -- examples/periodic-at2.no_rs | 61 --- examples/periodic.no_rs | 48 -- examples/peripherals-taken.rs | 28 - examples/pool.no_rs | 70 --- examples/preempt.rs | 47 -- examples/ramfunc.rs | 50 -- examples/resource-user-struct.rs | 72 --- examples/schedule.no_rs | 64 --- examples/shared.rs | 51 -- examples/smallest.rs | 25 - examples/spawn.rs | 36 -- examples/static.rs | 61 --- examples/t-binds.rs | 45 -- examples/t-cfg-resources.rs | 42 -- examples/t-htask-main.rs | 32 -- examples/t-idle-main.rs | 33 -- examples/t-late-not-send.rs | 48 -- examples/t-schedule.no_rs | 136 ----- examples/t-spawn.no_rs | 69 --- examples/task.rs | 58 -- examples/zero-prio-task.rs | 60 -- macros/.gitignore | 2 - macros/Cargo.toml | 41 -- macros/src/analyze.rs | 49 -- macros/src/bindings.rs | 1 - macros/src/check.rs | 70 --- macros/src/codegen.rs | 75 --- macros/src/codegen/assertions.rs | 53 -- macros/src/codegen/async_dispatchers.rs | 89 --- macros/src/codegen/hardware_tasks.rs | 87 --- macros/src/codegen/idle.rs | 58 -- macros/src/codegen/init.rs | 95 ---- macros/src/codegen/local_resources.rs | 65 --- macros/src/codegen/local_resources_struct.rs | 102 ---- macros/src/codegen/main.rs | 52 -- macros/src/codegen/module.rs | 194 ------- macros/src/codegen/post_init.rs | 47 -- macros/src/codegen/pre_init.rs | 85 --- macros/src/codegen/shared_resources.rs | 183 ------- macros/src/codegen/shared_resources_struct.rs | 119 ---- macros/src/codegen/software_tasks.rs | 64 --- macros/src/codegen/util.rs | 238 -------- macros/src/lib.rs | 92 ---- macros/src/syntax.rs | 121 ----- macros/src/syntax/.github/bors.toml | 3 - macros/src/syntax/.github/workflows/build.yml | 213 -------- macros/src/syntax/.github/workflows/changelog.yml | 28 - .../workflows/properties/build.properties.json | 6 - macros/src/syntax/.gitignore | 4 - macros/src/syntax/.travis.yml | 31 -- macros/src/syntax/accessors.rs | 113 ---- macros/src/syntax/analyze.rs | 417 -------------- macros/src/syntax/ast.rs | 335 ------------ macros/src/syntax/check.rs | 66 --- macros/src/syntax/optimize.rs | 36 -- macros/src/syntax/parse.rs | 363 ------------- macros/src/syntax/parse/app.rs | 480 ---------------- macros/src/syntax/parse/hardware_task.rs | 76 --- macros/src/syntax/parse/idle.rs | 42 -- macros/src/syntax/parse/init.rs | 51 -- macros/src/syntax/parse/resource.rs | 55 -- macros/src/syntax/parse/software_task.rs | 76 --- macros/src/syntax/parse/util.rs | 338 ------------ macros/src/tests.rs | 4 - macros/src/tests/single.rs | 40 -- macros/tests/ui.rs | 7 - macros/ui/extern-interrupt-used.rs | 16 - macros/ui/extern-interrupt-used.stderr | 5 - macros/ui/idle-double-local.rs | 9 - macros/ui/idle-double-local.stderr | 5 - macros/ui/idle-double-shared.rs | 9 - macros/ui/idle-double-shared.stderr | 5 - macros/ui/idle-input.rs | 9 - macros/ui/idle-input.stderr | 5 - macros/ui/idle-no-context.rs | 9 - macros/ui/idle-no-context.stderr | 5 - macros/ui/idle-not-divergent.rs | 7 - macros/ui/idle-not-divergent.stderr | 5 - macros/ui/idle-output.rs | 9 - macros/ui/idle-output.stderr | 5 - macros/ui/idle-pub.rs | 9 - macros/ui/idle-pub.stderr | 5 - macros/ui/idle-unsafe.rs | 9 - macros/ui/idle-unsafe.stderr | 5 - macros/ui/init-divergent.rs | 13 - macros/ui/init-divergent.stderr | 5 - macros/ui/init-double-local.rs | 7 - macros/ui/init-double-local.stderr | 5 - macros/ui/init-double-shared.rs | 7 - macros/ui/init-double-shared.stderr | 5 - macros/ui/init-input.rs | 13 - macros/ui/init-input.stderr | 5 - macros/ui/init-no-context.rs | 13 - macros/ui/init-no-context.stderr | 5 - macros/ui/init-output.rs | 9 - macros/ui/init-output.stderr | 5 - macros/ui/init-pub.rs | 13 - macros/ui/init-pub.stderr | 5 - macros/ui/init-unsafe.rs | 7 - macros/ui/init-unsafe.stderr | 5 - macros/ui/interrupt-double.rs | 10 - macros/ui/interrupt-double.stderr | 5 - macros/ui/local-collision-2.rs | 18 - macros/ui/local-collision-2.stderr | 17 - macros/ui/local-collision.rs | 21 - macros/ui/local-collision.stderr | 11 - macros/ui/local-malformed-1.rs | 16 - macros/ui/local-malformed-1.stderr | 5 - macros/ui/local-malformed-2.rs | 16 - macros/ui/local-malformed-2.stderr | 5 - macros/ui/local-malformed-3.rs | 16 - macros/ui/local-malformed-3.stderr | 5 - macros/ui/local-malformed-4.rs | 16 - macros/ui/local-malformed-4.stderr | 5 - macros/ui/local-not-declared.rs | 16 - macros/ui/local-not-declared.stderr | 5 - macros/ui/local-pub.rs | 15 - macros/ui/local-pub.stderr | 5 - macros/ui/local-shared-attribute.rs | 21 - macros/ui/local-shared-attribute.stderr | 6 - macros/ui/local-shared.rs | 28 - macros/ui/local-shared.stderr | 11 - macros/ui/shared-lock-free.rs | 38 -- macros/ui/shared-lock-free.stderr | 17 - macros/ui/shared-not-declared.rs | 16 - macros/ui/shared-not-declared.stderr | 5 - macros/ui/shared-pub.rs | 9 - macros/ui/shared-pub.stderr | 5 - macros/ui/task-divergent.rs | 9 - macros/ui/task-divergent.stderr | 5 - macros/ui/task-double-local.rs | 7 - macros/ui/task-double-local.stderr | 5 - macros/ui/task-double-priority.rs | 7 - macros/ui/task-double-priority.stderr | 5 - macros/ui/task-double-shared.rs | 7 - macros/ui/task-double-shared.stderr | 5 - macros/ui/task-idle.rs | 13 - macros/ui/task-idle.stderr | 5 - macros/ui/task-init.rs | 17 - macros/ui/task-init.stderr | 5 - macros/ui/task-interrupt.rs | 10 - macros/ui/task-interrupt.stderr | 5 - macros/ui/task-no-context.rs | 7 - macros/ui/task-no-context.stderr | 5 - macros/ui/task-priority-too-high.rs | 7 - macros/ui/task-priority-too-high.stderr | 5 - macros/ui/task-priority-too-low.rs | 7 - macros/ui/task-priority-too-low.stderr | 5 - macros/ui/task-pub.rs | 7 - macros/ui/task-pub.stderr | 5 - macros/ui/task-unsafe.rs | 7 - macros/ui/task-unsafe.stderr | 5 - macros/ui/task-zero-prio.rs | 19 - macros/ui/task-zero-prio.stderr | 5 - rtic-monotonics/.gitignore | 6 + rtic-monotonics/Cargo.toml | 12 + rtic-monotonics/rust-toolchain.toml | 4 + rtic-monotonics/src/lib.rs | 11 + rtic-monotonics/src/systick_monotonic.rs | 1 + rtic-timer/.gitignore | 6 + rtic-timer/Cargo.toml | 7 +- rtic-timer/rust-toolchain.toml | 4 + rtic-timer/src/lib.rs | 390 +++++++++---- rtic-timer/src/linked_list.rs | 173 ++++++ rtic-timer/src/monotonic.rs | 60 ++ rtic-timer/src/sll.rs | 421 -------------- rtic/.cargo/config.toml | 13 + rtic/.gitignore | 6 + rtic/CHANGELOG.md | 603 +++++++++++++++++++++ rtic/Cargo.toml | 80 +++ rtic/build.rs | 25 + rtic/ci/expected/async-delay.run | 7 + rtic/ci/expected/async-infinite-loop.run | 6 + rtic/ci/expected/async-task-multiple-prios.run | 6 + rtic/ci/expected/async-task.run | 5 + rtic/ci/expected/async-timeout.run | 5 + rtic/ci/expected/big-struct-opt.run | 3 + rtic/ci/expected/binds.run | 4 + rtic/ci/expected/cancel-reschedule.run | 3 + rtic/ci/expected/capacity.run | 5 + rtic/ci/expected/cfg-whole-task.run | 0 rtic/ci/expected/common.run | 0 rtic/ci/expected/complex.run | 47 ++ rtic/ci/expected/declared_locals.run | 0 rtic/ci/expected/destructure.run | 2 + rtic/ci/expected/extern_binds.run | 4 + rtic/ci/expected/extern_spawn.run | 1 + rtic/ci/expected/generics.run | 6 + rtic/ci/expected/hardware.run | 4 + rtic/ci/expected/idle-wfi.run | 2 + rtic/ci/expected/idle.run | 2 + rtic/ci/expected/init.run | 1 + rtic/ci/expected/locals.run | 3 + rtic/ci/expected/lock-free.run | 2 + rtic/ci/expected/lock.run | 5 + rtic/ci/expected/message.run | 6 + rtic/ci/expected/message_passing.run | 3 + rtic/ci/expected/multilock.run | 1 + rtic/ci/expected/not-sync.run | 3 + rtic/ci/expected/only-shared-access.run | 2 + rtic/ci/expected/periodic-at.run | 4 + rtic/ci/expected/periodic-at2.run | 7 + rtic/ci/expected/periodic.run | 4 + rtic/ci/expected/peripherals-taken.run | 0 rtic/ci/expected/pool.run | 0 rtic/ci/expected/preempt.run | 5 + rtic/ci/expected/ramfunc.run | 1 + rtic/ci/expected/ramfunc.run.grep.bar | 1 + rtic/ci/expected/ramfunc.run.grep.foo | 1 + rtic/ci/expected/resource-user-struct.run | 2 + rtic/ci/expected/schedule.run | 4 + rtic/ci/expected/shared.run | 1 + rtic/ci/expected/smallest.run | 0 rtic/ci/expected/spawn.run | 2 + rtic/ci/expected/static.run | 3 + rtic/ci/expected/t-binds.run | 0 rtic/ci/expected/t-cfg-resources.run | 0 rtic/ci/expected/t-htask-main.run | 0 rtic/ci/expected/t-idle-main.run | 0 rtic/ci/expected/t-late-not-send.run | 0 rtic/ci/expected/t-schedule.run | 0 rtic/ci/expected/t-spawn.run | 0 rtic/ci/expected/task.run | 5 + rtic/ci/expected/zero-prio-task.run | 3 + rtic/examples/async-delay.no_rs | 63 +++ rtic/examples/async-infinite-loop.no_rs | 53 ++ rtic/examples/async-task-multiple-prios.rs | 92 ++++ rtic/examples/async-task.rs | 70 +++ rtic/examples/async-timeout.no_rs | 87 +++ rtic/examples/big-struct-opt.rs | 80 +++ rtic/examples/binds.rs | 54 ++ rtic/examples/cancel-reschedule.no_rs | 73 +++ rtic/examples/capacity.no_rs | 49 ++ rtic/examples/cfg-whole-task.no_rs | 94 ++++ rtic/examples/common.no_rs | 102 ++++ rtic/examples/complex.rs | 129 +++++ rtic/examples/declared_locals.rs | 47 ++ rtic/examples/destructure.rs | 57 ++ rtic/examples/extern_binds.rs | 54 ++ rtic/examples/extern_spawn.rs | 41 ++ rtic/examples/generics.rs | 67 +++ rtic/examples/hardware.rs | 58 ++ rtic/examples/idle-wfi.rs | 48 ++ rtic/examples/idle.rs | 41 ++ rtic/examples/init.rs | 42 ++ rtic/examples/locals.rs | 87 +++ rtic/examples/lock-free.no_rs | 50 ++ rtic/examples/lock.rs | 73 +++ rtic/examples/message.no_rs | 52 ++ rtic/examples/message_passing.no_rs | 37 ++ rtic/examples/multilock.rs | 57 ++ rtic/examples/not-sync.rs | 69 +++ rtic/examples/only-shared-access.rs | 44 ++ rtic/examples/periodic-at.no_rs | 49 ++ rtic/examples/periodic-at2.no_rs | 61 +++ rtic/examples/periodic.no_rs | 48 ++ rtic/examples/peripherals-taken.rs | 28 + rtic/examples/pool.no_rs | 70 +++ rtic/examples/preempt.rs | 47 ++ rtic/examples/ramfunc.rs | 50 ++ rtic/examples/resource-user-struct.rs | 72 +++ rtic/examples/schedule.no_rs | 64 +++ rtic/examples/shared.rs | 51 ++ rtic/examples/smallest.rs | 25 + rtic/examples/spawn.rs | 36 ++ rtic/examples/static.rs | 61 +++ rtic/examples/t-binds.rs | 45 ++ rtic/examples/t-cfg-resources.rs | 42 ++ rtic/examples/t-htask-main.rs | 32 ++ rtic/examples/t-idle-main.rs | 33 ++ rtic/examples/t-late-not-send.rs | 48 ++ rtic/examples/t-schedule.no_rs | 136 +++++ rtic/examples/t-spawn.no_rs | 69 +++ rtic/examples/task.rs | 58 ++ rtic/examples/zero-prio-task.rs | 60 ++ rtic/macros/.gitignore | 2 + rtic/macros/Cargo.toml | 41 ++ rtic/macros/src/analyze.rs | 49 ++ rtic/macros/src/bindings.rs | 1 + rtic/macros/src/check.rs | 70 +++ rtic/macros/src/codegen.rs | 75 +++ rtic/macros/src/codegen/assertions.rs | 53 ++ rtic/macros/src/codegen/async_dispatchers.rs | 89 +++ rtic/macros/src/codegen/hardware_tasks.rs | 87 +++ rtic/macros/src/codegen/idle.rs | 58 ++ rtic/macros/src/codegen/init.rs | 95 ++++ rtic/macros/src/codegen/local_resources.rs | 65 +++ rtic/macros/src/codegen/local_resources_struct.rs | 102 ++++ rtic/macros/src/codegen/main.rs | 52 ++ rtic/macros/src/codegen/module.rs | 194 +++++++ rtic/macros/src/codegen/post_init.rs | 47 ++ rtic/macros/src/codegen/pre_init.rs | 85 +++ rtic/macros/src/codegen/shared_resources.rs | 183 +++++++ rtic/macros/src/codegen/shared_resources_struct.rs | 119 ++++ rtic/macros/src/codegen/software_tasks.rs | 64 +++ rtic/macros/src/codegen/util.rs | 238 ++++++++ rtic/macros/src/lib.rs | 92 ++++ rtic/macros/src/syntax.rs | 121 +++++ rtic/macros/src/syntax/.github/bors.toml | 3 + rtic/macros/src/syntax/.github/workflows/build.yml | 213 ++++++++ .../src/syntax/.github/workflows/changelog.yml | 28 + .../workflows/properties/build.properties.json | 6 + rtic/macros/src/syntax/.gitignore | 4 + rtic/macros/src/syntax/.travis.yml | 31 ++ rtic/macros/src/syntax/accessors.rs | 113 ++++ rtic/macros/src/syntax/analyze.rs | 417 ++++++++++++++ rtic/macros/src/syntax/ast.rs | 335 ++++++++++++ rtic/macros/src/syntax/check.rs | 66 +++ rtic/macros/src/syntax/optimize.rs | 36 ++ rtic/macros/src/syntax/parse.rs | 363 +++++++++++++ rtic/macros/src/syntax/parse/app.rs | 480 ++++++++++++++++ rtic/macros/src/syntax/parse/hardware_task.rs | 76 +++ rtic/macros/src/syntax/parse/idle.rs | 42 ++ rtic/macros/src/syntax/parse/init.rs | 51 ++ rtic/macros/src/syntax/parse/resource.rs | 55 ++ rtic/macros/src/syntax/parse/software_task.rs | 76 +++ rtic/macros/src/syntax/parse/util.rs | 338 ++++++++++++ rtic/macros/tests/ui.rs | 7 + rtic/macros/ui/extern-interrupt-used.rs | 16 + rtic/macros/ui/extern-interrupt-used.stderr | 5 + rtic/macros/ui/idle-double-local.rs | 9 + rtic/macros/ui/idle-double-local.stderr | 5 + rtic/macros/ui/idle-double-shared.rs | 9 + rtic/macros/ui/idle-double-shared.stderr | 5 + rtic/macros/ui/idle-input.rs | 9 + rtic/macros/ui/idle-input.stderr | 5 + rtic/macros/ui/idle-no-context.rs | 9 + rtic/macros/ui/idle-no-context.stderr | 5 + rtic/macros/ui/idle-not-divergent.rs | 7 + rtic/macros/ui/idle-not-divergent.stderr | 5 + rtic/macros/ui/idle-output.rs | 9 + rtic/macros/ui/idle-output.stderr | 5 + rtic/macros/ui/idle-pub.rs | 9 + rtic/macros/ui/idle-pub.stderr | 5 + rtic/macros/ui/idle-unsafe.rs | 9 + rtic/macros/ui/idle-unsafe.stderr | 5 + rtic/macros/ui/init-divergent.rs | 13 + rtic/macros/ui/init-divergent.stderr | 5 + rtic/macros/ui/init-double-local.rs | 7 + rtic/macros/ui/init-double-local.stderr | 5 + rtic/macros/ui/init-double-shared.rs | 7 + rtic/macros/ui/init-double-shared.stderr | 5 + rtic/macros/ui/init-input.rs | 13 + rtic/macros/ui/init-input.stderr | 5 + rtic/macros/ui/init-no-context.rs | 13 + rtic/macros/ui/init-no-context.stderr | 5 + rtic/macros/ui/init-output.rs | 9 + rtic/macros/ui/init-output.stderr | 5 + rtic/macros/ui/init-pub.rs | 13 + rtic/macros/ui/init-pub.stderr | 5 + rtic/macros/ui/init-unsafe.rs | 7 + rtic/macros/ui/init-unsafe.stderr | 5 + rtic/macros/ui/interrupt-double.rs | 10 + rtic/macros/ui/interrupt-double.stderr | 5 + rtic/macros/ui/local-collision-2.rs | 18 + rtic/macros/ui/local-collision-2.stderr | 17 + rtic/macros/ui/local-collision.rs | 21 + rtic/macros/ui/local-collision.stderr | 11 + rtic/macros/ui/local-malformed-1.rs | 16 + rtic/macros/ui/local-malformed-1.stderr | 5 + rtic/macros/ui/local-malformed-2.rs | 16 + rtic/macros/ui/local-malformed-2.stderr | 5 + rtic/macros/ui/local-malformed-3.rs | 16 + rtic/macros/ui/local-malformed-3.stderr | 5 + rtic/macros/ui/local-malformed-4.rs | 16 + rtic/macros/ui/local-malformed-4.stderr | 5 + rtic/macros/ui/local-not-declared.rs | 16 + rtic/macros/ui/local-not-declared.stderr | 5 + rtic/macros/ui/local-pub.rs | 15 + rtic/macros/ui/local-pub.stderr | 5 + rtic/macros/ui/local-shared-attribute.rs | 21 + rtic/macros/ui/local-shared-attribute.stderr | 6 + rtic/macros/ui/local-shared.rs | 28 + rtic/macros/ui/local-shared.stderr | 11 + rtic/macros/ui/shared-lock-free.rs | 38 ++ rtic/macros/ui/shared-lock-free.stderr | 17 + rtic/macros/ui/shared-not-declared.rs | 16 + rtic/macros/ui/shared-not-declared.stderr | 5 + rtic/macros/ui/shared-pub.rs | 9 + rtic/macros/ui/shared-pub.stderr | 5 + rtic/macros/ui/task-divergent.rs | 9 + rtic/macros/ui/task-divergent.stderr | 5 + rtic/macros/ui/task-double-local.rs | 7 + rtic/macros/ui/task-double-local.stderr | 5 + rtic/macros/ui/task-double-priority.rs | 7 + rtic/macros/ui/task-double-priority.stderr | 5 + rtic/macros/ui/task-double-shared.rs | 7 + rtic/macros/ui/task-double-shared.stderr | 5 + rtic/macros/ui/task-idle.rs | 13 + rtic/macros/ui/task-idle.stderr | 5 + rtic/macros/ui/task-init.rs | 17 + rtic/macros/ui/task-init.stderr | 5 + rtic/macros/ui/task-interrupt.rs | 10 + rtic/macros/ui/task-interrupt.stderr | 5 + rtic/macros/ui/task-no-context.rs | 7 + rtic/macros/ui/task-no-context.stderr | 5 + rtic/macros/ui/task-priority-too-high.rs | 7 + rtic/macros/ui/task-priority-too-high.stderr | 5 + rtic/macros/ui/task-priority-too-low.rs | 7 + rtic/macros/ui/task-priority-too-low.stderr | 5 + rtic/macros/ui/task-pub.rs | 7 + rtic/macros/ui/task-pub.stderr | 5 + rtic/macros/ui/task-unsafe.rs | 7 + rtic/macros/ui/task-unsafe.stderr | 5 + rtic/macros/ui/task-zero-prio.rs | 19 + rtic/macros/ui/task-zero-prio.stderr | 5 + rtic/rust-toolchain.toml | 4 + rtic/src/export.rs | 324 +++++++++++ rtic/src/export/executor.rs | 110 ++++ rtic/src/lib.rs | 121 +++++ rtic/tests/tests.rs | 7 + rtic/ui/exception-invalid.rs | 18 + rtic/ui/exception-invalid.stderr | 5 + rtic/ui/extern-interrupt-not-enough.rs | 18 + rtic/ui/extern-interrupt-not-enough.stderr | 5 + rtic/ui/extern-interrupt-used.rs | 18 + rtic/ui/extern-interrupt-used.stderr | 5 + rtic/ui/task-priority-too-high.rs | 44 ++ rtic/ui/task-priority-too-high.stderr | 15 + rtic/ui/unknown-interrupt.rs | 15 + rtic/ui/unknown-interrupt.stderr | 5 + rtic/ui/v6m-interrupt-not-enough.rs_no | 54 ++ rtic/xtask/Cargo.toml | 9 + rtic/xtask/src/build.rs | 13 + rtic/xtask/src/command.rs | 172 ++++++ rtic/xtask/src/main.rs | 176 ++++++ rust-toolchain.toml | 4 - src/export.rs | 324 ----------- src/export/executor.rs | 110 ---- src/lib.rs | 121 ----- tests/tests.rs | 7 - ui/exception-invalid.rs | 18 - ui/exception-invalid.stderr | 5 - ui/extern-interrupt-not-enough.rs | 18 - ui/extern-interrupt-not-enough.stderr | 5 - ui/extern-interrupt-used.rs | 18 - ui/extern-interrupt-used.stderr | 5 - ui/task-priority-too-high.rs | 44 -- ui/task-priority-too-high.stderr | 15 - ui/unknown-interrupt.rs | 15 - ui/unknown-interrupt.stderr | 5 - ui/v6m-interrupt-not-enough.rs_no | 54 -- xtask/Cargo.toml | 9 - xtask/src/build.rs | 13 - xtask/src/command.rs | 172 ------ xtask/src/main.rs | 176 ------ 535 files changed, 11202 insertions(+), 11308 deletions(-) delete mode 100644 .cargo/config.toml delete mode 100644 CHANGELOG.md delete mode 100644 Cargo.toml delete mode 100644 build.rs delete mode 100644 ci/expected/async-delay.run delete mode 100644 ci/expected/async-infinite-loop.run delete mode 100644 ci/expected/async-task-multiple-prios.run delete mode 100644 ci/expected/async-task.run delete mode 100644 ci/expected/async-timeout.run delete mode 100644 ci/expected/big-struct-opt.run delete mode 100644 ci/expected/binds.run delete mode 100644 ci/expected/cancel-reschedule.run delete mode 100644 ci/expected/capacity.run delete mode 100644 ci/expected/cfg-monotonic.run delete mode 100644 ci/expected/cfg-whole-task.run delete mode 100644 ci/expected/common.run delete mode 100644 ci/expected/complex.run delete mode 100644 ci/expected/declared_locals.run delete mode 100644 ci/expected/destructure.run delete mode 100644 ci/expected/extern_binds.run delete mode 100644 ci/expected/extern_spawn.run delete mode 100644 ci/expected/generics.run delete mode 100644 ci/expected/hardware.run delete mode 100644 ci/expected/idle-wfi.run delete mode 100644 ci/expected/idle.run delete mode 100644 ci/expected/init.run delete mode 100644 ci/expected/locals.run delete mode 100644 ci/expected/lock-free.run delete mode 100644 ci/expected/lock.run delete mode 100644 ci/expected/message.run delete mode 100644 ci/expected/message_passing.run delete mode 100644 ci/expected/multilock.run delete mode 100644 ci/expected/not-sync.run delete mode 100644 ci/expected/only-shared-access.run delete mode 100644 ci/expected/periodic-at.run delete mode 100644 ci/expected/periodic-at2.run delete mode 100644 ci/expected/periodic.run delete mode 100644 ci/expected/peripherals-taken.run delete mode 100644 ci/expected/pool.run delete mode 100644 ci/expected/preempt.run delete mode 100644 ci/expected/ramfunc.run delete mode 100644 ci/expected/ramfunc.run.grep.bar delete mode 100644 ci/expected/ramfunc.run.grep.foo delete mode 100644 ci/expected/resource-user-struct.run delete mode 100644 ci/expected/schedule.run delete mode 100644 ci/expected/shared.run delete mode 100644 ci/expected/smallest.run delete mode 100644 ci/expected/spawn.run delete mode 100644 ci/expected/static.run delete mode 100644 ci/expected/t-binds.run delete mode 100644 ci/expected/t-cfg-resources.run delete mode 100644 ci/expected/t-htask-main.run delete mode 100644 ci/expected/t-idle-main.run delete mode 100644 ci/expected/t-late-not-send.run delete mode 100644 ci/expected/t-schedule.run delete mode 100644 ci/expected/t-spawn.run delete mode 100644 ci/expected/task.run delete mode 100644 ci/expected/zero-prio-task.run delete mode 100644 examples/async-delay.no_rs delete mode 100644 examples/async-infinite-loop.no_rs delete mode 100644 examples/async-task-multiple-prios.rs delete mode 100644 examples/async-task.rs delete mode 100644 examples/async-timeout.no_rs delete mode 100644 examples/big-struct-opt.rs delete mode 100644 examples/binds.rs delete mode 100644 examples/cancel-reschedule.no_rs delete mode 100644 examples/capacity.no_rs delete mode 100644 examples/cfg-monotonic.rs delete mode 100644 examples/cfg-whole-task.no_rs delete mode 100644 examples/common.no_rs delete mode 100644 examples/complex.rs delete mode 100644 examples/declared_locals.rs delete mode 100644 examples/destructure.rs delete mode 100644 examples/extern_binds.rs delete mode 100644 examples/extern_spawn.rs delete mode 100644 examples/generics.rs delete mode 100644 examples/hardware.rs delete mode 100644 examples/idle-wfi.rs delete mode 100644 examples/idle.rs delete mode 100644 examples/init.rs delete mode 100644 examples/locals.rs delete mode 100644 examples/lock-free.no_rs delete mode 100644 examples/lock.rs delete mode 100644 examples/message.no_rs delete mode 100644 examples/message_passing.no_rs delete mode 100644 examples/multilock.rs delete mode 100644 examples/not-sync.rs delete mode 100644 examples/only-shared-access.rs delete mode 100644 examples/periodic-at.no_rs delete mode 100644 examples/periodic-at2.no_rs delete mode 100644 examples/periodic.no_rs delete mode 100644 examples/peripherals-taken.rs delete mode 100644 examples/pool.no_rs delete mode 100644 examples/preempt.rs delete mode 100644 examples/ramfunc.rs delete mode 100644 examples/resource-user-struct.rs delete mode 100644 examples/schedule.no_rs delete mode 100644 examples/shared.rs delete mode 100644 examples/smallest.rs delete mode 100644 examples/spawn.rs delete mode 100644 examples/static.rs delete mode 100644 examples/t-binds.rs delete mode 100644 examples/t-cfg-resources.rs delete mode 100644 examples/t-htask-main.rs delete mode 100644 examples/t-idle-main.rs delete mode 100644 examples/t-late-not-send.rs delete mode 100644 examples/t-schedule.no_rs delete mode 100644 examples/t-spawn.no_rs delete mode 100644 examples/task.rs delete mode 100644 examples/zero-prio-task.rs delete mode 100644 macros/.gitignore delete mode 100644 macros/Cargo.toml delete mode 100644 macros/src/analyze.rs delete mode 100644 macros/src/bindings.rs delete mode 100644 macros/src/check.rs delete mode 100644 macros/src/codegen.rs delete mode 100644 macros/src/codegen/assertions.rs delete mode 100644 macros/src/codegen/async_dispatchers.rs delete mode 100644 macros/src/codegen/hardware_tasks.rs delete mode 100644 macros/src/codegen/idle.rs delete mode 100644 macros/src/codegen/init.rs delete mode 100644 macros/src/codegen/local_resources.rs delete mode 100644 macros/src/codegen/local_resources_struct.rs delete mode 100644 macros/src/codegen/main.rs delete mode 100644 macros/src/codegen/module.rs delete mode 100644 macros/src/codegen/post_init.rs delete mode 100644 macros/src/codegen/pre_init.rs delete mode 100644 macros/src/codegen/shared_resources.rs delete mode 100644 macros/src/codegen/shared_resources_struct.rs delete mode 100644 macros/src/codegen/software_tasks.rs delete mode 100644 macros/src/codegen/util.rs delete mode 100644 macros/src/lib.rs delete mode 100644 macros/src/syntax.rs delete mode 100644 macros/src/syntax/.github/bors.toml delete mode 100644 macros/src/syntax/.github/workflows/build.yml delete mode 100644 macros/src/syntax/.github/workflows/changelog.yml delete mode 100644 macros/src/syntax/.github/workflows/properties/build.properties.json delete mode 100644 macros/src/syntax/.gitignore delete mode 100644 macros/src/syntax/.travis.yml delete mode 100644 macros/src/syntax/accessors.rs delete mode 100644 macros/src/syntax/analyze.rs delete mode 100644 macros/src/syntax/ast.rs delete mode 100644 macros/src/syntax/check.rs delete mode 100644 macros/src/syntax/optimize.rs delete mode 100644 macros/src/syntax/parse.rs delete mode 100644 macros/src/syntax/parse/app.rs delete mode 100644 macros/src/syntax/parse/hardware_task.rs delete mode 100644 macros/src/syntax/parse/idle.rs delete mode 100644 macros/src/syntax/parse/init.rs delete mode 100644 macros/src/syntax/parse/resource.rs delete mode 100644 macros/src/syntax/parse/software_task.rs delete mode 100644 macros/src/syntax/parse/util.rs delete mode 100644 macros/src/tests.rs delete mode 100644 macros/src/tests/single.rs delete mode 100644 macros/tests/ui.rs delete mode 100644 macros/ui/extern-interrupt-used.rs delete mode 100644 macros/ui/extern-interrupt-used.stderr delete mode 100644 macros/ui/idle-double-local.rs delete mode 100644 macros/ui/idle-double-local.stderr delete mode 100644 macros/ui/idle-double-shared.rs delete mode 100644 macros/ui/idle-double-shared.stderr delete mode 100644 macros/ui/idle-input.rs delete mode 100644 macros/ui/idle-input.stderr delete mode 100644 macros/ui/idle-no-context.rs delete mode 100644 macros/ui/idle-no-context.stderr delete mode 100644 macros/ui/idle-not-divergent.rs delete mode 100644 macros/ui/idle-not-divergent.stderr delete mode 100644 macros/ui/idle-output.rs delete mode 100644 macros/ui/idle-output.stderr delete mode 100644 macros/ui/idle-pub.rs delete mode 100644 macros/ui/idle-pub.stderr delete mode 100644 macros/ui/idle-unsafe.rs delete mode 100644 macros/ui/idle-unsafe.stderr delete mode 100644 macros/ui/init-divergent.rs delete mode 100644 macros/ui/init-divergent.stderr delete mode 100644 macros/ui/init-double-local.rs delete mode 100644 macros/ui/init-double-local.stderr delete mode 100644 macros/ui/init-double-shared.rs delete mode 100644 macros/ui/init-double-shared.stderr delete mode 100644 macros/ui/init-input.rs delete mode 100644 macros/ui/init-input.stderr delete mode 100644 macros/ui/init-no-context.rs delete mode 100644 macros/ui/init-no-context.stderr delete mode 100644 macros/ui/init-output.rs delete mode 100644 macros/ui/init-output.stderr delete mode 100644 macros/ui/init-pub.rs delete mode 100644 macros/ui/init-pub.stderr delete mode 100644 macros/ui/init-unsafe.rs delete mode 100644 macros/ui/init-unsafe.stderr delete mode 100644 macros/ui/interrupt-double.rs delete mode 100644 macros/ui/interrupt-double.stderr delete mode 100644 macros/ui/local-collision-2.rs delete mode 100644 macros/ui/local-collision-2.stderr delete mode 100644 macros/ui/local-collision.rs delete mode 100644 macros/ui/local-collision.stderr delete mode 100644 macros/ui/local-malformed-1.rs delete mode 100644 macros/ui/local-malformed-1.stderr delete mode 100644 macros/ui/local-malformed-2.rs delete mode 100644 macros/ui/local-malformed-2.stderr delete mode 100644 macros/ui/local-malformed-3.rs delete mode 100644 macros/ui/local-malformed-3.stderr delete mode 100644 macros/ui/local-malformed-4.rs delete mode 100644 macros/ui/local-malformed-4.stderr delete mode 100644 macros/ui/local-not-declared.rs delete mode 100644 macros/ui/local-not-declared.stderr delete mode 100644 macros/ui/local-pub.rs delete mode 100644 macros/ui/local-pub.stderr delete mode 100644 macros/ui/local-shared-attribute.rs delete mode 100644 macros/ui/local-shared-attribute.stderr delete mode 100644 macros/ui/local-shared.rs delete mode 100644 macros/ui/local-shared.stderr delete mode 100644 macros/ui/shared-lock-free.rs delete mode 100644 macros/ui/shared-lock-free.stderr delete mode 100644 macros/ui/shared-not-declared.rs delete mode 100644 macros/ui/shared-not-declared.stderr delete mode 100644 macros/ui/shared-pub.rs delete mode 100644 macros/ui/shared-pub.stderr delete mode 100644 macros/ui/task-divergent.rs delete mode 100644 macros/ui/task-divergent.stderr delete mode 100644 macros/ui/task-double-local.rs delete mode 100644 macros/ui/task-double-local.stderr delete mode 100644 macros/ui/task-double-priority.rs delete mode 100644 macros/ui/task-double-priority.stderr delete mode 100644 macros/ui/task-double-shared.rs delete mode 100644 macros/ui/task-double-shared.stderr delete mode 100644 macros/ui/task-idle.rs delete mode 100644 macros/ui/task-idle.stderr delete mode 100644 macros/ui/task-init.rs delete mode 100644 macros/ui/task-init.stderr delete mode 100644 macros/ui/task-interrupt.rs delete mode 100644 macros/ui/task-interrupt.stderr delete mode 100644 macros/ui/task-no-context.rs delete mode 100644 macros/ui/task-no-context.stderr delete mode 100644 macros/ui/task-priority-too-high.rs delete mode 100644 macros/ui/task-priority-too-high.stderr delete mode 100644 macros/ui/task-priority-too-low.rs delete mode 100644 macros/ui/task-priority-too-low.stderr delete mode 100644 macros/ui/task-pub.rs delete mode 100644 macros/ui/task-pub.stderr delete mode 100644 macros/ui/task-unsafe.rs delete mode 100644 macros/ui/task-unsafe.stderr delete mode 100644 macros/ui/task-zero-prio.rs delete mode 100644 macros/ui/task-zero-prio.stderr create mode 100644 rtic-monotonics/.gitignore create mode 100644 rtic-monotonics/Cargo.toml create mode 100644 rtic-monotonics/rust-toolchain.toml create mode 100644 rtic-monotonics/src/lib.rs create mode 100644 rtic-monotonics/src/systick_monotonic.rs create mode 100644 rtic-timer/.gitignore create mode 100644 rtic-timer/rust-toolchain.toml create mode 100644 rtic-timer/src/linked_list.rs create mode 100644 rtic-timer/src/monotonic.rs delete mode 100644 rtic-timer/src/sll.rs create mode 100644 rtic/.cargo/config.toml create mode 100644 rtic/.gitignore create mode 100644 rtic/CHANGELOG.md create mode 100644 rtic/Cargo.toml create mode 100644 rtic/build.rs create mode 100644 rtic/ci/expected/async-delay.run create mode 100644 rtic/ci/expected/async-infinite-loop.run create mode 100644 rtic/ci/expected/async-task-multiple-prios.run create mode 100644 rtic/ci/expected/async-task.run create mode 100644 rtic/ci/expected/async-timeout.run create mode 100644 rtic/ci/expected/big-struct-opt.run create mode 100644 rtic/ci/expected/binds.run create mode 100644 rtic/ci/expected/cancel-reschedule.run create mode 100644 rtic/ci/expected/capacity.run create mode 100644 rtic/ci/expected/cfg-whole-task.run create mode 100644 rtic/ci/expected/common.run create mode 100644 rtic/ci/expected/complex.run create mode 100644 rtic/ci/expected/declared_locals.run create mode 100644 rtic/ci/expected/destructure.run create mode 100644 rtic/ci/expected/extern_binds.run create mode 100644 rtic/ci/expected/extern_spawn.run create mode 100644 rtic/ci/expected/generics.run create mode 100644 rtic/ci/expected/hardware.run create mode 100644 rtic/ci/expected/idle-wfi.run create mode 100644 rtic/ci/expected/idle.run create mode 100644 rtic/ci/expected/init.run create mode 100644 rtic/ci/expected/locals.run create mode 100644 rtic/ci/expected/lock-free.run create mode 100644 rtic/ci/expected/lock.run create mode 100644 rtic/ci/expected/message.run create mode 100644 rtic/ci/expected/message_passing.run create mode 100644 rtic/ci/expected/multilock.run create mode 100644 rtic/ci/expected/not-sync.run create mode 100644 rtic/ci/expected/only-shared-access.run create mode 100644 rtic/ci/expected/periodic-at.run create mode 100644 rtic/ci/expected/periodic-at2.run create mode 100644 rtic/ci/expected/periodic.run create mode 100644 rtic/ci/expected/peripherals-taken.run create mode 100644 rtic/ci/expected/pool.run create mode 100644 rtic/ci/expected/preempt.run create mode 100644 rtic/ci/expected/ramfunc.run create mode 100644 rtic/ci/expected/ramfunc.run.grep.bar create mode 100644 rtic/ci/expected/ramfunc.run.grep.foo create mode 100644 rtic/ci/expected/resource-user-struct.run create mode 100644 rtic/ci/expected/schedule.run create mode 100644 rtic/ci/expected/shared.run create mode 100644 rtic/ci/expected/smallest.run create mode 100644 rtic/ci/expected/spawn.run create mode 100644 rtic/ci/expected/static.run create mode 100644 rtic/ci/expected/t-binds.run create mode 100644 rtic/ci/expected/t-cfg-resources.run create mode 100644 rtic/ci/expected/t-htask-main.run create mode 100644 rtic/ci/expected/t-idle-main.run create mode 100644 rtic/ci/expected/t-late-not-send.run create mode 100644 rtic/ci/expected/t-schedule.run create mode 100644 rtic/ci/expected/t-spawn.run create mode 100644 rtic/ci/expected/task.run create mode 100644 rtic/ci/expected/zero-prio-task.run create mode 100644 rtic/examples/async-delay.no_rs create mode 100644 rtic/examples/async-infinite-loop.no_rs create mode 100644 rtic/examples/async-task-multiple-prios.rs create mode 100644 rtic/examples/async-task.rs create mode 100644 rtic/examples/async-timeout.no_rs create mode 100644 rtic/examples/big-struct-opt.rs create mode 100644 rtic/examples/binds.rs create mode 100644 rtic/examples/cancel-reschedule.no_rs create mode 100644 rtic/examples/capacity.no_rs create mode 100644 rtic/examples/cfg-whole-task.no_rs create mode 100644 rtic/examples/common.no_rs create mode 100644 rtic/examples/complex.rs create mode 100644 rtic/examples/declared_locals.rs create mode 100644 rtic/examples/destructure.rs create mode 100644 rtic/examples/extern_binds.rs create mode 100644 rtic/examples/extern_spawn.rs create mode 100644 rtic/examples/generics.rs create mode 100644 rtic/examples/hardware.rs create mode 100644 rtic/examples/idle-wfi.rs create mode 100644 rtic/examples/idle.rs create mode 100644 rtic/examples/init.rs create mode 100644 rtic/examples/locals.rs create mode 100644 rtic/examples/lock-free.no_rs create mode 100644 rtic/examples/lock.rs create mode 100644 rtic/examples/message.no_rs create mode 100644 rtic/examples/message_passing.no_rs create mode 100644 rtic/examples/multilock.rs create mode 100644 rtic/examples/not-sync.rs create mode 100644 rtic/examples/only-shared-access.rs create mode 100644 rtic/examples/periodic-at.no_rs create mode 100644 rtic/examples/periodic-at2.no_rs create mode 100644 rtic/examples/periodic.no_rs create mode 100644 rtic/examples/peripherals-taken.rs create mode 100644 rtic/examples/pool.no_rs create mode 100644 rtic/examples/preempt.rs create mode 100644 rtic/examples/ramfunc.rs create mode 100644 rtic/examples/resource-user-struct.rs create mode 100644 rtic/examples/schedule.no_rs create mode 100644 rtic/examples/shared.rs create mode 100644 rtic/examples/smallest.rs create mode 100644 rtic/examples/spawn.rs create mode 100644 rtic/examples/static.rs create mode 100644 rtic/examples/t-binds.rs create mode 100644 rtic/examples/t-cfg-resources.rs create mode 100644 rtic/examples/t-htask-main.rs create mode 100644 rtic/examples/t-idle-main.rs create mode 100644 rtic/examples/t-late-not-send.rs create mode 100644 rtic/examples/t-schedule.no_rs create mode 100644 rtic/examples/t-spawn.no_rs create mode 100644 rtic/examples/task.rs create mode 100644 rtic/examples/zero-prio-task.rs create mode 100644 rtic/macros/.gitignore create mode 100644 rtic/macros/Cargo.toml create mode 100644 rtic/macros/src/analyze.rs create mode 100644 rtic/macros/src/bindings.rs create mode 100644 rtic/macros/src/check.rs create mode 100644 rtic/macros/src/codegen.rs create mode 100644 rtic/macros/src/codegen/assertions.rs create mode 100644 rtic/macros/src/codegen/async_dispatchers.rs create mode 100644 rtic/macros/src/codegen/hardware_tasks.rs create mode 100644 rtic/macros/src/codegen/idle.rs create mode 100644 rtic/macros/src/codegen/init.rs create mode 100644 rtic/macros/src/codegen/local_resources.rs create mode 100644 rtic/macros/src/codegen/local_resources_struct.rs create mode 100644 rtic/macros/src/codegen/main.rs create mode 100644 rtic/macros/src/codegen/module.rs create mode 100644 rtic/macros/src/codegen/post_init.rs create mode 100644 rtic/macros/src/codegen/pre_init.rs create mode 100644 rtic/macros/src/codegen/shared_resources.rs create mode 100644 rtic/macros/src/codegen/shared_resources_struct.rs create mode 100644 rtic/macros/src/codegen/software_tasks.rs create mode 100644 rtic/macros/src/codegen/util.rs create mode 100644 rtic/macros/src/lib.rs create mode 100644 rtic/macros/src/syntax.rs create mode 100644 rtic/macros/src/syntax/.github/bors.toml create mode 100644 rtic/macros/src/syntax/.github/workflows/build.yml create mode 100644 rtic/macros/src/syntax/.github/workflows/changelog.yml create mode 100644 rtic/macros/src/syntax/.github/workflows/properties/build.properties.json create mode 100644 rtic/macros/src/syntax/.gitignore create mode 100644 rtic/macros/src/syntax/.travis.yml create mode 100644 rtic/macros/src/syntax/accessors.rs create mode 100644 rtic/macros/src/syntax/analyze.rs create mode 100644 rtic/macros/src/syntax/ast.rs create mode 100644 rtic/macros/src/syntax/check.rs create mode 100644 rtic/macros/src/syntax/optimize.rs create mode 100644 rtic/macros/src/syntax/parse.rs create mode 100644 rtic/macros/src/syntax/parse/app.rs create mode 100644 rtic/macros/src/syntax/parse/hardware_task.rs create mode 100644 rtic/macros/src/syntax/parse/idle.rs create mode 100644 rtic/macros/src/syntax/parse/init.rs create mode 100644 rtic/macros/src/syntax/parse/resource.rs create mode 100644 rtic/macros/src/syntax/parse/software_task.rs create mode 100644 rtic/macros/src/syntax/parse/util.rs create mode 100644 rtic/macros/tests/ui.rs create mode 100644 rtic/macros/ui/extern-interrupt-used.rs create mode 100644 rtic/macros/ui/extern-interrupt-used.stderr create mode 100644 rtic/macros/ui/idle-double-local.rs create mode 100644 rtic/macros/ui/idle-double-local.stderr create mode 100644 rtic/macros/ui/idle-double-shared.rs create mode 100644 rtic/macros/ui/idle-double-shared.stderr create mode 100644 rtic/macros/ui/idle-input.rs create mode 100644 rtic/macros/ui/idle-input.stderr create mode 100644 rtic/macros/ui/idle-no-context.rs create mode 100644 rtic/macros/ui/idle-no-context.stderr create mode 100644 rtic/macros/ui/idle-not-divergent.rs create mode 100644 rtic/macros/ui/idle-not-divergent.stderr create mode 100644 rtic/macros/ui/idle-output.rs create mode 100644 rtic/macros/ui/idle-output.stderr create mode 100644 rtic/macros/ui/idle-pub.rs create mode 100644 rtic/macros/ui/idle-pub.stderr create mode 100644 rtic/macros/ui/idle-unsafe.rs create mode 100644 rtic/macros/ui/idle-unsafe.stderr create mode 100644 rtic/macros/ui/init-divergent.rs create mode 100644 rtic/macros/ui/init-divergent.stderr create mode 100644 rtic/macros/ui/init-double-local.rs create mode 100644 rtic/macros/ui/init-double-local.stderr create mode 100644 rtic/macros/ui/init-double-shared.rs create mode 100644 rtic/macros/ui/init-double-shared.stderr create mode 100644 rtic/macros/ui/init-input.rs create mode 100644 rtic/macros/ui/init-input.stderr create mode 100644 rtic/macros/ui/init-no-context.rs create mode 100644 rtic/macros/ui/init-no-context.stderr create mode 100644 rtic/macros/ui/init-output.rs create mode 100644 rtic/macros/ui/init-output.stderr create mode 100644 rtic/macros/ui/init-pub.rs create mode 100644 rtic/macros/ui/init-pub.stderr create mode 100644 rtic/macros/ui/init-unsafe.rs create mode 100644 rtic/macros/ui/init-unsafe.stderr create mode 100644 rtic/macros/ui/interrupt-double.rs create mode 100644 rtic/macros/ui/interrupt-double.stderr create mode 100644 rtic/macros/ui/local-collision-2.rs create mode 100644 rtic/macros/ui/local-collision-2.stderr create mode 100644 rtic/macros/ui/local-collision.rs create mode 100644 rtic/macros/ui/local-collision.stderr create mode 100644 rtic/macros/ui/local-malformed-1.rs create mode 100644 rtic/macros/ui/local-malformed-1.stderr create mode 100644 rtic/macros/ui/local-malformed-2.rs create mode 100644 rtic/macros/ui/local-malformed-2.stderr create mode 100644 rtic/macros/ui/local-malformed-3.rs create mode 100644 rtic/macros/ui/local-malformed-3.stderr create mode 100644 rtic/macros/ui/local-malformed-4.rs create mode 100644 rtic/macros/ui/local-malformed-4.stderr create mode 100644 rtic/macros/ui/local-not-declared.rs create mode 100644 rtic/macros/ui/local-not-declared.stderr create mode 100644 rtic/macros/ui/local-pub.rs create mode 100644 rtic/macros/ui/local-pub.stderr create mode 100644 rtic/macros/ui/local-shared-attribute.rs create mode 100644 rtic/macros/ui/local-shared-attribute.stderr create mode 100644 rtic/macros/ui/local-shared.rs create mode 100644 rtic/macros/ui/local-shared.stderr create mode 100644 rtic/macros/ui/shared-lock-free.rs create mode 100644 rtic/macros/ui/shared-lock-free.stderr create mode 100644 rtic/macros/ui/shared-not-declared.rs create mode 100644 rtic/macros/ui/shared-not-declared.stderr create mode 100644 rtic/macros/ui/shared-pub.rs create mode 100644 rtic/macros/ui/shared-pub.stderr create mode 100644 rtic/macros/ui/task-divergent.rs create mode 100644 rtic/macros/ui/task-divergent.stderr create mode 100644 rtic/macros/ui/task-double-local.rs create mode 100644 rtic/macros/ui/task-double-local.stderr create mode 100644 rtic/macros/ui/task-double-priority.rs create mode 100644 rtic/macros/ui/task-double-priority.stderr create mode 100644 rtic/macros/ui/task-double-shared.rs create mode 100644 rtic/macros/ui/task-double-shared.stderr create mode 100644 rtic/macros/ui/task-idle.rs create mode 100644 rtic/macros/ui/task-idle.stderr create mode 100644 rtic/macros/ui/task-init.rs create mode 100644 rtic/macros/ui/task-init.stderr create mode 100644 rtic/macros/ui/task-interrupt.rs create mode 100644 rtic/macros/ui/task-interrupt.stderr create mode 100644 rtic/macros/ui/task-no-context.rs create mode 100644 rtic/macros/ui/task-no-context.stderr create mode 100644 rtic/macros/ui/task-priority-too-high.rs create mode 100644 rtic/macros/ui/task-priority-too-high.stderr create mode 100644 rtic/macros/ui/task-priority-too-low.rs create mode 100644 rtic/macros/ui/task-priority-too-low.stderr create mode 100644 rtic/macros/ui/task-pub.rs create mode 100644 rtic/macros/ui/task-pub.stderr create mode 100644 rtic/macros/ui/task-unsafe.rs create mode 100644 rtic/macros/ui/task-unsafe.stderr create mode 100644 rtic/macros/ui/task-zero-prio.rs create mode 100644 rtic/macros/ui/task-zero-prio.stderr create mode 100644 rtic/rust-toolchain.toml create mode 100644 rtic/src/export.rs create mode 100644 rtic/src/export/executor.rs create mode 100644 rtic/src/lib.rs create mode 100644 rtic/tests/tests.rs create mode 100644 rtic/ui/exception-invalid.rs create mode 100644 rtic/ui/exception-invalid.stderr create mode 100644 rtic/ui/extern-interrupt-not-enough.rs create mode 100644 rtic/ui/extern-interrupt-not-enough.stderr create mode 100644 rtic/ui/extern-interrupt-used.rs create mode 100644 rtic/ui/extern-interrupt-used.stderr create mode 100644 rtic/ui/task-priority-too-high.rs create mode 100644 rtic/ui/task-priority-too-high.stderr create mode 100644 rtic/ui/unknown-interrupt.rs create mode 100644 rtic/ui/unknown-interrupt.stderr create mode 100644 rtic/ui/v6m-interrupt-not-enough.rs_no create mode 100644 rtic/xtask/Cargo.toml create mode 100644 rtic/xtask/src/build.rs create mode 100644 rtic/xtask/src/command.rs create mode 100644 rtic/xtask/src/main.rs delete mode 100644 rust-toolchain.toml delete mode 100644 src/export.rs delete mode 100644 src/export/executor.rs delete mode 100644 src/lib.rs delete mode 100644 tests/tests.rs delete mode 100644 ui/exception-invalid.rs delete mode 100644 ui/exception-invalid.stderr delete mode 100644 ui/extern-interrupt-not-enough.rs delete mode 100644 ui/extern-interrupt-not-enough.stderr delete mode 100644 ui/extern-interrupt-used.rs delete mode 100644 ui/extern-interrupt-used.stderr delete mode 100644 ui/task-priority-too-high.rs delete mode 100644 ui/task-priority-too-high.stderr delete mode 100644 ui/unknown-interrupt.rs delete mode 100644 ui/unknown-interrupt.stderr delete mode 100644 ui/v6m-interrupt-not-enough.rs_no delete mode 100644 xtask/Cargo.toml delete mode 100644 xtask/src/build.rs delete mode 100644 xtask/src/command.rs delete mode 100644 xtask/src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index d70faef..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,13 +0,0 @@ -[alias] -xtask = "run --package xtask --" - -[target.thumbv6m-none-eabi] -runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" - -[target.thumbv7m-none-eabi] -runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" - -[target.'cfg(all(target_arch = "arm", target_os = "none"))'] -rustflags = [ - "-C", "link-arg=-Tlink.x", -] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d172278..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,603 +0,0 @@ -# Change Log - -All notable changes to this project will be documented in this file. -This project adheres to [Semantic Versioning](http://semver.org/). - -For each category, *Added*, *Changed*, *Fixed* add new entries at the top! - -## [Unreleased] - -### Added - -### Fixed - -### Changed - -## [v1.1.4] - 2023-02-26 - -### Added - -- CFG: Support #[cfg] on HW task, cleanup for SW tasks -- CFG: Slightly improved support for #[cfg] on Monotonics -- CI: Check examples also for thumbv8.{base,main} -- Allow custom `link_section` attributes for late resources - -### Fixed - -- Attempt to handle docs generation enabling `deny(missing_docs)` -- Book: Editorial review -- Use native GHA rustup and cargo -- Distinguish between thumbv8m.base and thumbv8m.main for basepri usage. - -### Changed - -- Updated dev-dependency cortex-m-semihosting to v0.5 -- CI: Updated to setup-python@v4 -- CI: Updated to checkout@v3 -- Tuned redirect message for rtic.rs/meeting - -## [v1.1.3] - 2022-06-23 - -### Added - -### Fixed - -- Bump cortex-m-rtic-macros to 1.1.5 -- fix ci: use SYST::PTR - -#### cortex-m-rtic-macros v1.1.5 - 2022-06-23 - -- Bump rtic-syntax to 1.0.2 - -#### cortex-m-rtic-macros v1.1.4 - 2022-05-24 - -- Fix macros to Rust 2021 - -#### cortex-m-rtic-macros v1.1.3 - 2022-05-24 - -- Fix clash with defmt - -### Changed - -## [v1.1.2] - 2022-05-09 - -### Added - -### Fixed - -- Generation of masks for the source masking scheduling for thumbv6 - -### Changed - -## [v1.1.1] - 2022-04-13 - YANKED - -### Added - -### Fixed - -- Fixed `marcro` version - -### Changed - -## [v1.1.0] - 2022-04-13 - YANKED - -### Added - -- Improve how CHANGELOG.md merges are handled -- If current $stable and master version matches, dev-book redirects to $stable book -- During deploy stage, merge master branch into current stable IFF cargo package version matches -- Rework branch structure, release/vVERSION -- Cargo clippy in CI -- Use rust-cache Github Action -- Support for NVIC based SPR based scheduling for armv6m. -- CI changelog entry enforcer -- `examples/periodic-at.rs`, an example of a periodic timer without accumulated drift. -- `examples/periodic-at2.rs`, an example of a periodic process with two tasks, with offset timing. - Here we depict two alternative usages of the timer type, explicit and trait based. -- book: Update `Monotonic` tips. - -### Fixed - -- Re-export `rtic_core::prelude` as `rtic::mutex::prelude` to allow glob imports + Clippy -- Fix all except `must_use` lints from clippy::pedantic -- Fix dated migration docs for spawn -- Remove obsolete action-rs tool-cache -- Force mdBook to return error codes -- Readded missing ramfunc output to book - -### Changed - -- Try to detect `target-dir` for rtic-expansion.rs - -## [v1.0.0] - 2021-12-25 - -### Changed - -- Bump RTIC dependencies also updated to v1.0.0 -- Edition 2021 -- Change default `idle` behaviour to be `NOP` instead of `WFI` - -## [v0.6.0-rc.4] - 2021-11-09 - -- Updated to use the new generic `Monotonic` trait - -## [v0.6.0-rc.3] - 2021-11-08 - -### Fixed - -- Match rtic-syntax Analysis-struct updates from https://github.com/rtic-rs/rtic-syntax/pull/61 - -## [v0.6.0-rc.2] - 2021-09-28 - -- Fixed issue with `cortex_m` being used by the codegen instead of using the `rtic::export::...` which could make an app not compile if Systick is used and the user did not have the cortex-m crate as a dependency - -## [v0.6.0-rc.1] - 2021-09-27 - -- Documentation updates -- Monotonic handlers default to maximum priority instead of minimum (to follow RTIC 0.5) -- Better support for `rust-analyzer` - -## [v0.5.9] - 2021-09-27 - -- Removed the `cortex-m-rt` dependency -- Docs updates - -## [v0.5.8] - 2021-08-19 - -- Feature flag was added to support `cortex-m v0.7.x` -- MSRV raised to 1.38. - -## [v0.6.0-alpha.5] - 2021-07-09 - -### Changed - -- The new resources syntax is implemented. - -## [v0.5.7] - 2021-07-05 - -- Backport: "you must enable the rt feature" compile time detection - -## [v0.6.0-alpha.4] - 2021-05-27 - -### Fixed - -- Fixed codegen structure to not have issues with local paths -- Default paths for monotonics now work properly -- New `embedded-time` version to `0.11` - -## [v0.6.0-alpha.3] - 2021-0X-XX - -- Lost in the ether... - -## [v0.6.0-alpha.2] - 2021-04-08 - -### Added - -- Cancel and reschedule support to the monotonics - -### Fixed - -- UB in `spawn_at` -- `#[cfg]` and other attributes now work on hardware tasks -- Type aliases now work in `mod app` - -### Changed - -- The access to monotonic static methods was for example `MyMono::now()`, and is now `monotonics::MyMono::now()` - -## [v0.6.0-alpha.1] - 2021-03-04 - -### Added - -- Support for multi-locks, see `examples/multilock.rs` for syntax. -- New monotonic syntax and support, see `#[monotonic]` - -## [v0.5.6] - 2021-03-03 - -- **Security** Use latest security patched heapless - -## [v0.6.0-alpha.0] - 2020-11-14 - -### Added - -- Allow annotating resources to activate special resource locking behaviour. - - `#[lock_free]`, there might be several tasks with the same priority accessing - the resource without critical section. - - `#[task_local]`, there must be only one task, similar to a task local - resource, but (optionally) set-up by init. This is similar to move. - -- Improved ergonomics allowing separation of task signatures to actual implementation in extern block `extern "Rust" { #[task(..)] fn t(..); }`. - -### Changed - -- [breaking-change] [PR 400] Move dispatchers from extern block to app argument. - -[PR 400]: https://github.com/rtic-rs/cortex-m-rtic/pull/400 - -- [breaking-change] [PR 399] Locking resources are now always required to achieve a symmetric UI. - -[PR 399]: https://github.com/rtic-rs/cortex-m-rtic/pull/399 - -- [breaking-change] [PR 390] Rework whole spawn/schedule, support `foo::spawn( ... )`, - `foo::schedule( ... )`. - -[PR 390]: https://github.com/rtic-rs/cortex-m-rtic/pull/390 - -- [breaking-change] [PR 368] `struct Resources` changed to attribute `#[resources]` on a struct. - -- [breaking-change] [PR 368] Mod over const, instead of `const APP: () = {` use `mod app {`. - -- [breaking-change] [PR 372] Init function always return `LateResources` for a symmetric API. - -- [PR 355] Multi-core support was removed to reduce overall complexity. - -[PR 368]: https://github.com/rtic-rs/cortex-m-rtic/pull/368 -[PR 372]: https://github.com/rtic-rs/cortex-m-rtic/pull/372 -[PR 355]: https://github.com/rtic-rs/cortex-m-rtic/pull/355 - -## [v0.5.5] - 2020-08-27 - -- Includes the previous soundness fix. -- Fixes wrong use of the `cortex_m` crate which can cause some projects to stop compiling. - -## [v0.5.4] - 2020-08-26 - YANKED - -- **Soundness fix in RTIC**, it was previously possible to get the `cortex_m::Peripherals` more than once, causing UB. - -## [v0.5.3] - 2020-06-12 - -- Added migration guide from `cortex-m-rtfm` to `cortex-m-rtic` -- No code changes, only a version compatibility release with `cortex-m-rtfm` to ease the transition -for users. - -## [v0.5.2] - 2020-06-11 - -- Using safe `DWT` interface -- Using GitHub Actions now -- Improved CI speed -- Now `main` can be used as function name -- Fixed so one can `cfg`-out resources when using a newer compiler - -## [v0.5.1] - 2019-11-19 - -- Fixed arithmetic wrapping bug in src/cyccntr.rs - elapsed and duration could cause an internal overflow trap - on subtraction in debug mode. - -- Fixed bug in SysTick implementation where the SysTick could be disabled by - accident - -## [v0.5.0] - 2019-11-14 - -### Added - -- Experimental support for homogeneous and heterogeneous multi-core - microcontrollers has been added. Support is gated behind the `homogeneous` and - `heterogeneous` Cargo features. - -### Changed - -- [breaking-change] [RFC 155] "explicit `Context` parameter" has been - implemented. - -[RFC 155]: https://github.com/rtic-rs/cortex-m-rtic/issues/155 - -- [breaking-change] [RFC 147] "all functions must be safe" has been - implemented. - -[RFC 147]: https://github.com/rtic-rs/cortex-m-rtic/issues/147 - -- All the queues internally used by the framework now use `AtomicU8` indices - instead of `AtomicUsize`; this reduces the static memory used by the - framework. - -- [breaking-change] when the `capacity` argument is omitted, the capacity of - the task is assumed to be `1`. Before, a reasonable (but hard to predict) - capacity was computed based on the number of `spawn` references the task had. - -- [breaking-change] resources that are appear as exclusive references - (`&mut-`) no longer appear behind the `Exclusive` newtype. - -- [breaking-change] the `timer-queue` Cargo feature has been removed. The - `schedule` API can be used without enabling any Cargo feature. - -- [breaking-change] when the `schedule` API is used the type of - `init::Context.core` changes from `cortex_m::Peripherals` to - `rtic::Peripherals`. The fields of `rtic::Peripherals` do not change when - Cargo features are enabled. - -- [breaking-change] the monotonic timer used to implement the `schedule` API - is now user configurable via the `#[app(monotonic = ..)]` argument. IMPORTANT: - it is now the responsibility of the application author to configure and - initialize the chosen `monotonic` timer during the `#[init]` phase. - -- [breaking-change] the `peripherals` field is not include in `init::Context` - by default. One must opt-in using the `#[app(peripherals = ..)]` argument. - -- [breaking-change] the `#[exception]` and `#[interrupt]` attributes have been - removed. Hardware tasks are now declared using the `#[task(binds = ..)]` - attribute. - -- [breaking-change] the syntax to declare resources has changed. Instead of - using a `static [mut]` variable for each resource, all resources must be - declared in a `Resources` structure. - -### Removed - -- [breaking-change] the integration with the `owned_singleton` crate has been - removed. You can use `heapless::Pool` instead of `alloc_singleton`. - -- [breaking-change] late resources can no longer be initialized using the assign - syntax. `init::LateResources` is the only method to initialize late resources. - See [PR #140] for more details. - -[PR #140]: https://github.com/rtic-rs/cortex-m-rtic/pull/140 - -## [v0.4.3] - 2019-04-21 - -### Changed - -- Checking that the specified priorities are supported by the target device is - now done at compile time. - -### Fixed - -- Building this crate with the "nightly" feature and a recent compiler has been - fixed. - -## [v0.4.2] - 2019-02-27 - -### Added - -- `Duration` now has an `as_cycles` method to get the number of clock cycles - contained in it. - -- An opt-in "nightly" feature that reduces static memory usage, shortens - initialization time and reduces runtime overhead has been added. To use this - feature you need a nightly compiler! - -- [RFC 128] has been implemented. The `exception` and `interrupt` have gained a - `binds` argument that lets you give the handler an arbitrary name. For - example: - -[RFC 128]: https://github.com/rtic-rs/cortex-m-rtic/issues/128 - -``` rust -// on v0.4.1 you had to write -#[interrupt] -fn USART0() { .. } - -// on v0.4.2 you can write -#[interrupt(binds = USART0)] -fn on_new_frame() { .. } -``` - -### Changed - -- Builds are now reproducible. `cargo build; cargo clean; cargo build` will - produce binaries that are exactly the same (after `objcopy -O ihex`). This - wasn't the case before because we used randomly generated identifiers for - memory safety but now all the randomness is gone. - -### Fixed - -- Fixed a `non_camel_case_types` warning that showed up when using a recent - nightly. - -- Fixed a bug that allowed you to enter the `capacity` and `priority` arguments - in the `task` attribute more than once. Now all arguments can only be stated - once in the list, as it should be. - -## [v0.4.1] - 2019-02-12 - -### Added - -- The RTIC book has been translated to Russian. You can find the translation - online at https://japaric.github.io/cortex-m-rtic/book/ru/ - -- `Duration` now implements the `Default` trait. - -### Changed - -- [breaking-change] [soundness-fix] `init` can not contain any early return as - that would result in late resources not being initialized and thus undefined - behavior. - -- Use an absolute link to the book so it works when landing from crates.io - documentation page - -- The initialization function can now be written as `fn init() -> - init::LateResources` when late resources are used. This is preferred over the - old `fn init()` form. See the section on late resources (resources chapter) in - the book for more details. - -### Fixed - -- `#[interrupt]` and `#[exception]` no longer produce warnings on recent nightlies. - -## [v0.4.0] - 2018-11-03 - YANKED - -Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0.4.1. - -### Changed - -- This crate now compiles on stable 1.31. - -- [breaking-change] The `app!` macro has been transformed into an attribute. See - the documentation for details. - -- [breaking-change] Applications that use this library must be written using the - 2018 edition. - -- [breaking-change] The `Resource` trait has been renamed to `Mutex`. - `Resource.claim_mut` has been renamed to `Mutex.lock` and its signature has - changed (no `Threshold` token is required). - -- [breaking-change] The name of the library has changed to `rtic`. The package - name is still `cortex-m-rtic`. - -- [breaking-change] `cortex_m_rtic::set_pending` has been renamed to - `rtic::pend`. - -### Added - -- Software tasks, which can be immediately spawn and scheduled to run in the - future. - -- `Instant` and `Duration` API. - -- Integration with the [`Singleton`] abstraction. - -[`Singleton`]: https://docs.rs/owned-singleton/0.1.0/owned_singleton/ - -### Removed - -- [breaking-change] The `Threshold` token has been removed. - -- [breaking-change] The `bkpt` and `wfi` re-exports have been removed. - -- [breaking-change] `rtic::atomic` has been removed. - -## [v0.3.4] - 2018-08-27 - -### Changed - -- The documentation link to point to GH pages. - -## [v0.3.3] - 2018-08-24 - -### Fixed - -- Compilation with latest nightly - -## [v0.3.2] - 2018-04-16 - -### Added - -- Span information to error messages - -### Changed - -- Some non fatal error messages have become warning messages. For example, specifying an empty list - of resources now produces a warning instead of a hard error. - -## [v0.3.1] - 2018-01-16 - -### Fixed - -- Documentation link - -## [v0.3.0] - 2018-01-15 - -### Added - -- [feat] `&'static mut` references can be safely created by assigning resources to `init`. See the - `init.resources` section of the `app!` macro documentation and the `safe-static-mut-ref` example - for details. - -### Changed - -- [breaking-change] svd2rust dependency has been bumped to v0.12.0 - -- [breaking-change] resources assigned to tasks, or to idle, that were not declared in the top - `resources` field generate compiler errors. Before these were assumed to be peripherals, that's no - longer the case. - -- [breaking-change] the layout of `init::Peripherals` has changed. This struct now has two fields: - `core` and `device`. The value of the `core` field is a struct that owns all the core peripherals - of the device and the value of the `device` field is a struct that owns all the device specific - peripherals of the device. - -## [v0.2.2] - 2017-11-22 - -### Added - -- Support for runtime initialized resources ("late" resources). - -## [v0.2.1] - 2017-07-29 - -### Fixed - -- Link to `app!` macro documentation. - -## [v0.2.0] - 2017-07-29 - -### Added - -- The `app!` macro, a macro to declare the tasks and resources of an - application. - -- The `Resource` trait, which is used to write generic code that deals with - resources. - -- Support for system handlers like SYS_TICK. - -### Changed - -- [breaking-change] The signature of the `atomic` function has changed. - -- [breaking-change] The threshold token has become a concrete type and lost its - `raise` method. - -### Removed - -- [breaking-change] The `tasks!` and `peripherals!` macros. - -- [breaking-change] The ceiling and priority tokens. - -- [breaking-change] The `Local`, `Resource` and `Peripheral` structs. - -- [breaking-change] The traits related to type level integers. - -## [v0.1.1] - 2017-06-05 - -### Changed - -- `peripherals!`: The `register_block` field is now optional - -## v0.1.0 - 2017-05-09 - -- Initial release - -[Unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.4...HEAD -[v1.1.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.3...v1.1.4 -[v1.1.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.2...v1.1.3 -[v1.1.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.1...v1.1.2 -[v1.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.0...v1.1.1 -[v1.1.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.0.0...v1.1.0 -[v1.0.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.4...v1.0.0 -[v0.6.0-rc.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.3...v0.6.0-rc.4 -[v0.6.0-rc.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.2...v0.6.0-rc.3 -[v0.6.0-rc.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.1...v0.6.0-rc.2 -[v0.6.0-rc.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.0...v0.6.0-rc.1 -[v0.6.0-rc.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.5...v0.6.0-rc.0 -[v0.6.0-alpha.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.4...v0.6.0-alpha.5 -[v0.6.0-alpha.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.3...v0.6.0-alpha.4 -[v0.6.0-alpha.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.2...v0.6.0-alpha.3 -[v0.6.0-alpha.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.1...v0.6.0-alpha.2 -[v0.6.0-alpha.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.0...v0.6.0-alpha.1 -[v0.6.0-alpha.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.6.0-alpha.0 -[v0.5.x unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.x -[v0.5.9]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.9 -[v0.5.8]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.7...v0.5.8 -[v0.5.7]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.6...v0.5.7 -[v0.5.6]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.5.6 -[v0.5.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.4...v0.5.5 -[v0.5.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.3...v0.5.4 -[v0.5.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.2...v0.5.3 -[v0.5.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.1...v0.5.2 -[v0.5.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.0...v0.5.1 -[v0.5.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.3...v0.5.0 -[v0.4.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.2...v0.4.3 -[v0.4.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.1...v0.4.2 -[v0.4.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.0...v0.4.1 -[v0.4.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.4...v0.4.0 -[v0.3.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.3...v0.3.4 -[v0.3.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.2...v0.3.3 -[v0.3.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.1...v0.3.2 -[v0.3.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.0...v0.3.1 -[v0.3.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.2...v0.3.0 -[v0.2.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.1...v0.2.2 -[v0.2.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.0...v0.2.1 -[v0.2.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.1...v0.2.0 -[v0.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.0...v0.1.1 diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index c22d023..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,80 +0,0 @@ -[package] -authors = [ - "The Real-Time Interrupt-driven Concurrency developers", - "Emil Fresk ", - "Henrik Tjäder ", - "Jorge Aparicio ", - "Per Lindgren ", -] -categories = ["concurrency", "embedded", "no-std", "asynchronous"] -description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" -documentation = "https://rtic.rs/" -edition = "2021" -keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] -license = "MIT OR Apache-2.0" -name = "rtic" -readme = "README.md" -repository = "https://github.com/rtic-rs/rtic" - -version = "2.0.0-alpha.0" - -[lib] -name = "rtic" - -[dependencies] -cortex-m = "0.7.0" -rtic-macros = { path = "macros", version = "2.0.0-alpha.0" } -rtic-monotonic = "1.0.0" -rtic-core = "1.0.0" -heapless = "0.7.7" -bare-metal = "1.0.0" -#portable-atomic = { version = "0.3.19" } -atomic-polyfill = "1" - -[build-dependencies] -version_check = "0.9" - -[dev-dependencies] -lm3s6965 = "0.1.3" -cortex-m-semihosting = "0.5.0" -systick-monotonic = "1.0.0" - -[dev-dependencies.panic-semihosting] -features = ["exit"] -version = "0.6.0" - -[target.x86_64-unknown-linux-gnu.dev-dependencies] -trybuild = "1" - -[profile.release] -codegen-units = 1 -lto = true - -[workspace] -members = ["macros", "xtask", "rtic-timer"] - -# do not optimize proc-macro deps or build scripts -[profile.dev.build-override] -codegen-units = 16 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - - -[profile.release.build-override] -codegen-units = 16 -debug = false -debug-assertions = false -opt-level = 0 -overflow-checks = false - -[patch.crates-io] -lm3s6965 = { git = "https://github.com/japaric/lm3s6965" } - -[features] -test-critical-section = ["cortex-m/critical-section-single-core"] - -# [[example]] -# name = "pool" -# required-features = ["test-critical-section"] diff --git a/build.rs b/build.rs deleted file mode 100644 index 35f8303..0000000 --- a/build.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::env; - -fn main() { - let target = env::var("TARGET").unwrap(); - - if version_check::Channel::read().unwrap().is_nightly() { - println!("cargo:rustc-cfg=rustc_is_nightly"); - } - - // These targets all have know support for the BASEPRI register. - if target.starts_with("thumbv7m") - | target.starts_with("thumbv7em") - | target.starts_with("thumbv8m.main") - { - println!("cargo:rustc-cfg=have_basepri"); - - // These targets are all known to _not_ have the BASEPRI register. - } else if target.starts_with("thumb") - && !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base")) - { - panic!("Unknown target '{target}'. Need to update BASEPRI logic in build.rs."); - } - - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/ci/expected/async-delay.run b/ci/expected/async-delay.run deleted file mode 100644 index 61852ab..0000000 --- a/ci/expected/async-delay.run +++ /dev/null @@ -1,7 +0,0 @@ -init -hello from bar -hello from baz -hello from foo -bye from foo -bye from bar -bye from baz diff --git a/ci/expected/async-infinite-loop.run b/ci/expected/async-infinite-loop.run deleted file mode 100644 index f9fd4e4..0000000 --- a/ci/expected/async-infinite-loop.run +++ /dev/null @@ -1,6 +0,0 @@ -init -hello from async 0 -hello from async 1 -hello from async 2 -hello from async 3 -hello from async 4 diff --git a/ci/expected/async-task-multiple-prios.run b/ci/expected/async-task-multiple-prios.run deleted file mode 100644 index 0b42df0..0000000 --- a/ci/expected/async-task-multiple-prios.run +++ /dev/null @@ -1,6 +0,0 @@ -init -hello from async 3 a 1 -hello from async 4 a 2 -hello from async 1 a 3 -hello from async 2 a 4 -idle diff --git a/ci/expected/async-task.run b/ci/expected/async-task.run deleted file mode 100644 index 1f93a4c..0000000 --- a/ci/expected/async-task.run +++ /dev/null @@ -1,5 +0,0 @@ -init -hello from async2 -hello from async -hello from async with args a: 1, b: 2 -idle diff --git a/ci/expected/async-timeout.run b/ci/expected/async-timeout.run deleted file mode 100644 index a807423..0000000 --- a/ci/expected/async-timeout.run +++ /dev/null @@ -1,5 +0,0 @@ -init -hello from bar -hello from foo -foo no timeout -bar timeout diff --git a/ci/expected/big-struct-opt.run b/ci/expected/big-struct-opt.run deleted file mode 100644 index 7fdef35..0000000 --- a/ci/expected/big-struct-opt.run +++ /dev/null @@ -1,3 +0,0 @@ -async_task data:[22, 22, 22, 22, 22] -uart0 data:[22, 22, 22, 22, 22] -idle diff --git a/ci/expected/binds.run b/ci/expected/binds.run deleted file mode 100644 index f84cff0..0000000 --- a/ci/expected/binds.run +++ /dev/null @@ -1,4 +0,0 @@ -init -foo called 1 time -idle -foo called 2 times diff --git a/ci/expected/cancel-reschedule.run b/ci/expected/cancel-reschedule.run deleted file mode 100644 index 5a94752..0000000 --- a/ci/expected/cancel-reschedule.run +++ /dev/null @@ -1,3 +0,0 @@ -init -foo -bar diff --git a/ci/expected/capacity.run b/ci/expected/capacity.run deleted file mode 100644 index f96815d..0000000 --- a/ci/expected/capacity.run +++ /dev/null @@ -1,5 +0,0 @@ -foo(0) -foo(1) -foo(2) -foo(3) -bar diff --git a/ci/expected/cfg-monotonic.run b/ci/expected/cfg-monotonic.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/cfg-whole-task.run b/ci/expected/cfg-whole-task.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/common.run b/ci/expected/common.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/complex.run b/ci/expected/complex.run deleted file mode 100644 index 5df884d..0000000 --- a/ci/expected/complex.run +++ /dev/null @@ -1,47 +0,0 @@ -init -idle p0 started -t2 p4 called 1 time -enter lock s4 0 -t3 p4 exit -idle enter lock s3 0 -idle pend t0 -idle pend t1 -idle pend t2 -t2 p4 called 2 times -enter lock s4 1 -t3 p4 exit -idle still in lock s3 0 -t1 p3 called 1 time -t1 enter lock s4 2 -t1 pend t0 -t1 pend t2 -t1 still in lock s4 2 -t2 p4 called 3 times -enter lock s4 2 -t3 p4 exit -t1 p3 exit -t0 p2 called 1 time -t0 p2 exit - -back in idle -enter lock s2 0 -idle pend t0 -idle pend t1 -t1 p3 called 2 times -t1 enter lock s4 3 -t1 pend t0 -t1 pend t2 -t1 still in lock s4 3 -t2 p4 called 4 times -enter lock s4 3 -t3 p4 exit -t1 p3 exit -idle pend t2 -t2 p4 called 5 times -enter lock s4 4 -t3 p4 exit -idle still in lock s2 0 -t0 p2 called 2 times -t0 p2 exit - -idle exit diff --git a/ci/expected/declared_locals.run b/ci/expected/declared_locals.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/destructure.run b/ci/expected/destructure.run deleted file mode 100644 index 25a4b1b..0000000 --- a/ci/expected/destructure.run +++ /dev/null @@ -1,2 +0,0 @@ -bar: a = 0, b = 1, c = 2 -foo: a = 0, b = 1, c = 2 diff --git a/ci/expected/extern_binds.run b/ci/expected/extern_binds.run deleted file mode 100644 index 9d925d5..0000000 --- a/ci/expected/extern_binds.run +++ /dev/null @@ -1,4 +0,0 @@ -init -foo called -idle -foo called diff --git a/ci/expected/extern_spawn.run b/ci/expected/extern_spawn.run deleted file mode 100644 index 257cc56..0000000 --- a/ci/expected/extern_spawn.run +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/ci/expected/generics.run b/ci/expected/generics.run deleted file mode 100644 index fb31731..0000000 --- a/ci/expected/generics.run +++ /dev/null @@ -1,6 +0,0 @@ -UART1(STATE = 0) -shared: 0 -> 1 -UART0(STATE = 0) -shared: 1 -> 2 -UART1(STATE = 1) -shared: 2 -> 4 diff --git a/ci/expected/hardware.run b/ci/expected/hardware.run deleted file mode 100644 index ef00864..0000000 --- a/ci/expected/hardware.run +++ /dev/null @@ -1,4 +0,0 @@ -init -UART0 called 1 time -idle -UART0 called 2 times diff --git a/ci/expected/idle-wfi.run b/ci/expected/idle-wfi.run deleted file mode 100644 index 4307776..0000000 --- a/ci/expected/idle-wfi.run +++ /dev/null @@ -1,2 +0,0 @@ -init -idle diff --git a/ci/expected/idle.run b/ci/expected/idle.run deleted file mode 100644 index 4307776..0000000 --- a/ci/expected/idle.run +++ /dev/null @@ -1,2 +0,0 @@ -init -idle diff --git a/ci/expected/init.run b/ci/expected/init.run deleted file mode 100644 index b1b7161..0000000 --- a/ci/expected/init.run +++ /dev/null @@ -1 +0,0 @@ -init diff --git a/ci/expected/locals.run b/ci/expected/locals.run deleted file mode 100644 index 4f1d350..0000000 --- a/ci/expected/locals.run +++ /dev/null @@ -1,3 +0,0 @@ -bar: local_to_bar = 1 -foo: local_to_foo = 1 -idle: local_to_idle = 1 diff --git a/ci/expected/lock-free.run b/ci/expected/lock-free.run deleted file mode 100644 index 18de0ec..0000000 --- a/ci/expected/lock-free.run +++ /dev/null @@ -1,2 +0,0 @@ - foo = 1 - bar = 2 diff --git a/ci/expected/lock.run b/ci/expected/lock.run deleted file mode 100644 index a987b37..0000000 --- a/ci/expected/lock.run +++ /dev/null @@ -1,5 +0,0 @@ -A -B - shared = 1 -C -D - shared = 2 -E diff --git a/ci/expected/message.run b/ci/expected/message.run deleted file mode 100644 index 11814db..0000000 --- a/ci/expected/message.run +++ /dev/null @@ -1,6 +0,0 @@ -foo -bar(0) -baz(1, 2) -foo -bar(1) -baz(2, 3) diff --git a/ci/expected/message_passing.run b/ci/expected/message_passing.run deleted file mode 100644 index a1448d8..0000000 --- a/ci/expected/message_passing.run +++ /dev/null @@ -1,3 +0,0 @@ -foo 1, 1 -foo 1, 2 -foo 2, 3 diff --git a/ci/expected/multilock.run b/ci/expected/multilock.run deleted file mode 100644 index dd8c1f2..0000000 --- a/ci/expected/multilock.run +++ /dev/null @@ -1 +0,0 @@ -Multiple locks, s1: 1, s2: 1, s3: 1 diff --git a/ci/expected/not-sync.run b/ci/expected/not-sync.run deleted file mode 100644 index cd91476..0000000 --- a/ci/expected/not-sync.run +++ /dev/null @@ -1,3 +0,0 @@ -init -bar a 13 -foo a 13 diff --git a/ci/expected/only-shared-access.run b/ci/expected/only-shared-access.run deleted file mode 100644 index dcc73e6..0000000 --- a/ci/expected/only-shared-access.run +++ /dev/null @@ -1,2 +0,0 @@ -bar(key = 0xdeadbeef) -foo(key = 0xdeadbeef) diff --git a/ci/expected/periodic-at.run b/ci/expected/periodic-at.run deleted file mode 100644 index bf5bb06..0000000 --- a/ci/expected/periodic-at.run +++ /dev/null @@ -1,4 +0,0 @@ -foo Instant { ticks: 0 } -foo Instant { ticks: 10 } -foo Instant { ticks: 20 } -foo Instant { ticks: 30 } diff --git a/ci/expected/periodic-at2.run b/ci/expected/periodic-at2.run deleted file mode 100644 index 6e56421..0000000 --- a/ci/expected/periodic-at2.run +++ /dev/null @@ -1,7 +0,0 @@ -foo Instant { ticks: 0 } -bar Instant { ticks: 10 } -foo Instant { ticks: 30 } -bar Instant { ticks: 40 } -foo Instant { ticks: 60 } -bar Instant { ticks: 70 } -foo Instant { ticks: 90 } diff --git a/ci/expected/periodic.run b/ci/expected/periodic.run deleted file mode 100644 index a1f8944..0000000 --- a/ci/expected/periodic.run +++ /dev/null @@ -1,4 +0,0 @@ -foo -foo -foo -foo diff --git a/ci/expected/peripherals-taken.run b/ci/expected/peripherals-taken.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/pool.run b/ci/expected/pool.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/preempt.run b/ci/expected/preempt.run deleted file mode 100644 index 932b2b3..0000000 --- a/ci/expected/preempt.run +++ /dev/null @@ -1,5 +0,0 @@ -foo - start - baz - start - baz - end - bar -foo - end diff --git a/ci/expected/ramfunc.run b/ci/expected/ramfunc.run deleted file mode 100644 index 257cc56..0000000 --- a/ci/expected/ramfunc.run +++ /dev/null @@ -1 +0,0 @@ -foo diff --git a/ci/expected/ramfunc.run.grep.bar b/ci/expected/ramfunc.run.grep.bar deleted file mode 100644 index 33e002f..0000000 --- a/ci/expected/ramfunc.run.grep.bar +++ /dev/null @@ -1 +0,0 @@ -20000000 t ramfunc::bar::h9d6714fe5a3b0c89 diff --git a/ci/expected/ramfunc.run.grep.foo b/ci/expected/ramfunc.run.grep.foo deleted file mode 100644 index 44e8822..0000000 --- a/ci/expected/ramfunc.run.grep.foo +++ /dev/null @@ -1 +0,0 @@ -00000162 t ramfunc::foo::h30e7789b08c08e19 diff --git a/ci/expected/resource-user-struct.run b/ci/expected/resource-user-struct.run deleted file mode 100644 index a587a94..0000000 --- a/ci/expected/resource-user-struct.run +++ /dev/null @@ -1,2 +0,0 @@ -UART0: shared = 1 -UART1: shared = 2 diff --git a/ci/expected/schedule.run b/ci/expected/schedule.run deleted file mode 100644 index 1dbd445..0000000 --- a/ci/expected/schedule.run +++ /dev/null @@ -1,4 +0,0 @@ -init -foo -bar -baz diff --git a/ci/expected/shared.run b/ci/expected/shared.run deleted file mode 100644 index 6d3d3e4..0000000 --- a/ci/expected/shared.run +++ /dev/null @@ -1 +0,0 @@ -received message: 42 diff --git a/ci/expected/smallest.run b/ci/expected/smallest.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/spawn.run b/ci/expected/spawn.run deleted file mode 100644 index 240cd18..0000000 --- a/ci/expected/spawn.run +++ /dev/null @@ -1,2 +0,0 @@ -init -foo diff --git a/ci/expected/static.run b/ci/expected/static.run deleted file mode 100644 index 3d3f46f..0000000 --- a/ci/expected/static.run +++ /dev/null @@ -1,3 +0,0 @@ -received message: 1 -received message: 2 -received message: 3 diff --git a/ci/expected/t-binds.run b/ci/expected/t-binds.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-cfg-resources.run b/ci/expected/t-cfg-resources.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-htask-main.run b/ci/expected/t-htask-main.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-idle-main.run b/ci/expected/t-idle-main.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-late-not-send.run b/ci/expected/t-late-not-send.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-schedule.run b/ci/expected/t-schedule.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/t-spawn.run b/ci/expected/t-spawn.run deleted file mode 100644 index e69de29..0000000 diff --git a/ci/expected/task.run b/ci/expected/task.run deleted file mode 100644 index de45dce..0000000 --- a/ci/expected/task.run +++ /dev/null @@ -1,5 +0,0 @@ -foo - start -foo - middle -baz -foo - end -bar diff --git a/ci/expected/zero-prio-task.run b/ci/expected/zero-prio-task.run deleted file mode 100644 index 123b0f2..0000000 --- a/ci/expected/zero-prio-task.run +++ /dev/null @@ -1,3 +0,0 @@ -init -hello from async -hello from async2 diff --git a/examples/async-delay.no_rs b/examples/async-delay.no_rs deleted file mode 100644 index fb478c3..0000000 --- a/examples/async-delay.no_rs +++ /dev/null @@ -1,63 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - baz::spawn().ok(); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // debug::exit(debug::EXIT_SUCCESS); - loop { - // hprintln!("idle"); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - monotonics::delay(100.millis()).await; - hprintln!("bye from foo").ok(); - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - monotonics::delay(200.millis()).await; - hprintln!("bye from bar").ok(); - } - - #[task] - async fn baz(_cx: baz::Context) { - hprintln!("hello from baz").ok(); - monotonics::delay(300.millis()).await; - hprintln!("bye from baz").ok(); - - debug::exit(debug::EXIT_SUCCESS); - } -} diff --git a/examples/async-infinite-loop.no_rs b/examples/async-infinite-loop.no_rs deleted file mode 100644 index a95f998..0000000 --- a/examples/async-infinite-loop.no_rs +++ /dev/null @@ -1,53 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an - // await inside the loop. - #[task] - async fn foo(_cx: foo::Context) { - let mut i = 0; - loop { - if i == 5 { - debug::exit(debug::EXIT_SUCCESS); - } - - hprintln!("hello from async {}", i).ok(); - monotonics::delay(100.millis()).await; // This makes it okey! - - i += 1; - } - } -} diff --git a/examples/async-task-multiple-prios.rs b/examples/async-task-multiple-prios.rs deleted file mode 100644 index 5c9674d..0000000 --- a/examples/async-task-multiple-prios.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! examples/async-task-multiple-prios.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - a: u32, - b: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - async_task1::spawn().ok(); - async_task2::spawn().ok(); - async_task3::spawn().ok(); - async_task4::spawn().ok(); - - (Shared { a: 0, b: 0 }, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - hprintln!("idle"); - debug::exit(debug::EXIT_SUCCESS); - } - } - - #[task(priority = 1, shared = [a, b])] - async fn async_task1(mut cx: async_task1::Context) { - hprintln!( - "hello from async 1 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } - - #[task(priority = 1, shared = [a, b])] - async fn async_task2(mut cx: async_task2::Context) { - hprintln!( - "hello from async 2 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } - - #[task(priority = 2, shared = [a, b])] - async fn async_task3(mut cx: async_task3::Context) { - hprintln!( - "hello from async 3 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } - - #[task(priority = 2, shared = [a, b])] - async fn async_task4(mut cx: async_task4::Context) { - hprintln!( - "hello from async 4 a {}", - cx.shared.a.lock(|a| { - *a += 1; - *a - }) - ); - } -} diff --git a/examples/async-task.rs b/examples/async-task.rs deleted file mode 100644 index 7730c54..0000000 --- a/examples/async-task.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! examples/async-task.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - a: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_cx: init::Context) -> (Shared, Local) { - hprintln!("init"); - - async_task::spawn().unwrap(); - async_task_args::spawn(1, 2).unwrap(); - async_task2::spawn().unwrap(); - - (Shared { a: 0 }, Local {}) - } - - #[idle(shared = [a])] - fn idle(_: idle::Context) -> ! { - loop { - hprintln!("idle"); - debug::exit(debug::EXIT_SUCCESS); - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task(binds = UART1, shared = [a])] - fn hw_task(cx: hw_task::Context) { - let hw_task::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from hw"); - } - - #[task(shared = [a])] - async fn async_task(cx: async_task::Context) { - let async_task::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from async"); - } - - #[task] - async fn async_task_args(_cx: async_task_args::Context, a: u32, b: i32) { - hprintln!("hello from async with args a: {}, b: {}", a, b); - } - - #[task(priority = 2, shared = [a])] - async fn async_task2(cx: async_task2::Context) { - let async_task2::SharedResources { a: _, .. } = cx.shared; - hprintln!("hello from async2"); - } -} diff --git a/examples/async-timeout.no_rs b/examples/async-timeout.no_rs deleted file mode 100644 index 3f68df7..0000000 --- a/examples/async-timeout.no_rs +++ /dev/null @@ -1,87 +0,0 @@ -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -// NOTES: -// -// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async -// task can have a mutable reference stored. -// - Spawning an async task equates to it being polled once. - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] -mod app { - use core::{ - future::Future, - pin::Pin, - task::{Context, Poll}, - }; - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - hprintln!("init").unwrap(); - - foo::spawn().ok(); - bar::spawn().ok(); - - ( - Shared {}, - Local {}, - init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs - } - } - - #[task] - async fn foo(_cx: foo::Context) { - hprintln!("hello from foo").ok(); - - // This will not timeout - match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { - Ok(_) => hprintln!("foo no timeout").ok(), - Err(_) => hprintln!("foo timeout").ok(), - }; - } - - #[task] - async fn bar(_cx: bar::Context) { - hprintln!("hello from bar").ok(); - - // This will timeout - match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { - Ok(_) => hprintln!("bar no timeout").ok(), - Err(_) => hprintln!("bar timeout").ok(), - }; - - debug::exit(debug::EXIT_SUCCESS); - } - - pub struct NeverEndingFuture {} - - impl Future for NeverEndingFuture { - type Output = (); - - fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - // Never finish - Poll::Pending - } - } -} diff --git a/examples/big-struct-opt.rs b/examples/big-struct-opt.rs deleted file mode 100644 index 408a2de..0000000 --- a/examples/big-struct-opt.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! examples/big-struct-opt.rs -//! -//! Example on how to initialize a large struct without needing to copy it via `LateResources`, -//! effectively saving stack space needed for the copies. - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; - -/// Some big struct -pub struct BigStruct { - /// Big content - pub data: [u8; 2048], -} - -impl BigStruct { - fn new() -> Self { - BigStruct { data: [22; 2048] } - } -} - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use super::BigStruct; - use core::mem::MaybeUninit; - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - big_struct: &'static mut BigStruct, - } - - #[local] - struct Local {} - - #[init(local = [bs: MaybeUninit = MaybeUninit::uninit()])] - fn init(cx: init::Context) -> (Shared, Local) { - let big_struct = unsafe { - // write directly into the static storage - cx.local.bs.as_mut_ptr().write(BigStruct::new()); - &mut *cx.local.bs.as_mut_ptr() - }; - - rtic::pend(Interrupt::UART0); - async_task::spawn().unwrap(); - ( - Shared { - // assign the reference so we can use the resource - big_struct, - }, - Local {}, - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - hprintln!("idle"); - debug::exit(debug::EXIT_SUCCESS); - } - } - - #[task(binds = UART0, shared = [big_struct])] - fn uart0(mut cx: uart0::Context) { - cx.shared - .big_struct - .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5])); - } - - #[task(shared = [big_struct], priority = 2)] - async fn async_task(mut cx: async_task::Context) { - cx.shared - .big_struct - .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5])); - } -} diff --git a/examples/binds.rs b/examples/binds.rs deleted file mode 100644 index cf078ff..0000000 --- a/examples/binds.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! examples/binds.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use panic_semihosting as _; - -// `examples/interrupt.rs` rewritten to use `binds` -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - hprintln!("idle"); - - rtic::pend(Interrupt::UART0); - - loop { - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task(binds = UART0, local = [times: u32 = 0])] - fn foo(cx: foo::Context) { - *cx.local.times += 1; - - hprintln!( - "foo called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - } -} diff --git a/examples/cancel-reschedule.no_rs b/examples/cancel-reschedule.no_rs deleted file mode 100644 index a38a9c4..0000000 --- a/examples/cancel-reschedule.no_rs +++ /dev/null @@ -1,73 +0,0 @@ -//! examples/cancel-reschedule.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - hprintln!("init").ok(); - - // Schedule `foo` to run 1 second in the future - foo::spawn_after(1.secs()).unwrap(); - - ( - Shared {}, - Local {}, - init::Monotonics(mono), // Give the monotonic to RTIC - ) - } - - #[task] - fn foo(_: foo::Context) { - hprintln!("foo").ok(); - - // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) - let spawn_handle = baz::spawn_after(2.secs()).unwrap(); - bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true - } - - #[task] - fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { - hprintln!("bar").ok(); - - if do_reschedule { - // Reschedule baz 2 seconds from now, instead of the original 1 second - // from now. - baz_handle.reschedule_after(2.secs()).unwrap(); - // Or baz_handle.reschedule_at(/* time */) - } else { - // Or cancel it - baz_handle.cancel().unwrap(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task] - fn baz(_: baz::Context) { - hprintln!("baz").ok(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/capacity.no_rs b/examples/capacity.no_rs deleted file mode 100644 index a617269..0000000 --- a/examples/capacity.no_rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/capacity.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - rtic::pend(Interrupt::UART0); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(binds = UART0)] - fn uart0(_: uart0::Context) { - foo::spawn(0).unwrap(); - foo::spawn(1).unwrap(); - foo::spawn(2).unwrap(); - foo::spawn(3).unwrap(); - - bar::spawn().unwrap(); - } - - #[task(capacity = 4)] - fn foo(_: foo::Context, x: u32) { - hprintln!("foo({})", x).unwrap(); - } - - #[task] - fn bar(_: bar::Context) { - hprintln!("bar").unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/cfg-monotonic.rs b/examples/cfg-monotonic.rs deleted file mode 100644 index 88c0d6f..0000000 --- a/examples/cfg-monotonic.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! examples/cfg-monotonic.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; // Implements the `Monotonic` trait - - // A monotonic timer to enable scheduling in RTIC - #[cfg(feature = "killmono")] - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Not allowed by current rtic-syntax: - // error: `#[monotonic(...)]` on a specific type must appear at most once - // --> examples/cfg-monotonic.rs:23:10 - // | - // 23 | type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - // | ^^^^^^ - // #[monotonic(binds = SysTick, default = true)] - // type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Not allowed by current rtic-syntax: - // error: this interrupt is already bound - // --> examples/cfg-monotonic.rs:31:25 - // | - // 31 | #[monotonic(binds = SysTick, default = true)] - // | ^^^^^^^ - // #[monotonic(binds = SysTick, default = true)] - // type MyMono2 = DwtSystick<100>; // 100 Hz / 10 ms granularity - - // Resources shared between tasks - #[shared] - struct Shared { - s1: u32, - s2: i32, - } - - // Local resources to specific tasks (cannot be shared) - #[local] - struct Local { - l1: u8, - l2: i8, - } - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let _systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - #[cfg(feature = "killmono")] - let mono = Systick::new(systick, 12_000_000); - - // Spawn the task `foo` directly after `init` finishes - foo::spawn().unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - // Initialization of shared resources - Shared { s1: 0, s2: 1 }, - // Initialization of task local resources - Local { l1: 2, l2: 3 }, - // Move the monotonic timer to the RTIC run-time, this enables - // scheduling - #[cfg(feature = "killmono")] - init::Monotonics(mono), - init::Monotonics(), - ) - } - - // Background task, runs whenever no other tasks are running - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - continue; - } - } - - // Software task, not bound to a hardware interrupt. - // This task takes the task local resource `l1` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l1])] - fn foo(_: foo::Context) { - // This task is only spawned once in `init`, hence this task will run - // only once - - hprintln!("foo"); - } - - // Software task, also not bound to a hardware interrupt - // This task takes the task local resource `l2` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l2])] - fn bar(_: bar::Context) { - hprintln!("bar"); - - // Run `bar` once per second - // bar::spawn_after(1.secs()).unwrap(); - } - - // Hardware task, bound to a hardware interrupt - // The resources `s1` and `s2` are shared between all other tasks. - #[task(binds = UART0, priority = 3, shared = [s1, s2])] - fn uart0_interrupt(_: uart0_interrupt::Context) { - // This task is bound to the interrupt `UART0` and will run - // whenever the interrupt fires - - // Note that RTIC does NOT clear the interrupt flag, this is up to the - // user - - hprintln!("UART0 interrupt!"); - } -} diff --git a/examples/cfg-whole-task.no_rs b/examples/cfg-whole-task.no_rs deleted file mode 100644 index f41866d..0000000 --- a/examples/cfg-whole-task.no_rs +++ /dev/null @@ -1,94 +0,0 @@ -//! examples/cfg-whole-task.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::debug; - #[cfg(debug_assertions)] - use cortex_m_semihosting::hprintln; - - #[shared] - struct Shared { - count: u32, - #[cfg(never)] - unused: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn().unwrap(); - foo::spawn().unwrap(); - - ( - Shared { - count: 0, - #[cfg(never)] - unused: 1, - }, - Local {}, - init::Monotonics(), - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - #[task(capacity = 2, shared = [count])] - fn foo(mut _cx: foo::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 1); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - - // this wouldn't compile in `release` mode - // *_cx.shared.count += 1; - - // .. - } - - // The whole task should disappear, - // currently still present in the Tasks enum - #[cfg(never)] - #[task(capacity = 2, shared = [count])] - fn foo2(mut _cx: foo2::Context) { - #[cfg(debug_assertions)] - { - _cx.shared.count.lock(|count| *count += 10); - - log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); - } - - // this wouldn't compile in `release` mode - // *_cx.shared.count += 1; - - // .. - } - - #[cfg(debug_assertions)] - #[task(capacity = 2)] - fn log(_: log::Context, n: u32) { - hprintln!( - "foo has been called {} time{}", - n, - if n == 1 { "" } else { "s" } - ) - .ok(); - } -} diff --git a/examples/common.no_rs b/examples/common.no_rs deleted file mode 100644 index 1fe671e..0000000 --- a/examples/common.no_rs +++ /dev/null @@ -1,102 +0,0 @@ -//! examples/common.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; // Implements the `Monotonic` trait - - // A monotonic timer to enable scheduling in RTIC - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - // Resources shared between tasks - #[shared] - struct Shared { - s1: u32, - s2: i32, - } - - // Local resources to specific tasks (cannot be shared) - #[local] - struct Local { - l1: u8, - l2: i8, - } - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - // Spawn the task `foo` directly after `init` finishes - foo::spawn().unwrap(); - - // Spawn the task `bar` 1 second after `init` finishes, this is enabled - // by the `#[monotonic(..)]` above - bar::spawn_after(1.secs()).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - // Initialization of shared resources - Shared { s1: 0, s2: 1 }, - // Initialization of task local resources - Local { l1: 2, l2: 3 }, - // Move the monotonic timer to the RTIC run-time, this enables - // scheduling - init::Monotonics(mono), - ) - } - - // Background task, runs whenever no other tasks are running - #[idle] - fn idle(_: idle::Context) -> ! { - loop { - continue; - } - } - - // Software task, not bound to a hardware interrupt. - // This task takes the task local resource `l1` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l1])] - fn foo(_: foo::Context) { - // This task is only spawned once in `init`, hence this task will run - // only once - - hprintln!("foo").ok(); - } - - // Software task, also not bound to a hardware interrupt - // This task takes the task local resource `l2` - // The resources `s1` and `s2` are shared between all other tasks. - #[task(shared = [s1, s2], local = [l2])] - fn bar(_: bar::Context) { - hprintln!("bar").ok(); - - // Run `bar` once per second - bar::spawn_after(1.secs()).unwrap(); - } - - // Hardware task, bound to a hardware interrupt - // The resources `s1` and `s2` are shared between all other tasks. - #[task(binds = UART0, priority = 3, shared = [s1, s2])] - fn uart0_interrupt(_: uart0_interrupt::Context) { - // This task is bound to the interrupt `UART0` and will run - // whenever the interrupt fires - - // Note that RTIC does NOT clear the interrupt flag, this is up to the - // user - - hprintln!("UART0 interrupt!").ok(); - } -} diff --git a/examples/complex.rs b/examples/complex.rs deleted file mode 100644 index c1e9c6c..0000000 --- a/examples/complex.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! examples/complex.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - s2: u32, // shared with ceiling 2 - s3: u32, // shared with ceiling 3 - s4: u32, // shared with ceiling 4 - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - ( - Shared { - s2: 0, - s3: 0, - s4: 0, - }, - Local {}, - ) - } - - #[idle(shared = [s2, s3])] - fn idle(mut cx: idle::Context) -> ! { - hprintln!("idle p0 started"); - rtic::pend(Interrupt::GPIOC); - cx.shared.s3.lock(|s| { - hprintln!("idle enter lock s3 {}", s); - hprintln!("idle pend t0"); - rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 3 - hprintln!("idle pend t1"); - rtic::pend(Interrupt::GPIOB); // t1 p3, with shared ceiling 3 - hprintln!("idle pend t2"); - rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s3 {}", s); - }); - hprintln!("\nback in idle"); - - cx.shared.s2.lock(|s| { - hprintln!("enter lock s2 {}", s); - hprintln!("idle pend t0"); - rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("idle pend t1"); - rtic::pend(Interrupt::GPIOB); // t1 p3, no sharing - hprintln!("idle pend t2"); - rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("idle still in lock s2 {}", s); - }); - hprintln!("\nidle exit"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - #[task(binds = GPIOA, priority = 2, local = [times: u32 = 0], shared = [s2, s3])] - fn t0(cx: t0::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "t0 p2 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - hprintln!("t0 p2 exit"); - } - - #[task(binds = GPIOB, priority = 3, local = [times: u32 = 0], shared = [s3, s4])] - fn t1(mut cx: t1::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "t1 p3 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - - cx.shared.s4.lock(|s| { - hprintln!("t1 enter lock s4 {}", s); - hprintln!("t1 pend t0"); - rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 - hprintln!("t1 pend t2"); - rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing - hprintln!("t1 still in lock s4 {}", s); - }); - - hprintln!("t1 p3 exit"); - } - - #[task(binds = GPIOC, priority = 4, local = [times: u32 = 0], shared = [s4])] - fn t2(mut cx: t2::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "t2 p4 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - - cx.shared.s4.lock(|s| { - hprintln!("enter lock s4 {}", s); - *s += 1; - }); - hprintln!("t3 p4 exit"); - } -} diff --git a/examples/declared_locals.rs b/examples/declared_locals.rs deleted file mode 100644 index c845191..0000000 --- a/examples/declared_locals.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! examples/declared_locals.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [a: u32 = 0])] - fn init(cx: init::Context) -> (Shared, Local) { - // Locals in `#[init]` have 'static lifetime - let _a: &'static mut u32 = cx.local.a; - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } - - #[idle(local = [a: u32 = 0])] - fn idle(cx: idle::Context) -> ! { - // Locals in `#[idle]` have 'static lifetime - let _a: &'static mut u32 = cx.local.a; - - loop {} - } - - #[task(binds = UART0, local = [a: u32 = 0])] - fn foo(cx: foo::Context) { - // Locals in `#[task]`s have a local lifetime - let _a: &mut u32 = cx.local.a; - - // error: explicit lifetime required in the type of `cx` - // let _a: &'static mut u32 = cx.local.a; - } -} diff --git a/examples/destructure.rs b/examples/destructure.rs deleted file mode 100644 index 81eff3b..0000000 --- a/examples/destructure.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! examples/destructure.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - a: u32, - b: u32, - c: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - bar::spawn().unwrap(); - - (Shared { a: 0, b: 1, c: 2 }, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop {} - } - - // Direct destructure - #[task(shared = [&a, &b, &c])] - async fn foo(cx: foo::Context) { - let a = cx.shared.a; - let b = cx.shared.b; - let c = cx.shared.c; - - hprintln!("foo: a = {}, b = {}, c = {}", a, b, c); - } - - // De-structure-ing syntax - #[task(shared = [&a, &b, &c])] - async fn bar(cx: bar::Context) { - let bar::SharedResources { a, b, c, .. } = cx.shared; - - hprintln!("bar: a = {}, b = {}, c = {}", a, b, c); - } -} diff --git a/examples/extern_binds.rs b/examples/extern_binds.rs deleted file mode 100644 index 142a11d..0000000 --- a/examples/extern_binds.rs +++ /dev/null @@ -1,54 +0,0 @@ -//! examples/extern_binds.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use cortex_m_semihosting::hprintln; -use panic_semihosting as _; - -// Free function implementing the interrupt bound task `foo`. -fn foo(_: app::foo::Context) { - hprintln!("foo called"); -} - -#[rtic::app(device = lm3s6965)] -mod app { - use crate::foo; - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - hprintln!("idle"); - - rtic::pend(Interrupt::UART0); - - loop { - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - extern "Rust" { - #[task(binds = UART0)] - fn foo(_: foo::Context); - } -} diff --git a/examples/extern_spawn.rs b/examples/extern_spawn.rs deleted file mode 100644 index b2b95b9..0000000 --- a/examples/extern_spawn.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! examples/extern_spawn.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use cortex_m_semihosting::{debug, hprintln}; -use panic_semihosting as _; - -// Free function implementing the spawnable task `foo`. -// Notice, you need to indicate an anonymous lifetime <'a_> -async fn foo(_c: app::foo::Context<'_>) { - hprintln!("foo"); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator -} - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use crate::foo; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - extern "Rust" { - #[task()] - async fn foo(_c: foo::Context); - } -} diff --git a/examples/generics.rs b/examples/generics.rs deleted file mode 100644 index 2f23cce..0000000 --- a/examples/generics.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! examples/generics.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use cortex_m_semihosting::hprintln; -use panic_semihosting as _; -use rtic::Mutex; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - shared: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - rtic::pend(Interrupt::UART1); - - (Shared { shared: 0 }, Local {}) - } - - #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] - fn uart0(c: uart0::Context) { - hprintln!("UART0(STATE = {})", *c.local.state); - - // second argument has type `shared::shared` - super::advance(c.local.state, c.shared.shared); - - rtic::pend(Interrupt::UART1); - - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(binds = UART1, priority = 2, shared = [shared], local = [state: u32 = 0])] - fn uart1(c: uart1::Context) { - hprintln!("UART1(STATE = {})", *c.local.state); - - // second argument has type `shared::shared` - super::advance(c.local.state, c.shared.shared); - } -} - -// the second parameter is generic: it can be any type that implements the `Mutex` trait -fn advance(state: &mut u32, mut shared: impl Mutex) { - *state += 1; - - let (old, new) = shared.lock(|shared: &mut u32| { - let old = *shared; - *shared += *state; - (old, *shared) - }); - - hprintln!("shared: {} -> {}", old, new); -} diff --git a/examples/hardware.rs b/examples/hardware.rs deleted file mode 100644 index 62ae0d6..0000000 --- a/examples/hardware.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! examples/hardware.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - // Pends the UART0 interrupt but its handler won't run until *after* - // `init` returns because interrupts are disabled - rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend - - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // interrupts are enabled again; the `UART0` handler runs at this point - - hprintln!("idle"); - - rtic::pend(Interrupt::UART0); - - loop { - cortex_m::asm::nop(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } - - #[task(binds = UART0, local = [times: u32 = 0])] - fn uart0(cx: uart0::Context) { - // Safe access to local `static mut` variable - *cx.local.times += 1; - - hprintln!( - "UART0 called {} time{}", - *cx.local.times, - if *cx.local.times > 1 { "s" } else { "" } - ); - } -} diff --git a/examples/idle-wfi.rs b/examples/idle-wfi.rs deleted file mode 100644 index 8134ce3..0000000 --- a/examples/idle-wfi.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! examples/idle-wfi.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(mut cx: init::Context) -> (Shared, Local) { - hprintln!("init"); - - // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts - // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit - cx.core.SCB.set_sleepdeep(); - - (Shared {}, Local {}) - } - - #[idle(local = [x: u32 = 0])] - fn idle(cx: idle::Context) -> ! { - // Locals in idle have lifetime 'static - let _x: &'static mut u32 = cx.local.x; - - hprintln!("idle"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - // Now Wait For Interrupt is used instead of a busy-wait loop - // to allow MCU to sleep between interrupts - // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/WFI - rtic::export::wfi() - } - } -} diff --git a/examples/idle.rs b/examples/idle.rs deleted file mode 100644 index 0c4bd04..0000000 --- a/examples/idle.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! examples/idle.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - (Shared {}, Local {}) - } - - #[idle(local = [x: u32 = 0])] - fn idle(cx: idle::Context) -> ! { - // Locals in idle have lifetime 'static - let _x: &'static mut u32 = cx.local.x; - - hprintln!("idle"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/init.rs b/examples/init.rs deleted file mode 100644 index c3081bf..0000000 --- a/examples/init.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! examples/init.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, peripherals = true)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [x: u32 = 0])] - fn init(cx: init::Context) -> (Shared, Local) { - // Cortex-M peripherals - let _core: cortex_m::Peripherals = cx.core; - - // Device specific peripherals - let _device: lm3s6965::Peripherals = cx.device; - - // Locals in `init` have 'static lifetime - let _x: &'static mut u32 = cx.local.x; - - // Access to the critical section token, - // to indicate that this is a critical section - let _cs_token: bare_metal::CriticalSection = cx.cs; - - hprintln!("init"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } -} diff --git a/examples/locals.rs b/examples/locals.rs deleted file mode 100644 index ec3d59d..0000000 --- a/examples/locals.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! examples/locals.rs - -#![feature(type_alias_impl_trait)] -#![deny(unsafe_code)] -#![deny(missing_docs)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local { - local_to_foo: i64, - local_to_bar: i64, - local_to_idle: i64, - } - - // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - bar::spawn().unwrap(); - - ( - Shared {}, - // initial values for the `#[local]` resources - Local { - local_to_foo: 0, - local_to_bar: 0, - local_to_idle: 0, - }, - ) - } - - // `local_to_idle` can only be accessed from this context - #[idle(local = [local_to_idle])] - fn idle(cx: idle::Context) -> ! { - let local_to_idle = cx.local.local_to_idle; - *local_to_idle += 1; - - hprintln!("idle: local_to_idle = {}", local_to_idle); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - // error: no `local_to_foo` field in `idle::LocalResources` - // _cx.local.local_to_foo += 1; - - // error: no `local_to_bar` field in `idle::LocalResources` - // _cx.local.local_to_bar += 1; - - loop { - cortex_m::asm::nop(); - } - } - - // `local_to_foo` can only be accessed from this context - #[task(local = [local_to_foo])] - async fn foo(cx: foo::Context) { - let local_to_foo = cx.local.local_to_foo; - *local_to_foo += 1; - - // error: no `local_to_bar` field in `foo::LocalResources` - // cx.local.local_to_bar += 1; - - hprintln!("foo: local_to_foo = {}", local_to_foo); - } - - // `local_to_bar` can only be accessed from this context - #[task(local = [local_to_bar])] - async fn bar(cx: bar::Context) { - let local_to_bar = cx.local.local_to_bar; - *local_to_bar += 1; - - // error: no `local_to_foo` field in `bar::LocalResources` - // cx.local.local_to_foo += 1; - - hprintln!("bar: local_to_bar = {}", local_to_bar); - } -} diff --git a/examples/lock-free.no_rs b/examples/lock-free.no_rs deleted file mode 100644 index 053307c..0000000 --- a/examples/lock-free.no_rs +++ /dev/null @@ -1,50 +0,0 @@ -//! examples/lock-free.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - #[lock_free] // <- lock-free shared resource - counter: u64, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared { counter: 0 }, Local {}) - } - - #[task(shared = [counter])] // <- same priority - async fn foo(c: foo::Context) { - bar::spawn().unwrap(); - - *c.shared.counter += 1; // <- no lock API required - let counter = *c.shared.counter; - hprintln!(" foo = {}", counter).unwrap(); - } - - #[task(shared = [counter])] // <- same priority - async fn bar(c: bar::Context) { - foo::spawn().unwrap(); - - *c.shared.counter += 1; // <- no lock API required - let counter = *c.shared.counter; - hprintln!(" bar = {}", counter).unwrap(); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/lock.rs b/examples/lock.rs deleted file mode 100644 index 203ae6f..0000000 --- a/examples/lock.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! examples/lock.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA, GPIOB, GPIOC])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - shared: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared { shared: 0 }, Local {}) - } - - // when omitted priority is assumed to be `1` - #[task(shared = [shared])] - async fn foo(mut c: foo::Context) { - hprintln!("A"); - - // the lower priority task requires a critical section to access the data - c.shared.shared.lock(|shared| { - // data can only be modified within this critical section (closure) - *shared += 1; - - // bar will *not* run right now due to the critical section - bar::spawn().unwrap(); - - hprintln!("B - shared = {}", *shared); - - // baz does not contend for `shared` so it's allowed to run now - baz::spawn().unwrap(); - }); - - // critical section is over: bar can now start - - hprintln!("E"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2, shared = [shared])] - async fn bar(mut c: bar::Context) { - // the higher priority task does still need a critical section - let shared = c.shared.shared.lock(|shared| { - *shared += 1; - - *shared - }); - - hprintln!("D - shared = {}", shared); - } - - #[task(priority = 3)] - async fn baz(_: baz::Context) { - hprintln!("C"); - } -} diff --git a/examples/message.no_rs b/examples/message.no_rs deleted file mode 100644 index 76c5675..0000000 --- a/examples/message.no_rs +++ /dev/null @@ -1,52 +0,0 @@ -//! examples/message.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(/* no message */).unwrap(); - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(local = [count: u32 = 0])] - fn foo(cx: foo::Context) { - hprintln!("foo").unwrap(); - - bar::spawn(*cx.local.count).unwrap(); - *cx.local.count += 1; - } - - #[task] - fn bar(_: bar::Context, x: u32) { - hprintln!("bar({})", x).unwrap(); - - baz::spawn(x + 1, x + 2).unwrap(); - } - - #[task] - fn baz(_: baz::Context, x: u32, y: u32) { - hprintln!("baz({}, {})", x, y).unwrap(); - - if x + y > 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - foo::spawn().unwrap(); - } -} diff --git a/examples/message_passing.no_rs b/examples/message_passing.no_rs deleted file mode 100644 index ffa9537..0000000 --- a/examples/message_passing.no_rs +++ /dev/null @@ -1,37 +0,0 @@ -//! examples/message_passing.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - foo::spawn(1, 1).unwrap(); - foo::spawn(1, 2).unwrap(); - foo::spawn(2, 3).unwrap(); - assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached - - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(capacity = 3)] - fn foo(_c: foo::Context, x: i32, y: u32) { - hprintln!("foo {}, {}", x, y).unwrap(); - if x == 2 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - } -} diff --git a/examples/multilock.rs b/examples/multilock.rs deleted file mode 100644 index 6208cac..0000000 --- a/examples/multilock.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! examples/mutlilock.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - shared1: u32, - shared2: u32, - shared3: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - locks::spawn().unwrap(); - - ( - Shared { - shared1: 0, - shared2: 0, - shared3: 0, - }, - Local {}, - ) - } - - // when omitted priority is assumed to be `1` - #[task(shared = [shared1, shared2, shared3])] - async fn locks(c: locks::Context) { - let s1 = c.shared.shared1; - let s2 = c.shared.shared2; - let s3 = c.shared.shared3; - - (s1, s2, s3).lock(|s1, s2, s3| { - *s1 += 1; - *s2 += 1; - *s3 += 1; - - hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3); - }); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/not-sync.rs b/examples/not-sync.rs deleted file mode 100644 index 6d1ddae..0000000 --- a/examples/not-sync.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! `examples/not-sync.rs` - -// #![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use core::marker::PhantomData; -use panic_semihosting as _; - -/// Not sync -pub struct NotSync { - _0: PhantomData<*const ()>, - data: u32, -} - -unsafe impl Send for NotSync {} - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use super::NotSync; - use core::marker::PhantomData; - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - shared: NotSync, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - - foo::spawn().unwrap(); - bar::spawn().unwrap(); - ( - Shared { - shared: NotSync { - _0: PhantomData, - data: 13, - }, - }, - Local {}, - ) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop {} - } - - #[task(shared = [&shared])] - async fn foo(c: foo::Context) { - let shared: &NotSync = c.shared.shared; - hprintln!("foo a {}", shared.data); - } - - #[task(shared = [&shared])] - async fn bar(c: bar::Context) { - let shared: &NotSync = c.shared.shared; - hprintln!("bar a {}", shared.data); - } -} diff --git a/examples/only-shared-access.rs b/examples/only-shared-access.rs deleted file mode 100644 index 1d006e6..0000000 --- a/examples/only-shared-access.rs +++ /dev/null @@ -1,44 +0,0 @@ -//! examples/only-shared-access.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - key: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - bar::spawn().unwrap(); - - (Shared { key: 0xdeadbeef }, Local {}) - } - - #[task(shared = [&key])] - async fn foo(cx: foo::Context) { - let key: &u32 = cx.shared.key; - hprintln!("foo(key = {:#x})", key); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2, shared = [&key])] - async fn bar(cx: bar::Context) { - hprintln!("bar(key = {:#x})", cx.shared.key); - } -} diff --git a/examples/periodic-at.no_rs b/examples/periodic-at.no_rs deleted file mode 100644 index ca68ed5..0000000 --- a/examples/periodic-at.no_rs +++ /dev/null @@ -1,49 +0,0 @@ -//! examples/periodic-at.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mut mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(1.secs(), mono.now()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant).ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Periodic every 100 milliseconds - let next_instant = instant + 100.millis(); - foo::spawn_at(next_instant, next_instant).unwrap(); - } -} diff --git a/examples/periodic-at2.no_rs b/examples/periodic-at2.no_rs deleted file mode 100644 index ec9adcc..0000000 --- a/examples/periodic-at2.no_rs +++ /dev/null @@ -1,61 +0,0 @@ -//! examples/periodic-at2.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mut mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(200.millis(), mono.now()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - // Using the explicit type of the timer implementation - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { - hprintln!("foo {:?}", instant).ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Spawn a new message with 100 ms offset to spawned time - let next_instant = instant + 100.millis(); - bar::spawn_at(next_instant, next_instant).unwrap(); - } - - // Using the Instant from the Monotonic trait - // This remains agnostic to the timer implementation - #[task(local = [cnt: u32 = 0])] - fn bar(_cx: bar::Context, instant: ::Instant) { - hprintln!("bar {:?}", instant).ok(); - - // Spawn a new message with 200ms offset to spawned time - let next_instant = instant + 200.millis(); - foo::spawn_at(next_instant, next_instant).unwrap(); - } -} diff --git a/examples/periodic.no_rs b/examples/periodic.no_rs deleted file mode 100644 index 2f9e8e6..0000000 --- a/examples/periodic.no_rs +++ /dev/null @@ -1,48 +0,0 @@ -//! examples/periodic.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - foo::spawn_after(100.millis()).unwrap(); - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[task(local = [cnt: u32 = 0])] - fn foo(cx: foo::Context) { - hprintln!("foo").ok(); - *cx.local.cnt += 1; - - if *cx.local.cnt == 4 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // Periodic every 100ms - foo::spawn_after(100.millis()).unwrap(); - } -} diff --git a/examples/peripherals-taken.rs b/examples/peripherals-taken.rs deleted file mode 100644 index 2f710e9..0000000 --- a/examples/peripherals-taken.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! examples/peripherals-taken.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - assert!(cortex_m::Peripherals::take().is_none()); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } -} diff --git a/examples/pool.no_rs b/examples/pool.no_rs deleted file mode 100644 index fb8589a..0000000 --- a/examples/pool.no_rs +++ /dev/null @@ -1,70 +0,0 @@ -//! examples/pool.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use heapless::{ - pool, - pool::singleton::{Box, Pool}, -}; -use panic_semihosting as _; -use rtic::app; - -// Declare a pool of 128-byte memory blocks -pool!(P: [u8; 128]); - -#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use crate::{Box, Pool}; - use cortex_m_semihosting::debug; - use lm3s6965::Interrupt; - - // Import the memory pool into scope - use super::P; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init(local = [memory: [u8; 512] = [0; 512]])] - fn init(cx: init::Context) -> (Shared, Local) { - // Increase the capacity of the memory pool by ~4 - P::grow(cx.local.memory); - - rtic::pend(Interrupt::I2C0); - - (Shared {}, Local {}) - } - - #[task(binds = I2C0, priority = 2)] - async fn i2c0(_: i2c0::Context) { - // claim a memory block, initialize it and .. - let x = P::alloc().unwrap().init([0u8; 128]); - - // .. send it to the `foo` task - foo::spawn(x).ok().unwrap(); - - // send another block to the task `bar` - bar::spawn(P::alloc().unwrap().init([0u8; 128])) - .ok() - .unwrap(); - } - - #[task] - async fn foo(_: foo::Context, _x: Box

) { - // explicitly return the block to the pool - drop(_x); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - async fn bar(_: bar::Context, _x: Box

) { - // this is done automatically so we can omit the call to `drop` - // drop(_x); - } -} diff --git a/examples/preempt.rs b/examples/preempt.rs deleted file mode 100644 index 4b11907..0000000 --- a/examples/preempt.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! examples/preempt.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use panic_semihosting as _; -use rtic::app; - -#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[task(priority = 1)] - async fn foo(_: foo::Context) { - hprintln!("foo - start"); - baz::spawn().unwrap(); - hprintln!("foo - end"); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - async fn bar(_: bar::Context) { - hprintln!(" bar"); - } - - #[task(priority = 2)] - async fn baz(_: baz::Context) { - hprintln!(" baz - start"); - bar::spawn().unwrap(); - hprintln!(" baz - end"); - } -} diff --git a/examples/ramfunc.rs b/examples/ramfunc.rs deleted file mode 100644 index e2e7f67..0000000 --- a/examples/ramfunc.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! examples/ramfunc.rs - -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app( - device = lm3s6965, - dispatchers = [ - UART0, - #[link_section = ".data.UART1"] - UART1 - ]) -] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[inline(never)] - #[task] - async fn foo(_: foo::Context) { - hprintln!("foo"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - // run this task from RAM - #[inline(never)] - #[link_section = ".data.bar"] - #[task(priority = 2)] - async fn bar(_: bar::Context) { - foo::spawn().unwrap(); - } -} diff --git a/examples/resource-user-struct.rs b/examples/resource-user-struct.rs deleted file mode 100644 index fcbacae..0000000 --- a/examples/resource-user-struct.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! examples/resource.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - // A resource - shared: u32, - } - - // Should not collide with the struct above - #[allow(dead_code)] - struct Shared2 { - // A resource - shared: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(Interrupt::UART0); - rtic::pend(Interrupt::UART1); - - (Shared { shared: 0 }, Local {}) - } - - // `shared` cannot be accessed from this context - #[idle] - fn idle(_cx: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - // error: no `shared` field in `idle::Context` - // _cx.shared.shared += 1; - - loop {} - } - - // `shared` can be accessed from this context - #[task(binds = UART0, shared = [shared])] - fn uart0(mut cx: uart0::Context) { - let shared = cx.shared.shared.lock(|shared| { - *shared += 1; - *shared - }); - - hprintln!("UART0: shared = {}", shared); - } - - // `shared` can be accessed from this context - #[task(binds = UART1, shared = [shared])] - fn uart1(mut cx: uart1::Context) { - let shared = cx.shared.shared.lock(|shared| { - *shared += 1; - *shared - }); - - hprintln!("UART1: shared = {}", shared); - } -} diff --git a/examples/schedule.no_rs b/examples/schedule.no_rs deleted file mode 100644 index 5bad5a3..0000000 --- a/examples/schedule.no_rs +++ /dev/null @@ -1,64 +0,0 @@ -//! examples/schedule.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - hprintln!("init").ok(); - - // Schedule `foo` to run 1 second in the future - foo::spawn_after(1.secs()).unwrap(); - - ( - Shared {}, - Local {}, - init::Monotonics(mono), // Give the monotonic to RTIC - ) - } - - #[task] - fn foo(_: foo::Context) { - hprintln!("foo").ok(); - - // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) - bar::spawn_after(1.secs()).unwrap(); - } - - #[task] - fn bar(_: bar::Context) { - hprintln!("bar").ok(); - - // Schedule `baz` to run 1 seconds from now, but with a specific time instant. - baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); - } - - #[task] - fn baz(_: baz::Context) { - hprintln!("baz").ok(); - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/shared.rs b/examples/shared.rs deleted file mode 100644 index d0633fb..0000000 --- a/examples/shared.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! examples/late.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use heapless::spsc::{Consumer, Producer, Queue}; - use lm3s6965::Interrupt; - - #[shared] - struct Shared { - p: Producer<'static, u32, 5>, - c: Consumer<'static, u32, 5>, - } - - #[local] - struct Local {} - - #[init(local = [q: Queue = Queue::new()])] - fn init(cx: init::Context) -> (Shared, Local) { - let (p, c) = cx.local.q.split(); - - // Initialization of shared resources - (Shared { p, c }, Local {}) - } - - #[idle(shared = [c])] - fn idle(mut c: idle::Context) -> ! { - loop { - if let Some(byte) = c.shared.c.lock(|c| c.dequeue()) { - hprintln!("received message: {}", byte); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } else { - rtic::pend(Interrupt::UART0); - } - } - } - - #[task(binds = UART0, shared = [p])] - fn uart0(mut c: uart0::Context) { - c.shared.p.lock(|p| p.enqueue(42).unwrap()); - } -} diff --git a/examples/smallest.rs b/examples/smallest.rs deleted file mode 100644 index e54ae44..0000000 --- a/examples/smallest.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! examples/smallest.rs - -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use panic_semihosting as _; // panic handler -use rtic::app; - -#[app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - (Shared {}, Local {}) - } -} diff --git a/examples/spawn.rs b/examples/spawn.rs deleted file mode 100644 index d30ecf1..0000000 --- a/examples/spawn.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! examples/spawn.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - hprintln!("init"); - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[task] - async fn foo(_: foo::Context) { - hprintln!("foo"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/static.rs b/examples/static.rs deleted file mode 100644 index 7f656f4..0000000 --- a/examples/static.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! examples/static.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - use heapless::spsc::{Consumer, Producer, Queue}; - - #[shared] - struct Shared {} - - #[local] - struct Local { - p: Producer<'static, u32, 5>, - c: Consumer<'static, u32, 5>, - } - - #[init(local = [q: Queue = Queue::new()])] - fn init(cx: init::Context) -> (Shared, Local) { - // q has 'static life-time so after the split and return of `init` - // it will continue to exist and be allocated - let (p, c) = cx.local.q.split(); - - foo::spawn().unwrap(); - - (Shared {}, Local { p, c }) - } - - #[idle(local = [c])] - fn idle(c: idle::Context) -> ! { - loop { - // Lock-free access to the same underlying queue! - if let Some(data) = c.local.c.dequeue() { - hprintln!("received message: {}", data); - - // Run foo until data - if data == 3 { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } else { - foo::spawn().unwrap(); - } - } - } - } - - #[task(local = [p, state: u32 = 0])] - async fn foo(c: foo::Context) { - *c.local.state += 1; - - // Lock-free access to the same underlying queue! - c.local.p.enqueue(*c.local.state).unwrap(); - } -} diff --git a/examples/t-binds.rs b/examples/t-binds.rs deleted file mode 100644 index bdeb391..0000000 --- a/examples/t-binds.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! [compile-pass] Check that `binds` works as advertised - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } - - // Cortex-M exception - #[task(binds = SVCall)] - fn foo(c: foo::Context) { - crate::foo_trampoline(c) - } - - // LM3S6965 interrupt - #[task(binds = UART0)] - fn bar(c: bar::Context) { - crate::bar_trampoline(c) - } -} - -#[allow(dead_code)] -fn foo_trampoline(_: app::foo::Context) {} - -#[allow(dead_code)] -fn bar_trampoline(_: app::bar::Context) {} diff --git a/examples/t-cfg-resources.rs b/examples/t-cfg-resources.rs deleted file mode 100644 index 0328700..0000000 --- a/examples/t-cfg-resources.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! [compile-pass] check that `#[cfg]` attributes applied on resources work - -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared { - // A conditionally compiled resource behind feature_x - #[cfg(feature = "feature_x")] - x: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - ( - Shared { - #[cfg(feature = "feature_x")] - x: 0, - }, - Local {}, - ) - } - - #[idle] - fn idle(_cx: idle::Context) -> ! { - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/t-htask-main.rs b/examples/t-htask-main.rs deleted file mode 100644 index 8f885bc..0000000 --- a/examples/t-htask-main.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! examples/h-task-main.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - rtic::pend(lm3s6965::Interrupt::UART0); - - (Shared {}, Local {}) - } - - #[task(binds = UART0)] - fn taskmain(_: taskmain::Context) { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/examples/t-idle-main.rs b/examples/t-idle-main.rs deleted file mode 100644 index 43215cf..0000000 --- a/examples/t-idle-main.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! examples/t-idle-main.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[idle] - fn taskmain(_: taskmain::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/t-late-not-send.rs b/examples/t-late-not-send.rs deleted file mode 100644 index 44d1d85..0000000 --- a/examples/t-late-not-send.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! [compile-pass] shared resources don't need to be `Send` if they are owned by `idle` - -#![no_main] -#![no_std] -#![deny(missing_docs)] - -use core::marker::PhantomData; -use panic_semihosting as _; - -/// Not send -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -#[rtic::app(device = lm3s6965)] -mod app { - use super::NotSend; - use core::marker::PhantomData; - use cortex_m_semihosting::debug; - - #[shared] - struct Shared { - x: NotSend, - y: Option, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - ( - Shared { - x: NotSend { _0: PhantomData }, - y: None, - }, - Local {}, - ) - } - - #[idle(shared = [x, y])] - fn idle(_: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - loop { - cortex_m::asm::nop(); - } - } -} diff --git a/examples/t-schedule.no_rs b/examples/t-schedule.no_rs deleted file mode 100644 index 5ec4208..0000000 --- a/examples/t-schedule.no_rs +++ /dev/null @@ -1,136 +0,0 @@ -//! [compile-pass] Check `schedule` code generation - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::debug; - use systick_monotonic::*; - - #[monotonic(binds = SysTick, default = true)] - type MyMono = Systick<100>; // 100 Hz / 10 ms granularity - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { - let systick = cx.core.SYST; - - // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) - let mono = Systick::new(systick, 12_000_000); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}, init::Monotonics(mono)) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - // Task without message passing - - // Not default - let _: Result = - foo::MyMono::spawn_at(monotonics::MyMono::now()); - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = foo::MyMono::spawn_after(1.secs()); - let _: Result<(), ()> = handle.unwrap().cancel(); - - // Using default - let _: Result = foo::spawn_at(monotonics::now()); - let handle: Result = foo::spawn_after(1.secs()); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = foo::spawn_after(1.secs()); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = foo::spawn_after(1.secs()); - let _: Result<(), ()> = handle.unwrap().cancel(); - - // Task with single message passing - - // Not default - let _: Result = - bar::MyMono::spawn_at(monotonics::MyMono::now(), 0); - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().cancel(); - - // Using default - let _: Result = bar::spawn_at(monotonics::MyMono::now(), 0); - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = bar::spawn_after(1.secs(), 1); - let _: Result = handle.unwrap().cancel(); - - // Task with multiple message passing - - // Not default - let _: Result = - baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1); - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = - baz::MyMono::spawn_after(1.secs(), 1, 2); - let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); - - // Using default - let _: Result = - baz::spawn_at(monotonics::MyMono::now(), 0, 1); - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result = handle.unwrap().reschedule_after(1.secs()); - - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result = - handle.unwrap().reschedule_at(monotonics::MyMono::now()); - - let handle: Result = baz::spawn_after(1.secs(), 1, 2); - let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); - - loop { - cortex_m::asm::nop(); - } - } - - #[task] - fn foo(_: foo::Context) {} - - #[task] - fn bar(_: bar::Context, _x: u32) {} - - #[task] - fn baz(_: baz::Context, _x: u32, _y: u32) {} -} diff --git a/examples/t-spawn.no_rs b/examples/t-spawn.no_rs deleted file mode 100644 index dad0c83..0000000 --- a/examples/t-spawn.no_rs +++ /dev/null @@ -1,69 +0,0 @@ -//! [compile-pass] Check code generation of `spawn` - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] -mod app { - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - (Shared {}, Local {}) - } - - #[idle] - fn idle(_: idle::Context) -> ! { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - - loop { - cortex_m::asm::nop(); - } - } - - #[task(binds = SVCall)] - fn svcall(_: svcall::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task(binds = UART0)] - fn uart0(_: uart0::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task] - async fn foo(_: foo::Context) { - let _: Result<(), ()> = foo::spawn(); - let _: Result<(), u32> = bar::spawn(0); - let _: Result<(), (u32, u32)> = baz::spawn(0, 1); - } - - #[task] - async fn bar(_: bar::Context, _x: u32) {} - - #[task] - async fn baz(_: baz::Context, _x: u32, _y: u32) {} -} diff --git a/examples/task.rs b/examples/task.rs deleted file mode 100644 index ab6a1e0..0000000 --- a/examples/task.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! examples/task.rs - -#![deny(unsafe_code)] -#![deny(warnings)] -#![deny(missing_docs)] -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] -mod app { - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) { - foo::spawn().unwrap(); - - (Shared {}, Local {}) - } - - #[task] - async fn foo(_: foo::Context) { - hprintln!("foo - start"); - - // spawns `bar` onto the task scheduler - // `foo` and `bar` have the same priority so `bar` will not run until - // after `foo` terminates - bar::spawn().unwrap(); - - hprintln!("foo - middle"); - - // spawns `baz` onto the task scheduler - // `baz` has higher priority than `foo` so it immediately preempts `foo` - baz::spawn().unwrap(); - - hprintln!("foo - end"); - } - - #[task] - async fn bar(_: bar::Context) { - hprintln!("bar"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } - - #[task(priority = 2)] - async fn baz(_: baz::Context) { - hprintln!("baz"); - } -} diff --git a/examples/zero-prio-task.rs b/examples/zero-prio-task.rs deleted file mode 100644 index c810e8f..0000000 --- a/examples/zero-prio-task.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! examples/zero-prio-task.rs - -#![no_main] -#![no_std] -#![feature(type_alias_impl_trait)] -#![deny(missing_docs)] - -use core::marker::PhantomData; -use panic_semihosting as _; - -/// Does not impl send -pub struct NotSend { - _0: PhantomData<*const ()>, -} - -#[rtic::app(device = lm3s6965, peripherals = true)] -mod app { - use super::NotSend; - use core::marker::PhantomData; - use cortex_m_semihosting::{debug, hprintln}; - - #[shared] - struct Shared { - x: NotSend, - } - - #[local] - struct Local { - y: NotSend, - } - - #[init] - fn init(_cx: init::Context) -> (Shared, Local) { - hprintln!("init"); - - async_task::spawn().unwrap(); - async_task2::spawn().unwrap(); - - ( - Shared { - x: NotSend { _0: PhantomData }, - }, - Local { - y: NotSend { _0: PhantomData }, - }, - ) - } - - #[task(priority = 0, shared = [x], local = [y])] - async fn async_task(_: async_task::Context) { - hprintln!("hello from async"); - } - - #[task(priority = 0, shared = [x])] - async fn async_task2(_: async_task2::Context) { - hprintln!("hello from async2"); - - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - } -} diff --git a/macros/.gitignore b/macros/.gitignore deleted file mode 100644 index 4fffb2f..0000000 --- a/macros/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/target -/Cargo.lock diff --git a/macros/Cargo.toml b/macros/Cargo.toml deleted file mode 100644 index 1cc9556..0000000 --- a/macros/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -authors = [ - "The Real-Time Interrupt-driven Concurrency developers", - "Emil Fresk ", - "Henrik Tjäder ", - "Jorge Aparicio ", - "Per Lindgren ", -] -categories = ["concurrency", "embedded", "no-std", "asynchronous"] -description = "Procedural macros, syntax parsing, and codegen of the RTIC crate" -documentation = "https://rtic-rs.github.io/rtic/api/rtic" -edition = "2021" -keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] -license = "MIT OR Apache-2.0" -name = "rtic-macros" -readme = "../README.md" -repository = "https://github.com/rtic-rs/rtic" - -version = "2.0.0-alpha.0" - -[lib] -proc-macro = true - -[feature] -default = [] -debugprint = [] -# list of supported codegen backends -thumbv6 = [] -thumbv7 = [] -# riscv-clic = [] -# riscv-ch32 = [] - -[dependencies] -indexmap = "1.9.2" -proc-macro2 = "1.0.49" -proc-macro-error = "1.0.4" -quote = "1.0.23" -syn = { version = "1.0.107", features = ["extra-traits", "full"] } - -[dev-dependencies] -trybuild = "1.0.73" diff --git a/macros/src/analyze.rs b/macros/src/analyze.rs deleted file mode 100644 index 65774f6..0000000 --- a/macros/src/analyze.rs +++ /dev/null @@ -1,49 +0,0 @@ -use core::ops; -use std::collections::{BTreeMap, BTreeSet}; - -use crate::syntax::{ - analyze::{self, Priority}, - ast::{App, Dispatcher}, -}; -use syn::Ident; - -/// Extend the upstream `Analysis` struct with our field -pub struct Analysis { - parent: analyze::Analysis, - pub interrupts: BTreeMap, -} - -impl ops::Deref for Analysis { - type Target = analyze::Analysis; - - fn deref(&self) -> &Self::Target { - &self.parent - } -} - -// Assign an interrupt to each priority level -pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { - let mut available_interrupt = app.args.dispatchers.clone(); - - // the set of priorities (each priority only once) - let priorities = app - .software_tasks - .values() - .map(|task| task.args.priority) - .collect::>(); - - // map from priorities to interrupts (holding name and attributes) - - let interrupts: BTreeMap = priorities - .iter() - .filter(|prio| **prio > 0) // 0 prio tasks are run in main - .copied() - .rev() - .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) - .collect(); - - Analysis { - parent: analysis, - interrupts, - } -} diff --git a/macros/src/bindings.rs b/macros/src/bindings.rs deleted file mode 100644 index 8b13789..0000000 --- a/macros/src/bindings.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/macros/src/check.rs b/macros/src/check.rs deleted file mode 100644 index a05c82e..0000000 --- a/macros/src/check.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::collections::HashSet; - -use crate::syntax::ast::App; -use syn::parse; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that external (device-specific) interrupts are not named after known (Cortex-M) - // exceptions - for name in app.args.dispatchers.keys() { - let name_s = name.to_string(); - - match &*name_s { - "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" - | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { - return Err(parse::Error::new( - name.span(), - "Cortex-M exceptions can't be used as `extern` interrupts", - )); - } - - _ => {} - } - } - - // Check that there are enough external interrupts to dispatch the software tasks and the timer - // queue handler - let mut first = None; - let priorities = app - .software_tasks - .iter() - .map(|(name, task)| { - first = Some(name); - task.args.priority - }) - .filter(|prio| *prio > 0) - .collect::>(); - - let need = priorities.len(); - let given = app.args.dispatchers.len(); - if need > given { - let s = { - format!( - "not enough interrupts to dispatch \ - all software tasks (need: {need}; given: {given})" - ) - }; - - // If not enough tasks and first still is None, may cause - // "custom attribute panicked" due to unwrap on None - return Err(parse::Error::new(first.unwrap().span(), s)); - } - - // Check that all exceptions are valid; only exceptions with configurable priorities are - // accepted - for (name, task) in &app.hardware_tasks { - let name_s = task.args.binds.to_string(); - match &*name_s { - "NonMaskableInt" | "HardFault" => { - return Err(parse::Error::new( - name.span(), - "only exceptions with configurable priority can be used as hardware tasks", - )); - } - - _ => {} - } - } - - Ok(()) -} diff --git a/macros/src/codegen.rs b/macros/src/codegen.rs deleted file mode 100644 index 24e98ce..0000000 --- a/macros/src/codegen.rs +++ /dev/null @@ -1,75 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::analyze::Analysis; -use crate::syntax::ast::App; - -mod assertions; -mod async_dispatchers; -mod hardware_tasks; -mod idle; -mod init; -mod local_resources; -mod local_resources_struct; -mod module; -mod post_init; -mod pre_init; -mod shared_resources; -mod shared_resources_struct; -mod software_tasks; -mod util; - -mod main; - -// TODO: organize codegen to actual parts of code -// so `main::codegen` generates ALL the code for `fn main`, -// `software_tasks::codegen` generates ALL the code for software tasks etc... - -#[allow(clippy::too_many_lines)] -pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { - // Generate the `main` function - let main = main::codegen(app, analysis); - let init_codegen = init::codegen(app, analysis); - let idle_codegen = idle::codegen(app, analysis); - let shared_resources_codegen = shared_resources::codegen(app, analysis); - let local_resources_codegen = local_resources::codegen(app, analysis); - let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis); - let software_tasks_codegen = software_tasks::codegen(app, analysis); - let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis); - - let user_imports = &app.user_imports; - let user_code = &app.user_code; - let name = &app.name; - let device = &app.args.device; - - let rt_err = util::rt_err_ident(); - - quote!( - /// The RTIC application module - pub mod #name { - /// Always include the device crate which contains the vector table - use #device as #rt_err; - - #(#user_imports)* - - #(#user_code)* - /// User code end - - #init_codegen - - #idle_codegen - - #hardware_tasks_codegen - - #software_tasks_codegen - - #shared_resources_codegen - - #local_resources_codegen - - #async_dispatchers_codegen - - #main - } - ) -} diff --git a/macros/src/codegen/assertions.rs b/macros/src/codegen/assertions.rs deleted file mode 100644 index dd94aa6..0000000 --- a/macros/src/codegen/assertions.rs +++ /dev/null @@ -1,53 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; - -/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - for ty in &analysis.send_types { - stmts.push(quote!(rtic::export::assert_send::<#ty>();)); - } - - for ty in &analysis.sync_types { - stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); - } - - let device = &app.args.device; - let chunks_name = util::priority_mask_chunks_ident(); - let no_basepri_checks: Vec<_> = app - .hardware_tasks - .iter() - .filter_map(|(_, task)| { - if !util::is_exception(&task.args.binds) { - let interrupt_name = &task.args.binds; - Some(quote!( - if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) { - ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); - } - )) - } else { - None - } - }) - .collect(); - - let const_check = quote! { - const _CONST_CHECK: () = { - if !rtic::export::have_basepri() { - #(#no_basepri_checks)* - } else { - // TODO: Add armv7 checks here - } - }; - - let _ = _CONST_CHECK; - }; - - stmts.push(const_check); - - stmts -} diff --git a/macros/src/codegen/async_dispatchers.rs b/macros/src/codegen/async_dispatchers.rs deleted file mode 100644 index a12ad32..0000000 --- a/macros/src/codegen/async_dispatchers.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates task dispatchers -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut items = vec![]; - - let interrupts = &analysis.interrupts; - - // Generate executor definition and priority in global scope - for (name, _) in app.software_tasks.iter() { - let type_name = util::internal_task_ident(name, "F"); - let exec_name = util::internal_task_ident(name, "EXEC"); - - items.push(quote!( - #[allow(non_camel_case_types)] - type #type_name = impl core::future::Future; - #[allow(non_upper_case_globals)] - static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> = - rtic::export::executor::AsyncTaskExecutor::new(); - )); - } - - for (&level, channel) in &analysis.channels { - let mut stmts = vec![]; - - let dispatcher_name = if level > 0 { - util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string()) - } else { - util::zero_prio_dispatcher_ident() - }; - - let pend_interrupt = if level > 0 { - let device = &app.args.device; - let enum_ = util::interrupt_ident(); - - quote!(rtic::pend(#device::#enum_::#dispatcher_name);) - } else { - // For 0 priority tasks we don't need to pend anything - quote!() - }; - - for name in channel.tasks.iter() { - let exec_name = util::internal_task_ident(name, "EXEC"); - // TODO: Fix cfg - // let task = &app.software_tasks[name]; - // let cfgs = &task.cfgs; - - stmts.push(quote!( - #exec_name.poll(|| { - #exec_name.set_pending(); - #pend_interrupt - }); - )); - } - - if level > 0 { - let doc = format!("Interrupt handler to dispatch async tasks at priority {level}"); - let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; - items.push(quote!( - #[allow(non_snake_case)] - #[doc = #doc] - #[no_mangle] - #(#attribute)* - unsafe fn #dispatcher_name() { - /// The priority of this interrupt handler - const PRIORITY: u8 = #level; - - rtic::export::run(PRIORITY, || { - #(#stmts)* - }); - } - )); - } else { - items.push(quote!( - #[allow(non_snake_case)] - unsafe fn #dispatcher_name() -> ! { - loop { - #(#stmts)* - } - } - )); - } - } - - quote!(#(#items)*) -} diff --git a/macros/src/codegen/hardware_tasks.rs b/macros/src/codegen/hardware_tasks.rs deleted file mode 100644 index 8a5a8f6..0000000 --- a/macros/src/codegen/hardware_tasks.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut root = vec![]; - let mut user_tasks = vec![]; - - for (name, task) in &app.hardware_tasks { - let symbol = task.args.binds.clone(); - let priority = task.args.priority; - let cfgs = &task.cfgs; - let attrs = &task.attrs; - - mod_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - #(#attrs)* - #(#cfgs)* - unsafe fn #symbol() { - const PRIORITY: u8 = #priority; - - rtic::export::run(PRIORITY, || { - #name( - #name::Context::new() - ) - }); - } - )); - - // `${task}Locals` - if !task.args.local_resources.is_empty() { - let (item, constructor) = - local_resources_struct::codegen(Context::HardwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - // `${task}Resources` - if !task.args.shared_resources.is_empty() { - let (item, constructor) = - shared_resources_struct::codegen(Context::HardwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - // Module generation... - - root.push(module::codegen(Context::HardwareTask(name), app, analysis)); - - // End module generation - - if !task.is_extern { - let attrs = &task.attrs; - let context = &task.context; - let stmts = &task.stmts; - user_tasks.push(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(#context: #name::Context) { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - } - } - - quote!( - #(#mod_app)* - - #(#root)* - - #(#user_tasks)* - ) -} diff --git a/macros/src/codegen/idle.rs b/macros/src/codegen/idle.rs deleted file mode 100644 index 0c833ef..0000000 --- a/macros/src/codegen/idle.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates support code for `#[idle]` functions -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - if let Some(idle) = &app.idle { - let mut mod_app = vec![]; - let mut root_idle = vec![]; - - let name = &idle.name; - - if !idle.args.shared_resources.is_empty() { - let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app); - - root_idle.push(item); - mod_app.push(constructor); - } - - if !idle.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen(Context::Idle, app); - - root_idle.push(item); - - mod_app.push(constructor); - } - - root_idle.push(module::codegen(Context::Idle, app, analysis)); - - let attrs = &idle.attrs; - let context = &idle.context; - let stmts = &idle.stmts; - let user_idle = Some(quote!( - #(#attrs)* - #[allow(non_snake_case)] - fn #name(#context: #name::Context) -> ! { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - - quote!( - #(#mod_app)* - - #(#root_idle)* - - #user_idle - ) - } else { - quote!() - } -} diff --git a/macros/src/codegen/init.rs b/macros/src/codegen/init.rs deleted file mode 100644 index 6e1059f..0000000 --- a/macros/src/codegen/init.rs +++ /dev/null @@ -1,95 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module}, - syntax::{ast::App, Context}, -}; - -/// Generates support code for `#[init]` functions -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let init = &app.init; - let name = &init.name; - - let mut root_init = vec![]; - - let context = &init.context; - let attrs = &init.attrs; - let stmts = &init.stmts; - let shared = &init.user_shared_struct; - let local = &init.user_local_struct; - - let shared_resources: Vec<_> = app - .shared_resources - .iter() - .map(|(k, v)| { - let ty = &v.ty; - let cfgs = &v.cfgs; - let docs = &v.docs; - quote!( - #(#cfgs)* - #(#docs)* - #k: #ty, - ) - }) - .collect(); - let local_resources: Vec<_> = app - .local_resources - .iter() - .map(|(k, v)| { - let ty = &v.ty; - let cfgs = &v.cfgs; - let docs = &v.docs; - quote!( - #(#cfgs)* - #(#docs)* - #k: #ty, - ) - }) - .collect(); - - root_init.push(quote! { - struct #shared { - #(#shared_resources)* - } - - struct #local { - #(#local_resources)* - } - }); - - // let locals_pat = locals_pat.iter(); - - let user_init_return = quote! {#shared, #local}; - - let user_init = quote!( - #(#attrs)* - #[inline(always)] - #[allow(non_snake_case)] - fn #name(#context: #name::Context) -> (#user_init_return) { - #(#stmts)* - } - ); - - let mut mod_app = None; - - // `${task}Locals` - if !init.args.local_resources.is_empty() { - let (item, constructor) = local_resources_struct::codegen(Context::Init, app); - - root_init.push(item); - - mod_app = Some(constructor); - } - - root_init.push(module::codegen(Context::Init, app, analysis)); - - quote!( - #mod_app - - #(#root_init)* - - #user_init - ) -} diff --git a/macros/src/codegen/local_resources.rs b/macros/src/codegen/local_resources.rs deleted file mode 100644 index e6d1553..0000000 --- a/macros/src/codegen/local_resources.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::syntax::ast::App; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates `local` variables and local resource proxies -/// -/// I.e. the `static` variables and theirs proxies. -pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - - // All local resources declared in the `#[local]' struct - for (name, res) in &app.local_resources { - let cfgs = &res.cfgs; - let ty = &res.ty; - let mangled_name = util::static_local_resource_ident(name); - - let attrs = &res.attrs; - - // late resources in `util::link_section_uninit` - // unless user specifies custom link section - let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { - None - } else { - Some(util::link_section_uninit()) - }; - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - // #[doc = #doc] - #[doc(hidden)] - #(#attrs)* - #(#cfgs)* - #section - static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); - )); - } - - // All declared `local = [NAME: TY = EXPR]` local resources - for (task_name, resource_name, task_local) in app.declared_local_resources() { - let cfgs = &task_local.cfgs; - let ty = &task_local.ty; - let expr = &task_local.expr; - let attrs = &task_local.attrs; - - let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name); - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - // #[doc = #doc] - #[doc(hidden)] - #(#attrs)* - #(#cfgs)* - static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr); - )); - } - - quote!(#(#mod_app)*) -} diff --git a/macros/src/codegen/local_resources_struct.rs b/macros/src/codegen/local_resources_struct.rs deleted file mode 100644 index 100c3eb..0000000 --- a/macros/src/codegen/local_resources_struct.rs +++ /dev/null @@ -1,102 +0,0 @@ -use crate::syntax::{ - ast::{App, TaskLocal}, - Context, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::codegen::util; - -/// Generates local resources structs -pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { - let resources = match ctxt { - Context::Init => &app.init.args.local_resources, - Context::Idle => { - &app.idle - .as_ref() - .expect("RTIC-ICE: unable to get idle name") - .args - .local_resources - } - Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources, - Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources, - }; - - let task_name = util::get_task_name(ctxt, app); - - let mut fields = vec![]; - let mut values = vec![]; - - for (name, task_local) in resources { - let (cfgs, ty, is_declared) = match task_local { - TaskLocal::External => { - let r = app.local_resources.get(name).expect("UNREACHABLE"); - (&r.cfgs, &r.ty, false) - } - TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), - }; - - let lt = if ctxt.runs_once() { - quote!('static) - } else { - quote!('a) - }; - - let mangled_name = if matches!(task_local, TaskLocal::External) { - util::static_local_resource_ident(name) - } else { - util::declared_static_local_resource_ident(name, &task_name) - }; - - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: &#lt mut #ty - )); - - let expr = if is_declared { - // If the local resources is already initialized, we only need to access its value and - // not go through an `MaybeUninit` - quote!(&mut *#mangled_name.get_mut()) - } else { - quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) - }; - - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - - fields.push(quote!( - #[doc(hidden)] - pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); - - let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); - let ident = util::local_resources_ident(ctxt, app); - let item = quote!( - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - #[doc = #doc] - pub struct #ident<'a> { - #(#fields,)* - } - ); - - let constructor = quote!( - impl<'a> #ident<'a> { - #[inline(always)] - #[allow(missing_docs)] - pub unsafe fn new() -> Self { - #ident { - #(#values,)* - } - } - } - ); - - (item, constructor) -} diff --git a/macros/src/codegen/main.rs b/macros/src/codegen/main.rs deleted file mode 100644 index 2775d25..0000000 --- a/macros/src/codegen/main.rs +++ /dev/null @@ -1,52 +0,0 @@ -use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use super::{assertions, post_init, pre_init}; - -/// Generates code for `fn main` -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let assertion_stmts = assertions::codegen(app, analysis); - - let pre_init_stmts = pre_init::codegen(app, analysis); - - let post_init_stmts = post_init::codegen(app, analysis); - - let call_idle = if let Some(idle) = &app.idle { - let name = &idle.name; - quote!(#name(#name::Context::new())) - } else if analysis.channels.get(&0).is_some() { - let dispatcher = util::zero_prio_dispatcher_ident(); - quote!(#dispatcher();) - } else { - quote!(loop { - rtic::export::nop() - }) - }; - - let main = util::suffixed("main"); - let init_name = &app.init.name; - quote!( - #[doc(hidden)] - #[no_mangle] - unsafe extern "C" fn #main() -> ! { - #(#assertion_stmts)* - - #(#pre_init_stmts)* - - #[inline(never)] - fn __rtic_init_resources(f: F) where F: FnOnce() { - f(); - } - - // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. - __rtic_init_resources(||{ - let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into())); - - #(#post_init_stmts)* - }); - - #call_idle - } - ) -} diff --git a/macros/src/codegen/module.rs b/macros/src/codegen/module.rs deleted file mode 100644 index 4725b9a..0000000 --- a/macros/src/codegen/module.rs +++ /dev/null @@ -1,194 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -#[allow(clippy::too_many_lines)] -pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { - let mut items = vec![]; - let mut module_items = vec![]; - let mut fields = vec![]; - let mut values = vec![]; - // Used to copy task cfgs to the whole module - let mut task_cfgs = vec![]; - - let name = ctxt.ident(app); - - match ctxt { - Context::Init => { - fields.push(quote!( - /// Core (Cortex-M) peripherals - pub core: rtic::export::Peripherals - )); - - if app.args.peripherals { - let device = &app.args.device; - - fields.push(quote!( - /// Device peripherals - pub device: #device::Peripherals - )); - - values.push(quote!(device: #device::Peripherals::steal())); - } - - fields.push(quote!( - /// Critical section token for init - pub cs: rtic::export::CriticalSection<'a> - )); - - values.push(quote!(cs: rtic::export::CriticalSection::new())); - - values.push(quote!(core)); - } - - Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {} - } - - if ctxt.has_local_resources(app) { - let ident = util::local_resources_ident(ctxt, app); - - module_items.push(quote!( - #[doc(inline)] - pub use super::#ident as LocalResources; - )); - - fields.push(quote!( - /// Local Resources this task has access to - pub local: #name::LocalResources<'a> - )); - - values.push(quote!(local: #name::LocalResources::new())); - } - - if ctxt.has_shared_resources(app) { - let ident = util::shared_resources_ident(ctxt, app); - - module_items.push(quote!( - #[doc(inline)] - pub use super::#ident as SharedResources; - )); - - fields.push(quote!( - /// Shared Resources this task has access to - pub shared: #name::SharedResources<'a> - )); - - values.push(quote!(shared: #name::SharedResources::new())); - } - - let doc = match ctxt { - Context::Idle => "Idle loop", - Context::Init => "Initialization function", - Context::HardwareTask(_) => "Hardware task", - Context::SoftwareTask(_) => "Software task", - }; - - let v = Vec::new(); - let cfgs = match ctxt { - Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs, - Context::SoftwareTask(t) => &app.software_tasks[t].cfgs, - _ => &v, - }; - - let core = if ctxt.is_init() { - Some(quote!(core: rtic::export::Peripherals,)) - } else { - None - }; - - let internal_context_name = util::internal_task_ident(name, "Context"); - let exec_name = util::internal_task_ident(name, "EXEC"); - - items.push(quote!( - #(#cfgs)* - /// Execution context - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - pub struct #internal_context_name<'a> { - #[doc(hidden)] - __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, - #(#fields,)* - } - - #(#cfgs)* - impl<'a> #internal_context_name<'a> { - #[inline(always)] - #[allow(missing_docs)] - pub unsafe fn new(#core) -> Self { - #internal_context_name { - __rtic_internal_p: ::core::marker::PhantomData, - #(#values,)* - } - } - } - )); - - module_items.push(quote!( - #(#cfgs)* - #[doc(inline)] - pub use super::#internal_context_name as Context; - )); - - if let Context::SoftwareTask(..) = ctxt { - let spawnee = &app.software_tasks[name]; - let priority = spawnee.args.priority; - let cfgs = &spawnee.cfgs; - // Store a copy of the task cfgs - task_cfgs = cfgs.clone(); - - let pend_interrupt = if priority > 0 { - let device = &app.args.device; - let enum_ = util::interrupt_ident(); - let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0; - quote!(rtic::pend(#device::#enum_::#interrupt);) - } else { - quote!() - }; - - let internal_spawn_ident = util::internal_task_ident(name, "spawn"); - let (input_args, input_tupled, input_untupled, input_ty) = - util::regroup_inputs(&spawnee.inputs); - - // Spawn caller - items.push(quote!( - #(#cfgs)* - /// Spawns the task directly - #[allow(non_snake_case)] - #[doc(hidden)] - pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { - - if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { - - #pend_interrupt - - Ok(()) - } else { - Err(#input_tupled) - } - - } - )); - - module_items.push(quote!( - #(#cfgs)* - #[doc(inline)] - pub use super::#internal_spawn_ident as spawn; - )); - } - - if items.is_empty() { - quote!() - } else { - quote!( - #(#items)* - - #[allow(non_snake_case)] - #(#task_cfgs)* - #[doc = #doc] - pub mod #name { - #(#module_items)* - } - ) - } -} diff --git a/macros/src/codegen/post_init.rs b/macros/src/codegen/post_init.rs deleted file mode 100644 index c4e5383..0000000 --- a/macros/src/codegen/post_init.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -/// Generates code that runs after `#[init]` returns -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - // Initialize shared resources - for (name, res) in &app.shared_resources { - let mangled_name = util::static_shared_resource_ident(name); - // If it's live - let cfgs = res.cfgs.clone(); - if analysis.shared_resources.get(name).is_some() { - stmts.push(quote!( - // We include the cfgs - #(#cfgs)* - // Resource is a RacyCell> - // - `get_mut` to obtain a raw pointer to `MaybeUninit` - // - `write` the defined value for the late resource T - #mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name)); - )); - } - } - - // Initialize local resources - for (name, res) in &app.local_resources { - let mangled_name = util::static_local_resource_ident(name); - // If it's live - let cfgs = res.cfgs.clone(); - if analysis.local_resources.get(name).is_some() { - stmts.push(quote!( - // We include the cfgs - #(#cfgs)* - // Resource is a RacyCell> - // - `get_mut` to obtain a raw pointer to `MaybeUninit` - // - `write` the defined value for the late resource T - #mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name)); - )); - } - } - - // Enable the interrupts -- this completes the `init`-ialization phase - stmts.push(quote!(rtic::export::interrupt::enable();)); - - stmts -} diff --git a/macros/src/codegen/pre_init.rs b/macros/src/codegen/pre_init.rs deleted file mode 100644 index 28ba29c..0000000 --- a/macros/src/codegen/pre_init.rs +++ /dev/null @@ -1,85 +0,0 @@ -use crate::syntax::ast::App; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::{analyze::Analysis, codegen::util}; - -/// Generates code that runs before `#[init]` -pub fn codegen(app: &App, analysis: &Analysis) -> Vec { - let mut stmts = vec![]; - - let rt_err = util::rt_err_ident(); - - // Disable interrupts -- `init` must run with interrupts disabled - stmts.push(quote!(rtic::export::interrupt::disable();)); - - stmts.push(quote!( - // To set the variable in cortex_m so the peripherals cannot be taken multiple times - let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); - )); - - let device = &app.args.device; - let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); - - // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether - // they are used or not - let interrupt = util::interrupt_ident(); - for name in app.args.dispatchers.keys() { - stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); - } - - let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); - - // Unmask interrupts and set their priorities - for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { - if util::is_exception(&task.args.binds) { - // We do exceptions in another pass - None - } else { - Some((&task.args.priority, &task.args.binds)) - } - })) { - let es = format!( - "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" - ); - // Compile time assert that this priority is supported by the device - stmts.push(quote!( - const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; - )); - - stmts.push(quote!( - core.NVIC.set_priority( - #rt_err::#interrupt::#name, - rtic::export::logical2hw(#priority, #nvic_prio_bits), - ); - )); - - // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended - // interrupt is implementation defined - stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);)); - } - - // Set exception priorities - for (name, priority) in app.hardware_tasks.values().filter_map(|task| { - if util::is_exception(&task.args.binds) { - Some((&task.args.binds, task.args.priority)) - } else { - None - } - }) { - let es = format!( - "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" - ); - // Compile time assert that this priority is supported by the device - stmts.push(quote!( - const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; - )); - - stmts.push(quote!(core.SCB.set_priority( - rtic::export::SystemHandler::#name, - rtic::export::logical2hw(#priority, #nvic_prio_bits), - );)); - } - - stmts -} diff --git a/macros/src/codegen/shared_resources.rs b/macros/src/codegen/shared_resources.rs deleted file mode 100644 index 19fd13f..0000000 --- a/macros/src/codegen/shared_resources.rs +++ /dev/null @@ -1,183 +0,0 @@ -use crate::syntax::{analyze::Ownership, ast::App}; -use crate::{analyze::Analysis, codegen::util}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; -use std::collections::HashMap; - -/// Generates `static` variables and shared resource proxies -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut mod_resources = vec![]; - - for (name, res) in &app.shared_resources { - let cfgs = &res.cfgs; - let ty = &res.ty; - let mangled_name = &util::static_shared_resource_ident(name); - - let attrs = &res.attrs; - - // late resources in `util::link_section_uninit` - // unless user specifies custom link section - let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { - None - } else { - Some(util::link_section_uninit()) - }; - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - mod_app.push(quote!( - #[allow(non_camel_case_types)] - #[allow(non_upper_case_globals)] - // #[doc = #doc] - #[doc(hidden)] - #(#attrs)* - #(#cfgs)* - #section - static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); - )); - - // For future use - // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); - - let shared_name = util::need_to_lock_ident(name); - - if !res.properties.lock_free { - mod_resources.push(quote!( - // #[doc = #doc] - #[doc(hidden)] - #[allow(non_camel_case_types)] - #(#cfgs)* - pub struct #shared_name<'a> { - __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, - } - - #(#cfgs)* - impl<'a> #shared_name<'a> { - #[inline(always)] - pub unsafe fn new() -> Self { - #shared_name { __rtic_internal_p: ::core::marker::PhantomData } - } - } - )); - - let ptr = quote!( - #(#cfgs)* - #mangled_name.get_mut() as *mut _ - ); - - let ceiling = match analysis.ownerships.get(name) { - Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority, - Some(Ownership::Contended { ceiling }) => *ceiling, - None => 0, - }; - - // For future use - // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); - - mod_app.push(util::impl_mutex( - app, - cfgs, - true, - &shared_name, - "e!(#ty), - ceiling, - &ptr, - )); - } - } - - let mod_resources = if mod_resources.is_empty() { - quote!() - } else { - quote!(mod shared_resources { - #(#mod_resources)* - }) - }; - - // Computing mapping of used interrupts to masks - let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); - - let mut prio_to_masks = HashMap::new(); - let device = &app.args.device; - let mut uses_exceptions_with_resources = false; - - let mut mask_ids = Vec::new(); - - for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| { - if !util::is_exception(&task.args.binds) { - Some((&task.args.priority, &task.args.binds)) - } else { - // If any resource to the exception uses non-lock-free or non-local resources this is - // not allwed on thumbv6. - uses_exceptions_with_resources = uses_exceptions_with_resources - || task - .args - .shared_resources - .iter() - .map(|(ident, access)| { - if access.is_exclusive() { - if let Some(r) = app.shared_resources.get(ident) { - !r.properties.lock_free - } else { - false - } - } else { - false - } - }) - .any(|v| v); - - None - } - })) { - let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); - v.push(quote!(#device::Interrupt::#name as u32)); - mask_ids.push(quote!(#device::Interrupt::#name as u32)); - } - - // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts - - let mut mask_arr = Vec::new(); - // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec - for i in 0..3 { - let v = if let Some(v) = prio_to_masks.get(&i) { - v.clone() - } else { - Vec::new() - }; - - mask_arr.push(quote!( - rtic::export::create_mask([#(#v),*]) - )); - } - - // Generate a constant for the number of chunks needed by Mask. - let chunks_name = util::priority_mask_chunks_ident(); - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); - )); - - let masks_name = util::priority_masks_ident(); - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*]; - )); - - if uses_exceptions_with_resources { - mod_app.push(quote!( - #[doc(hidden)] - #[allow(non_upper_case_globals)] - const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic(); - )); - } - - quote!( - #(#mod_app)* - - #mod_resources - ) -} diff --git a/macros/src/codegen/shared_resources_struct.rs b/macros/src/codegen/shared_resources_struct.rs deleted file mode 100644 index fa6f0fc..0000000 --- a/macros/src/codegen/shared_resources_struct.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -use crate::codegen::util; - -/// Generate shared resources structs -pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { - let resources = match ctxt { - Context::Init => unreachable!("Tried to generate shared resources struct for init"), - Context::Idle => { - &app.idle - .as_ref() - .expect("RTIC-ICE: unable to get idle name") - .args - .shared_resources - } - Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources, - Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources, - }; - - let mut fields = vec![]; - let mut values = vec![]; - - for (name, access) in resources { - let res = app.shared_resources.get(name).expect("UNREACHABLE"); - - let cfgs = &res.cfgs; - - // access hold if the resource is [x] (exclusive) or [&x] (shared) - let mut_ = if access.is_exclusive() { - Some(quote!(mut)) - } else { - None - }; - let ty = &res.ty; - let mangled_name = util::static_shared_resource_ident(name); - let shared_name = util::need_to_lock_ident(name); - - if res.properties.lock_free { - // Lock free resources of `idle` and `init` get 'static lifetime - let lt = if ctxt.runs_once() { - quote!('static) - } else { - quote!('a) - }; - - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: &#lt #mut_ #ty - )); - } else if access.is_shared() { - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: &'a #ty - )); - } else { - fields.push(quote!( - #(#cfgs)* - #[allow(missing_docs)] - pub #name: shared_resources::#shared_name<'a> - )); - - values.push(quote!( - #(#cfgs)* - #name: shared_resources::#shared_name::new() - - )); - - // continue as the value has been filled, - continue; - } - - let expr = if access.is_exclusive() { - quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) - } else { - quote!(&*(&*#mangled_name.get()).as_ptr()) - }; - - values.push(quote!( - #(#cfgs)* - #name: #expr - )); - } - - fields.push(quote!( - #[doc(hidden)] - pub __rtic_internal_marker: core::marker::PhantomData<&'a ()> - )); - - values.push(quote!(__rtic_internal_marker: core::marker::PhantomData)); - - let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); - let ident = util::shared_resources_ident(ctxt, app); - let item = quote!( - #[allow(non_snake_case)] - #[allow(non_camel_case_types)] - #[doc = #doc] - pub struct #ident<'a> { - #(#fields,)* - } - ); - - let constructor = quote!( - impl<'a> #ident<'a> { - #[inline(always)] - #[allow(missing_docs)] - pub unsafe fn new() -> Self { - #ident { - #(#values,)* - } - } - } - ); - - (item, constructor) -} diff --git a/macros/src/codegen/software_tasks.rs b/macros/src/codegen/software_tasks.rs deleted file mode 100644 index 34fc851..0000000 --- a/macros/src/codegen/software_tasks.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use crate::{ - analyze::Analysis, - codegen::{local_resources_struct, module, shared_resources_struct}, -}; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; - -pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { - let mut mod_app = vec![]; - let mut root = vec![]; - let mut user_tasks = vec![]; - - // Any task - for (name, task) in app.software_tasks.iter() { - if !task.args.local_resources.is_empty() { - let (item, constructor) = - local_resources_struct::codegen(Context::SoftwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - if !task.args.shared_resources.is_empty() { - let (item, constructor) = - shared_resources_struct::codegen(Context::SoftwareTask(name), app); - - root.push(item); - - mod_app.push(constructor); - } - - if !&task.is_extern { - let context = &task.context; - let attrs = &task.attrs; - let cfgs = &task.cfgs; - let stmts = &task.stmts; - let inputs = &task.inputs; - - user_tasks.push(quote!( - #(#attrs)* - #(#cfgs)* - #[allow(non_snake_case)] - async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) { - use rtic::Mutex as _; - use rtic::mutex::prelude::*; - - #(#stmts)* - } - )); - } - - root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); - } - - quote!( - #(#mod_app)* - - #(#root)* - - #(#user_tasks)* - ) -} diff --git a/macros/src/codegen/util.rs b/macros/src/codegen/util.rs deleted file mode 100644 index e121487..0000000 --- a/macros/src/codegen/util.rs +++ /dev/null @@ -1,238 +0,0 @@ -use crate::syntax::{ast::App, Context}; -use core::sync::atomic::{AtomicUsize, Ordering}; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::quote; -use syn::{Attribute, Ident, PatType}; - -const RTIC_INTERNAL: &str = "__rtic_internal"; - -/// Generates a `Mutex` implementation -pub fn impl_mutex( - app: &App, - cfgs: &[Attribute], - resources_prefix: bool, - name: &Ident, - ty: &TokenStream2, - ceiling: u8, - ptr: &TokenStream2, -) -> TokenStream2 { - let path = if resources_prefix { - quote!(shared_resources::#name) - } else { - quote!(#name) - }; - - let device = &app.args.device; - let masks_name = priority_masks_ident(); - quote!( - #(#cfgs)* - impl<'a> rtic::Mutex for #path<'a> { - type T = #ty; - - #[inline(always)] - fn lock(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { - /// Priority ceiling - const CEILING: u8 = #ceiling; - - unsafe { - rtic::export::lock( - #ptr, - CEILING, - #device::NVIC_PRIO_BITS, - &#masks_name, - f, - ) - } - } - } - ) -} - -pub fn interrupt_ident() -> Ident { - let span = Span::call_site(); - Ident::new("interrupt", span) -} - -/// Whether `name` is an exception with configurable priority -pub fn is_exception(name: &Ident) -> bool { - let s = name.to_string(); - - matches!( - &*s, - "MemoryManagement" - | "BusFault" - | "UsageFault" - | "SecureFault" - | "SVCall" - | "DebugMonitor" - | "PendSV" - | "SysTick" - ) -} - -/// Mark a name as internal -pub fn mark_internal_name(name: &str) -> Ident { - Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) -} - -/// Generate an internal identifier for tasks -pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { - mark_internal_name(&format!("{task}_{ident_name}")) -} - -fn link_section_index() -> usize { - static INDEX: AtomicUsize = AtomicUsize::new(0); - - INDEX.fetch_add(1, Ordering::Relaxed) -} - -/// Add `link_section` attribute -pub fn link_section_uninit() -> TokenStream2 { - let section = format!(".uninit.rtic{}", link_section_index()); - - quote!(#[link_section = #section]) -} - -/// Regroups the inputs of a task -/// -/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] -pub fn regroup_inputs( - inputs: &[PatType], -) -> ( - // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] - Vec, - // tupled e.g. `_0`, `(_0, _1)` - TokenStream2, - // untupled e.g. &[`_0`], &[`_0`, `_1`] - Vec, - // ty e.g. `Foo`, `(i32, i64)` - TokenStream2, -) { - if inputs.len() == 1 { - let ty = &inputs[0].ty; - - ( - vec![quote!(_0: #ty)], - quote!(_0), - vec![quote!(_0)], - quote!(#ty), - ) - } else { - let mut args = vec![]; - let mut pats = vec![]; - let mut tys = vec![]; - - for (i, input) in inputs.iter().enumerate() { - let i = Ident::new(&format!("_{}", i), Span::call_site()); - let ty = &input.ty; - - args.push(quote!(#i: #ty)); - - pats.push(quote!(#i)); - - tys.push(quote!(#ty)); - } - - let tupled = { - let pats = pats.clone(); - quote!((#(#pats,)*)) - }; - let ty = quote!((#(#tys,)*)); - (args, tupled, pats, ty) - } -} - -/// Get the ident for the name of the task -pub fn get_task_name(ctxt: Context, app: &App) -> Ident { - let s = match ctxt { - Context::Init => app.init.name.to_string(), - Context::Idle => app - .idle - .as_ref() - .expect("RTIC-ICE: unable to find idle name") - .name - .to_string(), - Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), - }; - - Ident::new(&s, Span::call_site()) -} - -/// Generates a pre-reexport identifier for the "shared resources" struct -pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { - let mut s = match ctxt { - Context::Init => app.init.name.to_string(), - Context::Idle => app - .idle - .as_ref() - .expect("RTIC-ICE: unable to find idle name") - .name - .to_string(), - Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), - }; - - s.push_str("SharedResources"); - - mark_internal_name(&s) -} - -/// Generates a pre-reexport identifier for the "local resources" struct -pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { - let mut s = match ctxt { - Context::Init => app.init.name.to_string(), - Context::Idle => app - .idle - .as_ref() - .expect("RTIC-ICE: unable to find idle name") - .name - .to_string(), - Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), - }; - - s.push_str("LocalResources"); - - mark_internal_name(&s) -} - -/// Suffixed identifier -pub fn suffixed(name: &str) -> Ident { - let span = Span::call_site(); - Ident::new(name, span) -} - -pub fn static_shared_resource_ident(name: &Ident) -> Ident { - mark_internal_name(&format!("shared_resource_{name}")) -} - -/// Generates an Ident for the number of 32 bit chunks used for Mask storage. -pub fn priority_mask_chunks_ident() -> Ident { - mark_internal_name("MASK_CHUNKS") -} - -pub fn priority_masks_ident() -> Ident { - mark_internal_name("MASKS") -} - -pub fn static_local_resource_ident(name: &Ident) -> Ident { - mark_internal_name(&format!("local_resource_{name}")) -} - -pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { - mark_internal_name(&format!("local_{task_name}_{name}")) -} - -pub fn need_to_lock_ident(name: &Ident) -> Ident { - Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span()) -} - -pub fn zero_prio_dispatcher_ident() -> Ident { - Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site()) -} - -/// The name to get better RT flag errors -pub fn rt_err_ident() -> Ident { - Ident::new( - "you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml", - Span::call_site(), - ) -} diff --git a/macros/src/lib.rs b/macros/src/lib.rs deleted file mode 100644 index a8422d0..0000000 --- a/macros/src/lib.rs +++ /dev/null @@ -1,92 +0,0 @@ -#![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" -)] - -//deny_warnings_placeholder_for_ci - -use proc_macro::TokenStream; -use std::{env, fs, path::Path}; - -mod analyze; -mod bindings; -mod check; -mod codegen; -mod syntax; - -// Used for mocking the API in testing -#[doc(hidden)] -#[proc_macro_attribute] -pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { - if let Err(e) = syntax::parse(args, input) { - e.to_compile_error().into() - } else { - "fn main() {}".parse().unwrap() - } -} - -/// Attribute used to declare a RTIC application -/// -/// For user documentation see the [RTIC book](https://rtic.rs) -/// -/// # Panics -/// -/// Should never panic, cargo feeds a path which is later converted to a string -#[proc_macro_attribute] -pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { - let (app, analysis) = match syntax::parse(args, input) { - Err(e) => return e.to_compile_error().into(), - Ok(x) => x, - }; - - if let Err(e) = check::app(&app) { - return e.to_compile_error().into(); - } - - let analysis = analyze::app(analysis, &app); - - let ts = codegen::app(&app, &analysis); - - // Default output path: /target/ - let mut out_dir = Path::new("target"); - - // Get output directory from Cargo environment - // TODO don't want to break builds if OUT_DIR is not set, is this ever the case? - let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string()); - - if !out_dir.exists() { - // Set out_dir to OUT_DIR - out_dir = Path::new(&out_str); - - // Default build path, annotated below: - // $(pwd)/target/thumbv7em-none-eabihf/debug/build/cortex-m-rtic-/out/ - // ///debug/build/cortex-m-rtic-/out/ - // - // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv") - // and use the parent() of this path - // - // If no "target" directory is found, / is used - for path in out_dir.ancestors() { - if let Some(dir) = path.components().last() { - let dir = dir.as_os_str().to_str().unwrap(); - - if dir.starts_with("thumbv") || dir.starts_with("riscv") { - if let Some(out) = path.parent() { - out_dir = out; - break; - } - // If no parent, just use it - out_dir = path; - break; - } - } - } - } - - // Try to write the expanded code to disk - if let Some(out_str) = out_dir.to_str() { - fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok(); - } - - ts.into() -} diff --git a/macros/src/syntax.rs b/macros/src/syntax.rs deleted file mode 100644 index d6f5a47..0000000 --- a/macros/src/syntax.rs +++ /dev/null @@ -1,121 +0,0 @@ -#[allow(unused_extern_crates)] -extern crate proc_macro; - -use proc_macro::TokenStream; - -use indexmap::{IndexMap, IndexSet}; -use proc_macro2::TokenStream as TokenStream2; -use syn::Ident; - -use crate::syntax::ast::App; - -mod accessors; -pub mod analyze; -pub mod ast; -mod check; -mod parse; - -/// An ordered map keyed by identifier -pub type Map = IndexMap; - -/// An order set -pub type Set = IndexSet; - -/// Execution context -#[derive(Clone, Copy)] -pub enum Context<'a> { - /// The `idle` context - Idle, - - /// The `init`-ialization function - Init, - - /// A async software task - SoftwareTask(&'a Ident), - - /// A hardware task - HardwareTask(&'a Ident), -} - -impl<'a> Context<'a> { - /// The identifier of this context - pub fn ident(&self, app: &'a App) -> &'a Ident { - match self { - Context::HardwareTask(ident) => ident, - Context::Idle => &app.idle.as_ref().unwrap().name, - Context::Init => &app.init.name, - Context::SoftwareTask(ident) => ident, - } - } - - /// Is this the `idle` context? - pub fn is_idle(&self) -> bool { - matches!(self, Context::Idle) - } - - /// Is this the `init`-ialization context? - pub fn is_init(&self) -> bool { - matches!(self, Context::Init) - } - - /// Whether this context runs only once - pub fn runs_once(&self) -> bool { - self.is_init() || self.is_idle() - } - - /// Whether this context has shared resources - pub fn has_shared_resources(&self, app: &App) -> bool { - match *self { - Context::HardwareTask(name) => { - !app.hardware_tasks[name].args.shared_resources.is_empty() - } - Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(), - Context::Init => false, - Context::SoftwareTask(name) => { - !app.software_tasks[name].args.shared_resources.is_empty() - } - } - } - - /// Whether this context has local resources - pub fn has_local_resources(&self, app: &App) -> bool { - match *self { - Context::HardwareTask(name) => { - !app.hardware_tasks[name].args.local_resources.is_empty() - } - Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(), - Context::Init => !app.init.args.local_resources.is_empty(), - Context::SoftwareTask(name) => { - !app.software_tasks[name].args.local_resources.is_empty() - } - } - } -} - -/// Parses the input of the `#[app]` attribute -pub fn parse( - args: TokenStream, - input: TokenStream, -) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - parse2(args.into(), input.into()) -} - -/// `proc_macro2::TokenStream` version of `parse` -pub fn parse2( - args: TokenStream2, - input: TokenStream2, -) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { - let app = parse::app(args, input)?; - check::app(&app)?; - - match analyze::app(&app) { - Err(e) => Err(e), - // If no errors, return the app and analysis results - Ok(analysis) => Ok((app, analysis)), - } -} - -enum Either { - Left(A), - Right(B), -} diff --git a/macros/src/syntax/.github/bors.toml b/macros/src/syntax/.github/bors.toml deleted file mode 100644 index aee6042..0000000 --- a/macros/src/syntax/.github/bors.toml +++ /dev/null @@ -1,3 +0,0 @@ -block_labels = ["S-blocked"] -delete_merged_branches = true -status = ["ci"] diff --git a/macros/src/syntax/.github/workflows/build.yml b/macros/src/syntax/.github/workflows/build.yml deleted file mode 100644 index 29971b1..0000000 --- a/macros/src/syntax/.github/workflows/build.yml +++ /dev/null @@ -1,213 +0,0 @@ -name: Build -on: - pull_request: - push: - branches: - - master - - staging - - trying - - bors/staging - - bors/trying - -env: - CARGO_TERM_COLOR: always - -jobs: - # Run cargo fmt --check - style: - name: style - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - components: rustfmt - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: cargo fmt --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check - - # Compilation check - check: - name: check - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo check - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: check - args: --target=${{ matrix.target }} - - # Clippy - clippy: - name: Cargo clippy - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-gnu - override: true - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: cargo clippy - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: clippy - - # Verify all examples - testexamples: - name: testexamples - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --examples - - # Run test suite for UI - testui: - name: testui - runs-on: ubuntu-20.04 - strategy: - matrix: - target: - - x86_64-unknown-linux-gnu - toolchain: - - stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --test ui - - # Run test suite - test: - name: test - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: thumbv7m-none-eabi - override: true - - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1 - - - name: Fail on warnings - run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs - - - uses: actions-rs/cargo@v1 - with: - use-cross: false - command: test - args: --lib - - # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 - # - # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - ci-success: - name: ci - if: github.event_name == 'push' && success() - needs: - - style - - check - - clippy - - testexamples - - test - - testui - runs-on: ubuntu-20.04 - steps: - - name: Mark the job as a success - run: exit 0 diff --git a/macros/src/syntax/.github/workflows/changelog.yml b/macros/src/syntax/.github/workflows/changelog.yml deleted file mode 100644 index ccf6eb9..0000000 --- a/macros/src/syntax/.github/workflows/changelog.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Check that the changelog is updated for all changes. -# -# This is only run for PRs. - -on: - pull_request: - # opened, reopened, synchronize are the default types for pull_request. - # labeled, unlabeled ensure this check is also run if a label is added or removed. - types: [opened, reopened, labeled, unlabeled, synchronize] - -name: Changelog - -jobs: - changelog: - name: Changelog - runs-on: ubuntu-latest - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - name: Check that changelog updated - uses: dangoslen/changelog-enforcer@v3 - with: - changeLogPath: CHANGELOG.md - skipLabels: 'needs-changelog, skip-changelog' - missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/macros/src/syntax/.github/workflows/properties/build.properties.json b/macros/src/syntax/.github/workflows/properties/build.properties.json deleted file mode 100644 index fd3eed3..0000000 --- a/macros/src/syntax/.github/workflows/properties/build.properties.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Build", - "description": "RTIC Test Suite", - "iconName": "rust", - "categories": ["Rust"] -} diff --git a/macros/src/syntax/.gitignore b/macros/src/syntax/.gitignore deleted file mode 100644 index f8d7c8b..0000000 --- a/macros/src/syntax/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.rs.bk -.#* -/target/ -Cargo.lock diff --git a/macros/src/syntax/.travis.yml b/macros/src/syntax/.travis.yml deleted file mode 100644 index 52d1ffd..0000000 --- a/macros/src/syntax/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: rust - -matrix: - include: - # MSRV - - env: TARGET=x86_64-unknown-linux-gnu - rust: 1.36.0 - - - env: TARGET=x86_64-unknown-linux-gnu - rust: stable - -before_install: set -e - -script: - - bash ci/script.sh - -after_script: set +e - -cache: cargo - -before_cache: - - chmod -R a+r $HOME/.cargo; - -branches: - only: - - staging - - trying - -notifications: - email: - on_success: never diff --git a/macros/src/syntax/accessors.rs b/macros/src/syntax/accessors.rs deleted file mode 100644 index e75dde6..0000000 --- a/macros/src/syntax/accessors.rs +++ /dev/null @@ -1,113 +0,0 @@ -use syn::Ident; - -use crate::syntax::{ - analyze::Priority, - ast::{Access, App, Local, TaskLocal}, -}; - -impl App { - pub(crate) fn shared_resource_accesses( - &self, - ) -> impl Iterator, &Ident, Access)> { - self.idle - .iter() - .flat_map(|idle| { - idle.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(0), name, *access)) - }) - .chain(self.hardware_tasks.values().flat_map(|task| { - task.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(task.args.priority), name, *access)) - })) - .chain(self.software_tasks.values().flat_map(|task| { - task.args - .shared_resources - .iter() - .map(move |(name, access)| (Some(task.args.priority), name, *access)) - })) - } - - fn is_external(task_local: &TaskLocal) -> bool { - matches!(task_local, TaskLocal::External) - } - - pub(crate) fn local_resource_accesses(&self) -> impl Iterator { - self.init - .args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - .chain(self.idle.iter().flat_map(|idle| { - idle.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - .chain(self.hardware_tasks.values().flat_map(|task| { - task.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - .chain(self.software_tasks.values().flat_map(|task| { - task.args - .local_resources - .iter() - .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` - .map(move |(name, _)| name) - })) - } - - fn get_declared_local(tl: &TaskLocal) -> Option<&Local> { - match tl { - TaskLocal::External => None, - TaskLocal::Declared(l) => Some(l), - } - } - - /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`. - /// - /// Returns a vector of (task name, resource name, `Local` struct) - pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> { - self.init - .args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (&self.init.name, name, l)) - }) - .chain(self.idle.iter().flat_map(|idle| { - idle.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl) - .map(|l| (&self.idle.as_ref().unwrap().name, name, l)) - }) - })) - .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| { - task.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (task_name, name, l)) - }) - })) - .chain(self.software_tasks.iter().flat_map(|(task_name, task)| { - task.args - .local_resources - .iter() - .filter_map(move |(name, tl)| { - Self::get_declared_local(tl).map(|l| (task_name, name, l)) - }) - })) - .collect() - } -} diff --git a/macros/src/syntax/analyze.rs b/macros/src/syntax/analyze.rs deleted file mode 100644 index 3ed1487..0000000 --- a/macros/src/syntax/analyze.rs +++ /dev/null @@ -1,417 +0,0 @@ -//! RTIC application analysis - -use core::cmp; -use std::collections::{BTreeMap, BTreeSet, HashMap}; - -use indexmap::{IndexMap, IndexSet}; -use syn::{Ident, Type}; - -use crate::syntax::{ - ast::{App, LocalResources, TaskLocal}, - Set, -}; - -pub(crate) fn app(app: &App) -> Result { - // Collect all tasks into a vector - type TaskName = Ident; - type Priority = u8; - - // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) - let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = - Some(&app.init) - .iter() - .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0)) - .chain(app.idle.iter().map(|ht| { - ( - ht.name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::>(), - &ht.args.local_resources, - 0, - ) - })) - .chain(app.software_tasks.iter().map(|(name, ht)| { - ( - name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::>(), - &ht.args.local_resources, - ht.args.priority, - ) - })) - .chain(app.hardware_tasks.iter().map(|(name, ht)| { - ( - name.clone(), - ht.args - .shared_resources - .iter() - .map(|(v, _)| v) - .collect::>(), - &ht.args.local_resources, - ht.args.priority, - ) - })) - .collect(); - - let mut error = vec![]; - let mut lf_res_with_error = vec![]; - let mut lf_hash = HashMap::new(); - - // Collect lock free resources - let lock_free: Vec<&Ident> = app - .shared_resources - .iter() - .filter(|(_, r)| r.properties.lock_free) - .map(|(i, _)| i) - .collect(); - - // Check that lock_free resources are correct - for lf_res in lock_free.iter() { - for (task, tr, _, priority) in task_resources_list.iter() { - for r in tr { - // Get all uses of resources annotated lock_free - if lf_res == r { - // Check so async tasks do not use lock free resources - if app.software_tasks.get(task).is_some() { - error.push(syn::Error::new( - r.span(), - format!( - "Lock free shared resource {:?} is used by an async tasks, which is forbidden", - r.to_string(), - ), - )); - } - - // HashMap returns the previous existing object if old.key == new.key - if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { - // Check if priority differ, if it does, append to - // list of resources which will be annotated with errors - if priority != lf_res.2 { - lf_res_with_error.push(lf_res.1); - lf_res_with_error.push(r); - } - - // If the resource already violates lock free properties - if lf_res_with_error.contains(&r) { - lf_res_with_error.push(lf_res.1); - lf_res_with_error.push(r); - } - } - } - } - } - } - - // Add error message in the resource struct - for r in lock_free { - if lf_res_with_error.contains(&&r) { - error.push(syn::Error::new( - r.span(), - format!( - "Lock free shared resource {:?} is used by tasks at different priorities", - r.to_string(), - ), - )); - } - } - - // Add error message for each use of the shared resource - for resource in lf_res_with_error.clone() { - error.push(syn::Error::new( - resource.span(), - format!( - "Shared resource {:?} is declared lock free but used by tasks at different priorities", - resource.to_string(), - ), - )); - } - - // Collect local resources - let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect(); - - let mut lr_with_error = vec![]; - let mut lr_hash = HashMap::new(); - - // Check that local resources are not shared - for lr in local { - for (task, _, local_resources, _) in task_resources_list.iter() { - for (name, res) in local_resources.iter() { - // Get all uses of resources annotated lock_free - if lr == name { - match res { - TaskLocal::External => { - // HashMap returns the previous existing object if old.key == new.key - if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) { - lr_with_error.push(lr.1); - lr_with_error.push(name); - } - } - // If a declared local has the same name as the `#[local]` struct, it's an - // direct error - TaskLocal::Declared(_) => { - lr_with_error.push(lr); - lr_with_error.push(name); - } - } - } - } - } - } - - // Add error message for each use of the local resource - for resource in lr_with_error.clone() { - error.push(syn::Error::new( - resource.span(), - format!( - "Local resource {:?} is used by multiple tasks or collides with multiple definitions", - resource.to_string(), - ), - )); - } - - // Check 0-priority async software tasks and idle dependency - for (name, task) in &app.software_tasks { - if task.args.priority == 0 { - // If there is a 0-priority task, there must be no idle - if app.idle.is_some() { - error.push(syn::Error::new( - name.span(), - format!( - "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.", - name.to_string(), - ) - )); - } - } - } - - // Collect errors if any and return/halt - if !error.is_empty() { - let mut err = error.get(0).unwrap().clone(); - error.iter().for_each(|e| err.combine(e.clone())); - return Err(err); - } - - // e. Location of resources - let mut used_shared_resource = IndexSet::new(); - let mut ownerships = Ownerships::new(); - let mut sync_types = SyncTypes::new(); - for (prio, name, access) in app.shared_resource_accesses() { - let res = app.shared_resources.get(name).expect("UNREACHABLE"); - - // (e) - // This shared resource is used - used_shared_resource.insert(name.clone()); - - // (c) - if let Some(priority) = prio { - if let Some(ownership) = ownerships.get_mut(name) { - match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } - if priority != ceiling => - { - *ownership = Ownership::Contended { - ceiling: cmp::max(ceiling, priority), - }; - - if access.is_shared() { - sync_types.insert(res.ty.clone()); - } - } - - Ownership::Owned { priority: ceil } if ceil == priority => { - *ownership = Ownership::CoOwned { priority }; - } - - _ => {} - } - } else { - ownerships.insert(name.clone(), Ownership::Owned { priority }); - } - } - } - - // Create the list of used local resource Idents - let mut used_local_resource = IndexSet::new(); - - for (_, _, locals, _) in task_resources_list { - for (local, _) in locals { - used_local_resource.insert(local.clone()); - } - } - - // Most shared resources need to be `Send`, only 0 prio does not need it - let mut send_types = SendTypes::new(); - - for (name, res) in app.shared_resources.iter() { - if ownerships - .get(name) - .map(|ownership| match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } => ceiling != 0, - }) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } - - // Most local resources need to be `Send` as well, only 0 prio does not need it - for (name, res) in app.local_resources.iter() { - if ownerships - .get(name) - .map(|ownership| match *ownership { - Ownership::Owned { priority: ceiling } - | Ownership::CoOwned { priority: ceiling } - | Ownership::Contended { ceiling } => ceiling != 0, - }) - .unwrap_or(false) - { - send_types.insert(res.ty.clone()); - } - } - - let mut channels = Channels::new(); - - for (name, spawnee) in &app.software_tasks { - let spawnee_prio = spawnee.args.priority; - - let channel = channels.entry(spawnee_prio).or_default(); - channel.tasks.insert(name.clone()); - - // All inputs are send as we do not know from where they may be spawned. - spawnee.inputs.iter().for_each(|input| { - send_types.insert(input.ty.clone()); - }); - } - - // No channel should ever be empty - debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); - - Ok(Analysis { - channels, - shared_resources: used_shared_resource, - local_resources: used_local_resource, - ownerships, - send_types, - sync_types, - }) -} - -// /// Priority ceiling -// pub type Ceiling = Option; - -/// Task priority -pub type Priority = u8; - -/// Resource name -pub type Resource = Ident; - -/// Task name -pub type Task = Ident; - -/// The result of analyzing an RTIC application -pub struct Analysis { - /// SPSC message channels - pub channels: Channels, - - /// Shared resources - /// - /// If a resource is not listed here it means that's a "dead" (never - /// accessed) resource and the backend should not generate code for it - pub shared_resources: UsedSharedResource, - - /// Local resources - /// - /// If a resource is not listed here it means that's a "dead" (never - /// accessed) resource and the backend should not generate code for it - pub local_resources: UsedLocalResource, - - /// Resource ownership - pub ownerships: Ownerships, - - /// These types must implement the `Send` trait - pub send_types: SendTypes, - - /// These types must implement the `Sync` trait - pub sync_types: SyncTypes, -} - -/// All channels, keyed by dispatch priority -pub type Channels = BTreeMap; - -/// Location of all *used* shared resources -pub type UsedSharedResource = IndexSet; - -/// Location of all *used* local resources -pub type UsedLocalResource = IndexSet; - -/// Resource ownership -pub type Ownerships = IndexMap; - -/// These types must implement the `Send` trait -pub type SendTypes = Set>; - -/// These types must implement the `Sync` trait -pub type SyncTypes = Set>; - -/// A channel used to send messages -#[derive(Debug, Default)] -pub struct Channel { - /// The channel capacity - pub capacity: u8, - - /// Tasks that can be spawned on this channel - pub tasks: BTreeSet, -} - -/// Resource ownership -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Ownership { - /// Owned by a single task - Owned { - /// Priority of the task that owns this resource - priority: u8, - }, - - /// "Co-owned" by more than one task; all of them have the same priority - CoOwned { - /// Priority of the tasks that co-own this resource - priority: u8, - }, - - /// Contended by more than one task; the tasks have different priorities - Contended { - /// Priority ceiling - ceiling: u8, - }, -} - -// impl Ownership { -// /// Whether this resource needs to a lock at this priority level -// pub fn needs_lock(&self, priority: u8) -> bool { -// match self { -// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, -// -// Ownership::Contended { ceiling } => { -// debug_assert!(*ceiling >= priority); -// -// priority < *ceiling -// } -// } -// } -// -// /// Whether this resource is exclusively owned -// pub fn is_owned(&self) -> bool { -// matches!(self, Ownership::Owned { .. }) -// } -// } diff --git a/macros/src/syntax/ast.rs b/macros/src/syntax/ast.rs deleted file mode 100644 index 27e6773..0000000 --- a/macros/src/syntax/ast.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Abstract Syntax Tree - -use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; - -use crate::syntax::Map; - -/// The `#[app]` attribute -#[derive(Debug)] -#[non_exhaustive] -pub struct App { - /// The arguments to the `#[app]` attribute - pub args: AppArgs, - - /// The name of the `const` item on which the `#[app]` attribute has been placed - pub name: Ident, - - /// The `#[init]` function - pub init: Init, - - /// The `#[idle]` function - pub idle: Option, - - /// Resources shared between tasks defined in `#[shared]` - pub shared_resources: Map, - - /// Task local resources defined in `#[local]` - pub local_resources: Map, - - /// User imports - pub user_imports: Vec, - - /// User code - pub user_code: Vec, - - /// Hardware tasks: `#[task(binds = ..)]`s - pub hardware_tasks: Map, - - /// Async software tasks: `#[task]` - pub software_tasks: Map, -} - -/// Interrupts used to dispatch software tasks -pub type Dispatchers = Map; - -/// Interrupt that could be used to dispatch software tasks -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct Dispatcher { - /// Attributes that will apply to this interrupt handler - pub attrs: Vec, -} - -/// The arguments of the `#[app]` attribute -#[derive(Debug)] -pub struct AppArgs { - /// Device - pub device: Path, - - /// Peripherals - pub peripherals: bool, - - /// Interrupts used to dispatch software tasks - pub dispatchers: Dispatchers, -} - -/// The `init`-ialization function -#[derive(Debug)] -#[non_exhaustive] -pub struct Init { - /// `init` context metadata - pub args: InitArgs, - - /// Attributes that will apply to this `init` function - pub attrs: Vec, - - /// The name of the `#[init]` function - pub name: Ident, - - /// The context argument - pub context: Box, - - /// The statements that make up this `init` function - pub stmts: Vec, - - /// The name of the user provided shared resources struct - pub user_shared_struct: Ident, - - /// The name of the user provided local resources struct - pub user_local_struct: Ident, -} - -/// `init` context metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct InitArgs { - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, -} - -impl Default for InitArgs { - fn default() -> Self { - Self { - local_resources: LocalResources::new(), - } - } -} - -/// The `idle` context -#[derive(Debug)] -#[non_exhaustive] -pub struct Idle { - /// `idle` context metadata - pub args: IdleArgs, - - /// Attributes that will apply to this `idle` function - pub attrs: Vec, - - /// The name of the `#[idle]` function - pub name: Ident, - - /// The context argument - pub context: Box, - - /// The statements that make up this `idle` function - pub stmts: Vec, -} - -/// `idle` context metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct IdleArgs { - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -impl Default for IdleArgs { - fn default() -> Self { - Self { - local_resources: LocalResources::new(), - shared_resources: SharedResources::new(), - } - } -} - -/// Shared resource properties -#[derive(Debug)] -pub struct SharedResourceProperties { - /// A lock free (exclusive resource) - pub lock_free: bool, -} - -/// A shared resource, defined in `#[shared]` -#[derive(Debug)] -#[non_exhaustive] -pub struct SharedResource { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// `#[doc]` attributes like `/// this is a docstring` - pub docs: Vec, - - /// Attributes that will apply to this resource - pub attrs: Vec, - - /// The type of this resource - pub ty: Box, - - /// Shared resource properties - pub properties: SharedResourceProperties, -} - -/// A local resource, defined in `#[local]` -#[derive(Debug)] -#[non_exhaustive] -pub struct LocalResource { - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// `#[doc]` attributes like `/// this is a docstring` - pub docs: Vec, - - /// Attributes that will apply to this resource - pub attrs: Vec, - - /// The type of this resource - pub ty: Box, -} - -/// An async software task -#[derive(Debug)] -#[non_exhaustive] -pub struct SoftwareTask { - /// Software task metadata - pub args: SoftwareTaskArgs, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// Attributes that will apply to this interrupt handler - pub attrs: Vec, - - /// The context argument - pub context: Box, - - /// The inputs of this software task - pub inputs: Vec, - - /// The statements that make up the task handler - pub stmts: Vec, - - /// The task is declared externally - pub is_extern: bool, -} - -/// Software task metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct SoftwareTaskArgs { - /// The priority of this task - pub priority: u8, - - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -impl Default for SoftwareTaskArgs { - fn default() -> Self { - Self { - priority: 1, - local_resources: LocalResources::new(), - shared_resources: SharedResources::new(), - } - } -} - -/// A hardware task -#[derive(Debug)] -#[non_exhaustive] -pub struct HardwareTask { - /// Hardware task metadata - pub args: HardwareTaskArgs, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// Attributes that will apply to this interrupt handler - pub attrs: Vec, - - /// The context argument - pub context: Box, - - /// The statements that make up the task handler - pub stmts: Vec, - - /// The task is declared externally - pub is_extern: bool, -} - -/// Hardware task metadata -#[derive(Debug)] -#[non_exhaustive] -pub struct HardwareTaskArgs { - /// The interrupt or exception that this task is bound to - pub binds: Ident, - - /// The priority of this task - pub priority: u8, - - /// Local resources that can be accessed from this context - pub local_resources: LocalResources, - - /// Shared resources that can be accessed from this context - pub shared_resources: SharedResources, -} - -/// A `static mut` variable local to and owned by a context -#[derive(Debug)] -#[non_exhaustive] -pub struct Local { - /// Attributes like `#[link_section]` - pub attrs: Vec, - - /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` - pub cfgs: Vec, - - /// Type - pub ty: Box, - - /// Initial value - pub expr: Box, -} - -/// A wrapper of the 2 kinds of locals that tasks can have -#[derive(Debug)] -#[non_exhaustive] -pub enum TaskLocal { - /// The local is declared externally (i.e. `#[local]` struct) - External, - /// The local is declared in the task - Declared(Local), -} - -/// Resource access -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Access { - /// `[x]`, a mutable resource - Exclusive, - - /// `[&x]`, a static non-mutable resource - Shared, -} - -impl Access { - /// Is this enum in the `Exclusive` variant? - pub fn is_exclusive(&self) -> bool { - *self == Access::Exclusive - } - - /// Is this enum in the `Shared` variant? - pub fn is_shared(&self) -> bool { - *self == Access::Shared - } -} - -/// Shared resource access list in task attribute -pub type SharedResources = Map; - -/// Local resource access/declaration list in task attribute -pub type LocalResources = Map; diff --git a/macros/src/syntax/check.rs b/macros/src/syntax/check.rs deleted file mode 100644 index 989d418..0000000 --- a/macros/src/syntax/check.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::collections::HashSet; - -use syn::parse; - -use crate::syntax::ast::App; - -pub fn app(app: &App) -> parse::Result<()> { - // Check that all referenced resources have been declared - // Check that resources are NOT `Exclusive`-ly shared - let mut owners = HashSet::new(); - for (_, name, access) in app.shared_resource_accesses() { - if app.shared_resources.get(name).is_none() { - return Err(parse::Error::new( - name.span(), - "this shared resource has NOT been declared", - )); - } - - if access.is_exclusive() { - owners.insert(name); - } - } - - for name in app.local_resource_accesses() { - if app.local_resources.get(name).is_none() { - return Err(parse::Error::new( - name.span(), - "this local resource has NOT been declared", - )); - } - } - - // Check that no resource has both types of access (`Exclusive` & `Shared`) - let exclusive_accesses = app - .shared_resource_accesses() - .filter_map(|(priority, name, access)| { - if priority.is_some() && access.is_exclusive() { - Some(name) - } else { - None - } - }) - .collect::>(); - for (_, name, access) in app.shared_resource_accesses() { - if access.is_shared() && exclusive_accesses.contains(name) { - return Err(parse::Error::new( - name.span(), - "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`", - )); - } - } - - // check that dispatchers are not used as hardware tasks - for task in app.hardware_tasks.values() { - let binds = &task.args.binds; - - if app.args.dispatchers.contains_key(binds) { - return Err(parse::Error::new( - binds.span(), - "dispatcher interrupts can't be used as hardware tasks", - )); - } - } - - Ok(()) -} diff --git a/macros/src/syntax/optimize.rs b/macros/src/syntax/optimize.rs deleted file mode 100644 index e83ba31..0000000 --- a/macros/src/syntax/optimize.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::collections::{BTreeSet, HashMap}; - -use crate::syntax::ast::App; - -pub fn app(app: &mut App, settings: &Settings) { - // "compress" priorities - // If the user specified, for example, task priorities of "1, 3, 6", - // compress them into "1, 2, 3" as to leave no gaps - if settings.optimize_priorities { - // all task priorities ordered in ascending order - let priorities = app - .hardware_tasks - .values() - .map(|task| Some(task.args.priority)) - .chain( - app.software_tasks - .values() - .map(|task| Some(task.args.priority)), - ) - .collect::>(); - - let map = priorities - .iter() - .cloned() - .zip(1..) - .collect::>(); - - for task in app.hardware_tasks.values_mut() { - task.args.priority = map[&Some(task.args.priority)]; - } - - for task in app.software_tasks.values_mut() { - task.args.priority = map[&Some(task.args.priority)]; - } - } -} diff --git a/macros/src/syntax/parse.rs b/macros/src/syntax/parse.rs deleted file mode 100644 index c78453a..0000000 --- a/macros/src/syntax/parse.rs +++ /dev/null @@ -1,363 +0,0 @@ -mod app; -mod hardware_task; -mod idle; -mod init; -mod resource; -mod software_task; -mod util; - -use proc_macro2::TokenStream as TokenStream2; -use syn::{ - braced, parenthesized, - parse::{self, Parse, ParseStream, Parser}, - token::Brace, - Ident, Item, LitInt, Token, -}; - -use crate::syntax::{ - ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal}, - Either, -}; - -// Parse the app, both app arguments and body (input) -pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result { - let args = AppArgs::parse(args)?; - let input: Input = syn::parse2(input)?; - - App::parse(args, input) -} - -pub(crate) struct Input { - _mod_token: Token![mod], - pub ident: Ident, - _brace_token: Brace, - pub items: Vec, -} - -impl Parse for Input { - fn parse(input: ParseStream<'_>) -> parse::Result { - fn parse_items(input: ParseStream<'_>) -> parse::Result> { - let mut items = vec![]; - - while !input.is_empty() { - items.push(input.parse()?); - } - - Ok(items) - } - - let content; - - let _mod_token = input.parse()?; - let ident = input.parse()?; - let _brace_token = braced!(content in input); - let items = content.call(parse_items)?; - - Ok(Input { - _mod_token, - ident, - _brace_token, - items, - }) - } -} - -fn init_args(tokens: TokenStream2) -> parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - if input.is_empty() { - return Ok(InitArgs::default()); - } - - let mut local_resources = None; - - let content; - parenthesized!(content in input); - - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if content.is_empty() { - break; - } - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - if let Some(locals) = &local_resources { - for (ident, task_local) in locals { - if let TaskLocal::External = task_local { - return Err(parse::Error::new( - ident.span(), - "only declared local resources are allowed in init", - )); - } - } - } - - Ok(InitArgs { - local_resources: local_resources.unwrap_or_default(), - }) - }) - .parse2(tokens) -} - -fn idle_args(tokens: TokenStream2) -> parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - if input.is_empty() { - return Ok(IdleArgs::default()); - } - - let mut shared_resources = None; - let mut local_resources = None; - - let content; - parenthesized!(content in input); - if !content.is_empty() { - loop { - // Parse identifier name - let ident: Ident = content.parse()?; - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident.to_string() { - "shared" => { - if shared_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - shared_resources = Some(util::parse_shared_resources(&content)?); - } - - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - } - - Ok(IdleArgs { - shared_resources: shared_resources.unwrap_or_default(), - local_resources: local_resources.unwrap_or_default(), - }) - }) - .parse2(tokens) -} - -fn task_args(tokens: TokenStream2) -> parse::Result> { - (|input: ParseStream<'_>| -> parse::Result> { - if input.is_empty() { - return Ok(Either::Right(SoftwareTaskArgs::default())); - } - - let mut binds = None; - let mut capacity = None; - let mut priority = None; - let mut shared_resources = None; - let mut local_resources = None; - let mut prio_span = None; - - let content; - parenthesized!(content in input); - loop { - if content.is_empty() { - break; - } - - // Parse identifier name - let ident: Ident = content.parse()?; - let ident_s = ident.to_string(); - - // Handle equal sign - let _: Token![=] = content.parse()?; - - match &*ident_s { - "binds" => { - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // Parse identifier name - let ident = content.parse()?; - - binds = Some(ident); - } - - "capacity" => { - if capacity.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - if binds.is_some() { - return Err(parse::Error::new( - ident.span(), - "hardware tasks can't use the `capacity` argument", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::().ok(); - if value.is_none() || value == Some(0) { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 1...255", - )); - } - - capacity = Some(value.unwrap()); - } - - "priority" => { - if priority.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - // #lit - let lit: LitInt = content.parse()?; - - if !lit.suffix().is_empty() { - return Err(parse::Error::new( - lit.span(), - "this literal must be unsuffixed", - )); - } - - let value = lit.base10_parse::().ok(); - if value.is_none() { - return Err(parse::Error::new( - lit.span(), - "this literal must be in the range 0...255", - )); - } - - prio_span = Some(lit.span()); - priority = Some(value.unwrap()); - } - - "shared" => { - if shared_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - shared_resources = Some(util::parse_shared_resources(&content)?); - } - - "local" => { - if local_resources.is_some() { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - local_resources = Some(util::parse_local_resources(&content)?); - } - - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if content.is_empty() { - break; - } - - // Handle comma: , - let _: Token![,] = content.parse()?; - } - let priority = priority.unwrap_or(1); - let shared_resources = shared_resources.unwrap_or_default(); - let local_resources = local_resources.unwrap_or_default(); - - Ok(if let Some(binds) = binds { - if priority == 0 { - return Err(parse::Error::new( - prio_span.unwrap(), - "hardware tasks are not allowed to be at priority 0", - )); - } - - Either::Left(HardwareTaskArgs { - binds, - priority, - shared_resources, - local_resources, - }) - } else { - Either::Right(SoftwareTaskArgs { - priority, - shared_resources, - local_resources, - }) - }) - }) - .parse2(tokens) -} diff --git a/macros/src/syntax/parse/app.rs b/macros/src/syntax/parse/app.rs deleted file mode 100644 index e797f75..0000000 --- a/macros/src/syntax/parse/app.rs +++ /dev/null @@ -1,480 +0,0 @@ -use std::collections::HashSet; - -// use indexmap::map::Entry; -use proc_macro2::TokenStream as TokenStream2; -use syn::{ - parse::{self, ParseStream, Parser}, - spanned::Spanned, - Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility, -}; - -use super::Input; -use crate::syntax::{ - ast::{ - App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, - LocalResource, SharedResource, SoftwareTask, - }, - parse::{self as syntax_parse, util}, - Either, Map, Set, -}; - -impl AppArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { - (|input: ParseStream<'_>| -> parse::Result { - let mut custom = Set::new(); - let mut device = None; - let mut peripherals = true; - let mut dispatchers = Dispatchers::new(); - - loop { - if input.is_empty() { - break; - } - - // #ident = .. - let ident: Ident = input.parse()?; - let _eq_token: Token![=] = input.parse()?; - - if custom.contains(&ident) { - return Err(parse::Error::new( - ident.span(), - "argument appears more than once", - )); - } - - custom.insert(ident.clone()); - - let ks = ident.to_string(); - - match &*ks { - "device" => { - if let Ok(p) = input.parse::() { - device = Some(p); - } else { - return Err(parse::Error::new( - ident.span(), - "unexpected argument value; this should be a path", - )); - } - } - - "peripherals" => { - if let Ok(p) = input.parse::() { - peripherals = p.value; - } else { - return Err(parse::Error::new( - ident.span(), - "unexpected argument value; this should be a boolean", - )); - } - } - - "dispatchers" => { - if let Ok(p) = input.parse::() { - for e in p.elems { - match e { - Expr::Path(ep) => { - let path = ep.path; - let ident = if path.leading_colon.is_some() - || path.segments.len() != 1 - { - return Err(parse::Error::new( - path.span(), - "interrupt must be an identifier, not a path", - )); - } else { - path.segments[0].ident.clone() - }; - let span = ident.span(); - if dispatchers.contains_key(&ident) { - return Err(parse::Error::new( - span, - "this extern interrupt is listed more than once", - )); - } else { - dispatchers - .insert(ident, Dispatcher { attrs: ep.attrs }); - } - } - _ => { - return Err(parse::Error::new( - e.span(), - "interrupt must be an identifier", - )); - } - } - } - } else { - return Err(parse::Error::new( - ident.span(), - // increasing the length of the error message will break rustfmt - "unexpected argument value; expected an array", - )); - } - } - _ => { - return Err(parse::Error::new(ident.span(), "unexpected argument")); - } - } - - if input.is_empty() { - break; - } - - // , - let _: Token![,] = input.parse()?; - } - - let device = if let Some(device) = device { - device - } else { - return Err(parse::Error::new(input.span(), "missing `device = ...`")); - }; - - Ok(AppArgs { - device, - peripherals, - dispatchers, - }) - }) - .parse2(tokens) - } -} - -impl App { - pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result { - let mut init = None; - let mut idle = None; - - let mut shared_resources_ident = None; - let mut shared_resources = Map::new(); - let mut local_resources_ident = None; - let mut local_resources = Map::new(); - let mut hardware_tasks = Map::new(); - let mut software_tasks = Map::new(); - let mut user_imports = vec![]; - let mut user_code = vec![]; - - let mut seen_idents = HashSet::::new(); - let mut bindings = HashSet::::new(); - - let mut check_binding = |ident: &Ident| { - if bindings.contains(ident) { - return Err(parse::Error::new( - ident.span(), - "this interrupt is already bound", - )); - } else { - bindings.insert(ident.clone()); - } - - Ok(()) - }; - - let mut check_ident = |ident: &Ident| { - if seen_idents.contains(ident) { - return Err(parse::Error::new( - ident.span(), - "this identifier has already been used", - )); - } else { - seen_idents.insert(ident.clone()); - } - - Ok(()) - }; - - for mut item in input.items { - match item { - Item::Fn(mut item) => { - let span = item.sig.ident.span(); - if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "init")) - { - let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; - - // If an init function already exists, error - if init.is_some() { - return Err(parse::Error::new( - span, - "`#[init]` function must appear at most once", - )); - } - - check_ident(&item.sig.ident)?; - - init = Some(Init::parse(args, item)?); - } else if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "idle")) - { - let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; - - // If an idle function already exists, error - if idle.is_some() { - return Err(parse::Error::new( - span, - "`#[idle]` function must appear at most once", - )); - } - - check_ident(&item.sig.ident)?; - - idle = Some(Idle::parse(args, item)?); - } else if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "task")) - { - if hardware_tasks.contains_key(&item.sig.ident) - || software_tasks.contains_key(&item.sig.ident) - { - return Err(parse::Error::new( - span, - "this task is defined multiple times", - )); - } - - match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { - Either::Left(args) => { - check_binding(&args.binds)?; - check_ident(&item.sig.ident)?; - - hardware_tasks.insert( - item.sig.ident.clone(), - HardwareTask::parse(args, item)?, - ); - } - - Either::Right(args) => { - check_ident(&item.sig.ident)?; - - software_tasks.insert( - item.sig.ident.clone(), - SoftwareTask::parse(args, item)?, - ); - } - } - } else { - // Forward normal functions - user_code.push(Item::Fn(item.clone())); - } - } - - Item::Struct(ref mut struct_item) => { - // Match structures with the attribute #[shared], name of structure is not - // important - if let Some(_pos) = struct_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "shared")) - { - let span = struct_item.ident.span(); - - shared_resources_ident = Some(struct_item.ident.clone()); - - if !shared_resources.is_empty() { - return Err(parse::Error::new( - span, - "`#[shared]` struct must appear at most once", - )); - } - - if struct_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - struct_item.span(), - "this item must have inherited / private visibility", - )); - } - - if let Fields::Named(fields) = &mut struct_item.fields { - for field in &mut fields.named { - let ident = field.ident.as_ref().expect("UNREACHABLE"); - - if shared_resources.contains_key(ident) { - return Err(parse::Error::new( - ident.span(), - "this resource is listed more than once", - )); - } - - shared_resources.insert( - ident.clone(), - SharedResource::parse(field, ident.span())?, - ); - } - } else { - return Err(parse::Error::new( - struct_item.span(), - "this `struct` must have named fields", - )); - } - } else if let Some(_pos) = struct_item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "local")) - { - let span = struct_item.ident.span(); - - local_resources_ident = Some(struct_item.ident.clone()); - - if !local_resources.is_empty() { - return Err(parse::Error::new( - span, - "`#[local]` struct must appear at most once", - )); - } - - if struct_item.vis != Visibility::Inherited { - return Err(parse::Error::new( - struct_item.span(), - "this item must have inherited / private visibility", - )); - } - - if let Fields::Named(fields) = &mut struct_item.fields { - for field in &mut fields.named { - let ident = field.ident.as_ref().expect("UNREACHABLE"); - - if local_resources.contains_key(ident) { - return Err(parse::Error::new( - ident.span(), - "this resource is listed more than once", - )); - } - - local_resources.insert( - ident.clone(), - LocalResource::parse(field, ident.span())?, - ); - } - } else { - return Err(parse::Error::new( - struct_item.span(), - "this `struct` must have named fields", - )); - } - } else { - // Structure without the #[resources] attribute should just be passed along - user_code.push(item.clone()); - } - } - - Item::ForeignMod(mod_) => { - if !util::abi_is_rust(&mod_.abi) { - return Err(parse::Error::new( - mod_.abi.extern_token.span(), - "this `extern` block must use the \"Rust\" ABI", - )); - } - - for item in mod_.items { - if let ForeignItem::Fn(mut item) = item { - let span = item.sig.ident.span(); - if let Some(pos) = item - .attrs - .iter() - .position(|attr| util::attr_eq(attr, "task")) - { - if hardware_tasks.contains_key(&item.sig.ident) - || software_tasks.contains_key(&item.sig.ident) - { - return Err(parse::Error::new( - span, - "this task is defined multiple times", - )); - } - - if item.attrs.len() != 1 { - return Err(parse::Error::new( - span, - "`extern` task required `#[task(..)]` attribute", - )); - } - - match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { - Either::Left(args) => { - check_binding(&args.binds)?; - check_ident(&item.sig.ident)?; - - hardware_tasks.insert( - item.sig.ident.clone(), - HardwareTask::parse_foreign(args, item)?, - ); - } - - Either::Right(args) => { - check_ident(&item.sig.ident)?; - - software_tasks.insert( - item.sig.ident.clone(), - SoftwareTask::parse_foreign(args, item)?, - ); - } - } - } else { - return Err(parse::Error::new( - span, - "`extern` task required `#[task(..)]` attribute", - )); - } - } else { - return Err(parse::Error::new( - item.span(), - "this item must live outside the `#[app]` module", - )); - } - } - } - Item::Use(itemuse_) => { - // Store the user provided use-statements - user_imports.push(itemuse_.clone()); - } - _ => { - // Anything else within the module should not make any difference - user_code.push(item.clone()); - } - } - } - - let shared_resources_ident = - shared_resources_ident.expect("No `#[shared]` resource struct defined"); - let local_resources_ident = - local_resources_ident.expect("No `#[local]` resource struct defined"); - let init = init.expect("No `#[init]` function defined"); - - if shared_resources_ident != init.user_shared_struct { - return Err(parse::Error::new( - init.user_shared_struct.span(), - format!( - "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?" - ), - )); - } - - if local_resources_ident != init.user_local_struct { - return Err(parse::Error::new( - init.user_local_struct.span(), - format!( - "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?" - ), - )); - } - - Ok(App { - args, - name: input.ident, - init, - idle, - shared_resources, - local_resources, - user_imports, - user_code, - hardware_tasks, - software_tasks, - }) - } -} diff --git a/macros/src/syntax/parse/hardware_task.rs b/macros/src/syntax/parse/hardware_task.rs deleted file mode 100644 index 7f6dfbe..0000000 --- a/macros/src/syntax/parse/hardware_task.rs +++ /dev/null @@ -1,76 +0,0 @@ -use syn::{parse, ForeignItemFn, ItemFn, Stmt}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{HardwareTask, HardwareTaskArgs}, - parse::util, -}; - -impl HardwareTask { - pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result { - let span = item.sig.ident.span(); - let valid_signature = util::check_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_unit(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: item.block.stmts, - is_extern: false, - }); - } - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `fn({name}::Context)`"), - )) - } -} - -impl HardwareTask { - pub(crate) fn parse_foreign( - args: HardwareTaskArgs, - item: ForeignItemFn, - ) -> parse::Result { - let span = item.sig.ident.span(); - let valid_signature = util::check_foreign_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_unit(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(HardwareTask { - args, - cfgs, - attrs, - context, - stmts: Vec::::new(), - is_extern: true, - }); - } - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `fn({name}::Context)`"), - )) - } -} diff --git a/macros/src/syntax/parse/idle.rs b/macros/src/syntax/parse/idle.rs deleted file mode 100644 index 124c136..0000000 --- a/macros/src/syntax/parse/idle.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use syn::{parse, ItemFn}; - -use crate::syntax::{ - ast::{Idle, IdleArgs}, - parse::util, -}; - -impl IdleArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { - crate::syntax::parse::idle_args(tokens) - } -} - -impl Idle { - pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result { - let valid_signature = util::check_fn_signature(&item, false) - && item.sig.inputs.len() == 1 - && util::type_is_bottom(&item.sig.output); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Idle { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - }); - } - } - } - - Err(parse::Error::new( - item.sig.ident.span(), - format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"), - )) - } -} diff --git a/macros/src/syntax/parse/init.rs b/macros/src/syntax/parse/init.rs deleted file mode 100644 index 0aea20b..0000000 --- a/macros/src/syntax/parse/init.rs +++ /dev/null @@ -1,51 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; - -use syn::{parse, ItemFn}; - -use crate::syntax::{ - ast::{Init, InitArgs}, - parse::{self as syntax_parse, util}, -}; - -impl InitArgs { - pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { - syntax_parse::init_args(tokens) - } -} - -impl Init { - pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result { - let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1; - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Ok((user_shared_struct, user_local_struct)) = - util::type_is_init_return(&item.sig.output) - { - if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { - if rest.is_empty() { - return Ok(Init { - args, - attrs: item.attrs, - context, - name: item.sig.ident, - stmts: item.block.stmts, - user_shared_struct, - user_local_struct, - }); - } - } - } - } - - Err(parse::Error::new( - span, - format!( - "the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`" - ), - )) - } -} diff --git a/macros/src/syntax/parse/resource.rs b/macros/src/syntax/parse/resource.rs deleted file mode 100644 index ff10057..0000000 --- a/macros/src/syntax/parse/resource.rs +++ /dev/null @@ -1,55 +0,0 @@ -use proc_macro2::Span; -use syn::{parse, Field, Visibility}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{LocalResource, SharedResource, SharedResourceProperties}, - parse::util, -}; - -impl SharedResource { - pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { - cfgs, - mut attrs, - docs, - } = util::filter_attributes(item.attrs.clone()); - - let lock_free = util::extract_lock_free(&mut attrs)?; - - Ok(SharedResource { - cfgs, - attrs, - docs, - ty: Box::new(item.ty.clone()), - properties: SharedResourceProperties { lock_free }, - }) - } -} - -impl LocalResource { - pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { - if item.vis != Visibility::Inherited { - return Err(parse::Error::new( - span, - "this field must have inherited / private visibility", - )); - } - - let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone()); - - Ok(LocalResource { - cfgs, - attrs, - docs, - ty: Box::new(item.ty.clone()), - }) - } -} diff --git a/macros/src/syntax/parse/software_task.rs b/macros/src/syntax/parse/software_task.rs deleted file mode 100644 index 769aa65..0000000 --- a/macros/src/syntax/parse/software_task.rs +++ /dev/null @@ -1,76 +0,0 @@ -use syn::{parse, ForeignItemFn, ItemFn, Stmt}; - -use crate::syntax::parse::util::FilterAttrs; -use crate::syntax::{ - ast::{SoftwareTask, SoftwareTaskArgs}, - parse::util, -}; - -impl SoftwareTask { - pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result { - let valid_signature = util::check_fn_signature(&item, true) - && util::type_is_unit(&item.sig.output) - && item.sig.asyncness.is_some(); - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(SoftwareTask { - args, - attrs, - cfgs, - context, - inputs, - stmts: item.block.stmts, - is_extern: false, - }); - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `async fn({name}::Context, ..)`"), - )) - } -} - -impl SoftwareTask { - pub(crate) fn parse_foreign( - args: SoftwareTaskArgs, - item: ForeignItemFn, - ) -> parse::Result { - let valid_signature = util::check_foreign_fn_signature(&item, true) - && util::type_is_unit(&item.sig.output) - && item.sig.asyncness.is_some(); - - let span = item.sig.ident.span(); - - let name = item.sig.ident.to_string(); - - if valid_signature { - if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { - let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); - - return Ok(SoftwareTask { - args, - attrs, - cfgs, - context, - inputs, - stmts: Vec::::new(), - is_extern: true, - }); - } - } - - Err(parse::Error::new( - span, - format!("this task handler must have type signature `async fn({name}::Context, ..)`"), - )) - } -} diff --git a/macros/src/syntax/parse/util.rs b/macros/src/syntax/parse/util.rs deleted file mode 100644 index 5a5e0c0..0000000 --- a/macros/src/syntax/parse/util.rs +++ /dev/null @@ -1,338 +0,0 @@ -use syn::{ - bracketed, - parse::{self, ParseStream}, - punctuated::Punctuated, - spanned::Spanned, - Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, - PathArguments, ReturnType, Token, Type, Visibility, -}; - -use crate::syntax::{ - ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, - Map, -}; - -pub fn abi_is_rust(abi: &Abi) -> bool { - match &abi.name { - None => true, - Some(s) => s.value() == "Rust", - } -} - -pub fn attr_eq(attr: &Attribute, name: &str) -> bool { - attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { - let segment = attr.path.segments.first().unwrap(); - segment.arguments == PathArguments::None && *segment.ident.to_string() == *name - } -} - -/// checks that a function signature -/// -/// - has no bounds (like where clauses) -/// - is not `async` -/// - is not `const` -/// - is not `unsafe` -/// - is not generic (has no type parameters) -/// - is not variadic -/// - uses the Rust ABI (and not e.g. "C") -pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool { - item.vis == Visibility::Inherited - && item.sig.constness.is_none() - && (item.sig.asyncness.is_none() || allow_async) - && item.sig.abi.is_none() - && item.sig.unsafety.is_none() - && item.sig.generics.params.is_empty() - && item.sig.generics.where_clause.is_none() - && item.sig.variadic.is_none() -} - -#[allow(dead_code)] -pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool { - item.vis == Visibility::Inherited - && item.sig.constness.is_none() - && (item.sig.asyncness.is_none() || allow_async) - && item.sig.abi.is_none() - && item.sig.unsafety.is_none() - && item.sig.generics.params.is_empty() - && item.sig.generics.where_clause.is_none() - && item.sig.variadic.is_none() -} - -pub struct FilterAttrs { - pub cfgs: Vec, - pub docs: Vec, - pub attrs: Vec, -} - -pub fn filter_attributes(input_attrs: Vec) -> FilterAttrs { - let mut cfgs = vec![]; - let mut docs = vec![]; - let mut attrs = vec![]; - - for attr in input_attrs { - if attr_eq(&attr, "cfg") { - cfgs.push(attr); - } else if attr_eq(&attr, "doc") { - docs.push(attr); - } else { - attrs.push(attr); - } - } - - FilterAttrs { cfgs, docs, attrs } -} - -pub fn extract_lock_free(attrs: &mut Vec) -> parse::Result { - if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { - attrs.remove(pos); - Ok(true) - } else { - Ok(false) - } -} - -pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result { - let inner; - bracketed!(inner in content); - - let mut resources = Map::new(); - for e in inner.call(Punctuated::::parse_terminated)? { - let err = Err(parse::Error::new( - e.span(), - "identifier appears more than once in list", - )); - let (access, path) = match e { - Expr::Path(e) => (Access::Exclusive, e.path), - - Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { - Expr::Path(e) => (Access::Shared, e.path.clone()), - - _ => return err, - }, - - _ => return err, - }; - - let ident = extract_resource_name_ident(path)?; - - if resources.contains_key(&ident) { - return Err(parse::Error::new( - ident.span(), - "resource appears more than once in list", - )); - } - - resources.insert(ident, access); - } - - Ok(resources) -} - -fn extract_resource_name_ident(path: Path) -> parse::Result { - if path.leading_colon.is_some() - || path.segments.len() != 1 - || path.segments[0].arguments != PathArguments::None - { - Err(parse::Error::new( - path.span(), - "resource must be an identifier, not a path", - )) - } else { - Ok(path.segments[0].ident.clone()) - } -} - -pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result { - let inner; - bracketed!(inner in content); - - let mut resources = Map::new(); - - for e in inner.call(Punctuated::::parse_terminated)? { - let err = Err(parse::Error::new( - e.span(), - "identifier appears more than once in list", - )); - - let (name, local) = match e { - // local = [IDENT], - Expr::Path(path) => { - if !path.attrs.is_empty() { - return Err(parse::Error::new( - path.span(), - "attributes are not supported here", - )); - } - - let ident = extract_resource_name_ident(path.path)?; - // let (cfgs, attrs) = extract_cfgs(path.attrs); - - (ident, TaskLocal::External) - } - - // local = [IDENT: TYPE = EXPR] - Expr::Assign(e) => { - let (name, ty, cfgs, attrs) = match *e.left { - Expr::Type(t) => { - // Extract name and attributes - let (name, cfgs, attrs) = match *t.expr { - Expr::Path(path) => { - let name = extract_resource_name_ident(path.path)?; - let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); - - (name, cfgs, attrs) - } - _ => return err, - }; - - let ty = t.ty; - - // Error check - match &*ty { - Type::Array(_) => {} - Type::Path(_) => {} - Type::Ptr(_) => {} - Type::Tuple(_) => {} - _ => return Err(parse::Error::new( - ty.span(), - "unsupported type, must be an array, tuple, pointer or type path", - )), - }; - - (name, ty, cfgs, attrs) - } - e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), - }; - - let expr = e.right; // Expr - - ( - name, - TaskLocal::Declared(Local { - attrs, - cfgs, - ty, - expr, - }), - ) - } - - expr => { - return Err(parse::Error::new( - expr.span(), - "malformed, expected 'IDENT: TYPE = EXPR'", - )) - } - }; - - resources.insert(name, local); - } - - Ok(resources) -} - -type ParseInputResult = Option<(Box, Result, FnArg>)>; - -pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { - let mut inputs = inputs.into_iter(); - - match inputs.next() { - Some(FnArg::Typed(first)) => { - if type_is_path(&first.ty, &[name, "Context"]) { - let rest = inputs - .map(|arg| match arg { - FnArg::Typed(arg) => Ok(arg), - _ => Err(arg), - }) - .collect::, _>>(); - - Some((first.pat, rest)) - } else { - None - } - } - - _ => None, - } -} - -pub fn type_is_bottom(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - matches!(**ty, Type::Never(_)) - } else { - false - } -} - -fn extract_init_resource_name_ident(ty: Type) -> Result { - match ty { - Type::Path(path) => { - let path = path.path; - - if path.leading_colon.is_some() - || path.segments.len() != 1 - || path.segments[0].arguments != PathArguments::None - { - Err(()) - } else { - Ok(path.segments[0].ident.clone()) - } - } - _ => Err(()), - } -} - -/// Checks Init's return type, return the user provided types for analysis -pub fn type_is_init_return(ty: &ReturnType) -> Result<(Ident, Ident), ()> { - match ty { - ReturnType::Default => Err(()), - - ReturnType::Type(_, ty) => match &**ty { - Type::Tuple(t) => { - // return should be: - // fn -> (User's #[shared] struct, User's #[local] struct) - // - // We check the length and the last one here, analysis checks that the user - // provided structs are correct. - if t.elems.len() == 2 { - return Ok(( - extract_init_resource_name_ident(t.elems[0].clone())?, - extract_init_resource_name_ident(t.elems[1].clone())?, - )); - } - - Err(()) - } - - _ => Err(()), - }, - } -} - -pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool { - match ty { - Type::Path(tpath) if tpath.qself.is_none() => { - tpath.path.segments.len() == segments.len() - && tpath - .path - .segments - .iter() - .zip(segments) - .all(|(lhs, rhs)| lhs.ident == **rhs) - } - - _ => false, - } -} - -pub fn type_is_unit(ty: &ReturnType) -> bool { - if let ReturnType::Type(_, ty) = ty { - if let Type::Tuple(ref tuple) = **ty { - tuple.elems.is_empty() - } else { - false - } - } else { - true - } -} diff --git a/macros/src/tests.rs b/macros/src/tests.rs deleted file mode 100644 index e9e3326..0000000 --- a/macros/src/tests.rs +++ /dev/null @@ -1,4 +0,0 @@ -// NOTE these tests are specific to the Cortex-M port; `rtic-syntax` has a more extensive test suite -// that tests functionality common to all the RTIC ports - -mod single; diff --git a/macros/src/tests/single.rs b/macros/src/tests/single.rs deleted file mode 100644 index f20c9cc..0000000 --- a/macros/src/tests/single.rs +++ /dev/null @@ -1,40 +0,0 @@ -use quote::quote; -use rtic_syntax::Settings; - -#[test] -fn analyze() { - let mut settings = Settings::default(); - settings.parse_extern_interrupt = true; - let (app, analysis) = rtic_syntax::parse2( - // First interrupt is assigned to the highest priority dispatcher - quote!(device = pac, dispatchers = [B, A]), - quote!( - mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) - } - - #[task(priority = 1)] - fn a(_: a::Context) {} - - #[task(priority = 2)] - fn b(_: b::Context) {} - } - ), - settings, - ) - .unwrap(); - - let analysis = crate::analyze::app(analysis, &app); - let interrupts = &analysis.interrupts; - assert_eq!(interrupts.len(), 2); - assert_eq!(interrupts[&2].0.to_string(), "B"); - assert_eq!(interrupts[&1].0.to_string(), "A"); -} diff --git a/macros/tests/ui.rs b/macros/tests/ui.rs deleted file mode 100644 index 9fb88a1..0000000 --- a/macros/tests/ui.rs +++ /dev/null @@ -1,7 +0,0 @@ -use trybuild::TestCases; - -#[test] -fn ui() { - let t = TestCases::new(); - t.compile_fail("ui/*.rs"); -} diff --git a/macros/ui/extern-interrupt-used.rs b/macros/ui/extern-interrupt-used.rs deleted file mode 100644 index 6346a7d..0000000 --- a/macros/ui/extern-interrupt-used.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock, dispatchers = [EXTI0])] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - #[task(binds = EXTI0)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/extern-interrupt-used.stderr b/macros/ui/extern-interrupt-used.stderr deleted file mode 100644 index 970d39b..0000000 --- a/macros/ui/extern-interrupt-used.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: dispatcher interrupts can't be used as hardware tasks - --> ui/extern-interrupt-used.rs:14:20 - | -14 | #[task(binds = EXTI0)] - | ^^^^^ diff --git a/macros/ui/idle-double-local.rs b/macros/ui/idle-double-local.rs deleted file mode 100644 index 54e67d3..0000000 --- a/macros/ui/idle-double-local.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle(local = [A], local = [B])] - fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-double-local.stderr b/macros/ui/idle-double-local.stderr deleted file mode 100644 index b558136..0000000 --- a/macros/ui/idle-double-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/idle-double-local.rs:5:25 - | -5 | #[idle(local = [A], local = [B])] - | ^^^^^ diff --git a/macros/ui/idle-double-shared.rs b/macros/ui/idle-double-shared.rs deleted file mode 100644 index f66cb93..0000000 --- a/macros/ui/idle-double-shared.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle(shared = [A], shared = [B])] - fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-double-shared.stderr b/macros/ui/idle-double-shared.stderr deleted file mode 100644 index 6f62ad2..0000000 --- a/macros/ui/idle-double-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/idle-double-shared.rs:5:26 - | -5 | #[idle(shared = [A], shared = [B])] - | ^^^^^^ diff --git a/macros/ui/idle-input.rs b/macros/ui/idle-input.rs deleted file mode 100644 index c896b1c..0000000 --- a/macros/ui/idle-input.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle(_: idle::Context, _undef: u32) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-input.stderr b/macros/ui/idle-input.stderr deleted file mode 100644 index 34c38fc..0000000 --- a/macros/ui/idle-input.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-input.rs:6:8 - | -6 | fn idle(_: idle::Context, _undef: u32) -> ! { - | ^^^^ diff --git a/macros/ui/idle-no-context.rs b/macros/ui/idle-no-context.rs deleted file mode 100644 index bab4680..0000000 --- a/macros/ui/idle-no-context.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle() -> ! { - loop {} - } -} diff --git a/macros/ui/idle-no-context.stderr b/macros/ui/idle-no-context.stderr deleted file mode 100644 index c9f4b3d..0000000 --- a/macros/ui/idle-no-context.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-no-context.rs:6:8 - | -6 | fn idle() -> ! { - | ^^^^ diff --git a/macros/ui/idle-not-divergent.rs b/macros/ui/idle-not-divergent.rs deleted file mode 100644 index d1ae8b1..0000000 --- a/macros/ui/idle-not-divergent.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle(_: idle::Context) {} -} diff --git a/macros/ui/idle-not-divergent.stderr b/macros/ui/idle-not-divergent.stderr deleted file mode 100644 index e318f58..0000000 --- a/macros/ui/idle-not-divergent.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-not-divergent.rs:6:8 - | -6 | fn idle(_: idle::Context) {} - | ^^^^ diff --git a/macros/ui/idle-output.rs b/macros/ui/idle-output.rs deleted file mode 100644 index 1662157..0000000 --- a/macros/ui/idle-output.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn idle(_: idle::Context) -> u32 { - 0 - } -} diff --git a/macros/ui/idle-output.stderr b/macros/ui/idle-output.stderr deleted file mode 100644 index 7070e25..0000000 --- a/macros/ui/idle-output.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-output.rs:6:8 - | -6 | fn idle(_: idle::Context) -> u32 { - | ^^^^ diff --git a/macros/ui/idle-pub.rs b/macros/ui/idle-pub.rs deleted file mode 100644 index 0d8dd01..0000000 --- a/macros/ui/idle-pub.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - pub fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-pub.stderr b/macros/ui/idle-pub.stderr deleted file mode 100644 index aa46ac3..0000000 --- a/macros/ui/idle-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-pub.rs:6:12 - | -6 | pub fn idle(_: idle::Context) -> ! { - | ^^^^ diff --git a/macros/ui/idle-unsafe.rs b/macros/ui/idle-unsafe.rs deleted file mode 100644 index 3422ef2..0000000 --- a/macros/ui/idle-unsafe.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - unsafe fn idle(_: idle::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/idle-unsafe.stderr b/macros/ui/idle-unsafe.stderr deleted file mode 100644 index a416800..0000000 --- a/macros/ui/idle-unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this `#[idle]` function must have signature `fn(idle::Context) -> !` - --> ui/idle-unsafe.rs:6:15 - | -6 | unsafe fn idle(_: idle::Context) -> ! { - | ^^^^ diff --git a/macros/ui/init-divergent.rs b/macros/ui/init-divergent.rs deleted file mode 100644 index 5e4e96a..0000000 --- a/macros/ui/init-divergent.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> ! {} -} diff --git a/macros/ui/init-divergent.stderr b/macros/ui/init-divergent.stderr deleted file mode 100644 index 9f6acf6..0000000 --- a/macros/ui/init-divergent.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-divergent.rs:12:8 - | -12 | fn init(_: init::Context) -> ! {} - | ^^^^ diff --git a/macros/ui/init-double-local.rs b/macros/ui/init-double-local.rs deleted file mode 100644 index 5f6d7ac..0000000 --- a/macros/ui/init-double-local.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init(local = [A], local = [B])] - fn init(_: init::Context) {} -} diff --git a/macros/ui/init-double-local.stderr b/macros/ui/init-double-local.stderr deleted file mode 100644 index 07c3b50..0000000 --- a/macros/ui/init-double-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/init-double-local.rs:5:25 - | -5 | #[init(local = [A], local = [B])] - | ^^^^^ diff --git a/macros/ui/init-double-shared.rs b/macros/ui/init-double-shared.rs deleted file mode 100644 index 4503c87..0000000 --- a/macros/ui/init-double-shared.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init(shared = [A], shared = [B])] - fn init(_: init::Context) {} -} diff --git a/macros/ui/init-double-shared.stderr b/macros/ui/init-double-shared.stderr deleted file mode 100644 index af2a97b..0000000 --- a/macros/ui/init-double-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unexpected argument - --> ui/init-double-shared.rs:5:12 - | -5 | #[init(shared = [A], shared = [B])] - | ^^^^^^ diff --git a/macros/ui/init-input.rs b/macros/ui/init-input.rs deleted file mode 100644 index d41a503..0000000 --- a/macros/ui/init-input.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} -} diff --git a/macros/ui/init-input.stderr b/macros/ui/init-input.stderr deleted file mode 100644 index e236043..0000000 --- a/macros/ui/init-input.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-input.rs:12:8 - | -12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/init-no-context.rs b/macros/ui/init-no-context.rs deleted file mode 100644 index cdce4c5..0000000 --- a/macros/ui/init-no-context.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init() -> (Shared, Local) {} -} diff --git a/macros/ui/init-no-context.stderr b/macros/ui/init-no-context.stderr deleted file mode 100644 index 28e1fd4..0000000 --- a/macros/ui/init-no-context.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-no-context.rs:12:8 - | -12 | fn init() -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/init-output.rs b/macros/ui/init-output.rs deleted file mode 100644 index 7057c95..0000000 --- a/macros/ui/init-output.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init] - fn init(_: init::Context) -> u32 { - 0 - } -} diff --git a/macros/ui/init-output.stderr b/macros/ui/init-output.stderr deleted file mode 100644 index 8bc3c83..0000000 --- a/macros/ui/init-output.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-output.rs:6:8 - | -6 | fn init(_: init::Context) -> u32 { - | ^^^^ diff --git a/macros/ui/init-pub.rs b/macros/ui/init-pub.rs deleted file mode 100644 index dd59aa1..0000000 --- a/macros/ui/init-pub.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - pub fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/init-pub.stderr b/macros/ui/init-pub.stderr deleted file mode 100644 index b1610ed..0000000 --- a/macros/ui/init-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-pub.rs:12:12 - | -12 | pub fn init(_: init::Context) -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/init-unsafe.rs b/macros/ui/init-unsafe.rs deleted file mode 100644 index 4f89baf..0000000 --- a/macros/ui/init-unsafe.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[init] - unsafe fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/init-unsafe.stderr b/macros/ui/init-unsafe.stderr deleted file mode 100644 index fd0b8f3..0000000 --- a/macros/ui/init-unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` - --> ui/init-unsafe.rs:6:15 - | -6 | unsafe fn init(_: init::Context) -> (Shared, Local) {} - | ^^^^ diff --git a/macros/ui/interrupt-double.rs b/macros/ui/interrupt-double.rs deleted file mode 100644 index e2addc7..0000000 --- a/macros/ui/interrupt-double.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = UART0)] - fn foo(_: foo::Context) {} - - #[task(binds = UART0)] - fn bar(_: bar::Context) {} -} diff --git a/macros/ui/interrupt-double.stderr b/macros/ui/interrupt-double.stderr deleted file mode 100644 index 8db34e2..0000000 --- a/macros/ui/interrupt-double.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this interrupt is already bound - --> ui/interrupt-double.rs:8:20 - | -8 | #[task(binds = UART0)] - | ^^^^^ diff --git a/macros/ui/local-collision-2.rs b/macros/ui/local-collision-2.rs deleted file mode 100644 index 08bc8e5..0000000 --- a/macros/ui/local-collision-2.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - a: u32, - } - - #[task(local = [a: u8 = 3])] - async fn bar(_: bar::Context) {} - - #[init(local = [a: u16 = 2])] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-collision-2.stderr b/macros/ui/local-collision-2.stderr deleted file mode 100644 index 47dbbe3..0000000 --- a/macros/ui/local-collision-2.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision-2.rs:10:9 - | -10 | a: u32, - | ^ - -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision-2.rs:16:21 - | -16 | #[init(local = [a: u16 = 2])] - | ^ - -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision-2.rs:13:21 - | -13 | #[task(local = [a: u8 = 3])] - | ^ diff --git a/macros/ui/local-collision.rs b/macros/ui/local-collision.rs deleted file mode 100644 index 0e4eef7..0000000 --- a/macros/ui/local-collision.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - a: u32, - } - - #[task(local = [a])] - async fn foo(_: foo::Context) {} - - #[task(local = [a: u8 = 3])] - async fn bar(_: bar::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-collision.stderr b/macros/ui/local-collision.stderr deleted file mode 100644 index 47fbb6e..0000000 --- a/macros/ui/local-collision.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision.rs:10:9 - | -10 | a: u32, - | ^ - -error: Local resource "a" is used by multiple tasks or collides with multiple definitions - --> ui/local-collision.rs:16:21 - | -16 | #[task(local = [a: u8 = 3])] - | ^ diff --git a/macros/ui/local-malformed-1.rs b/macros/ui/local-malformed-1.rs deleted file mode 100644 index 219eef5..0000000 --- a/macros/ui/local-malformed-1.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a:])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-1.stderr b/macros/ui/local-malformed-1.stderr deleted file mode 100644 index d15c324..0000000 --- a/macros/ui/local-malformed-1.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> ui/local-malformed-1.rs:11:23 - | -11 | #[task(local = [a:])] - | ^ diff --git a/macros/ui/local-malformed-2.rs b/macros/ui/local-malformed-2.rs deleted file mode 100644 index d691453..0000000 --- a/macros/ui/local-malformed-2.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a: u32])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-2.stderr b/macros/ui/local-malformed-2.stderr deleted file mode 100644 index 0b448f0..0000000 --- a/macros/ui/local-malformed-2.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: malformed, expected 'IDENT: TYPE = EXPR' - --> ui/local-malformed-2.rs:11:21 - | -11 | #[task(local = [a: u32])] - | ^^^^^^ diff --git a/macros/ui/local-malformed-3.rs b/macros/ui/local-malformed-3.rs deleted file mode 100644 index 7eddfa4..0000000 --- a/macros/ui/local-malformed-3.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a: u32 =])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-3.stderr b/macros/ui/local-malformed-3.stderr deleted file mode 100644 index 61af4f3..0000000 --- a/macros/ui/local-malformed-3.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: unexpected end of input, expected expression - --> ui/local-malformed-3.rs:11:29 - | -11 | #[task(local = [a: u32 =])] - | ^ diff --git a/macros/ui/local-malformed-4.rs b/macros/ui/local-malformed-4.rs deleted file mode 100644 index b913947..0000000 --- a/macros/ui/local-malformed-4.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [a = u32])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-malformed-4.stderr b/macros/ui/local-malformed-4.stderr deleted file mode 100644 index 0f7d9e7..0000000 --- a/macros/ui/local-malformed-4.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: malformed, expected a type - --> ui/local-malformed-4.rs:11:21 - | -11 | #[task(local = [a = u32])] - | ^ diff --git a/macros/ui/local-not-declared.rs b/macros/ui/local-not-declared.rs deleted file mode 100644 index 7c087e4..0000000 --- a/macros/ui/local-not-declared.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(local = [A])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-not-declared.stderr b/macros/ui/local-not-declared.stderr deleted file mode 100644 index 10d4b04..0000000 --- a/macros/ui/local-not-declared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this local resource has NOT been declared - --> ui/local-not-declared.rs:11:21 - | -11 | #[task(local = [A])] - | ^ diff --git a/macros/ui/local-pub.rs b/macros/ui/local-pub.rs deleted file mode 100644 index 42da4f4..0000000 --- a/macros/ui/local-pub.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - pub x: u32, - } - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/local-pub.stderr b/macros/ui/local-pub.stderr deleted file mode 100644 index e4814ca..0000000 --- a/macros/ui/local-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this field must have inherited / private visibility - --> ui/local-pub.rs:10:13 - | -10 | pub x: u32, - | ^ diff --git a/macros/ui/local-shared-attribute.rs b/macros/ui/local-shared-attribute.rs deleted file mode 100644 index c594b5f..0000000 --- a/macros/ui/local-shared-attribute.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - #[task(local = [ - #[test] - a: u32 = 0, // Ok - #[test] - b, // Error - ])] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/local-shared-attribute.stderr b/macros/ui/local-shared-attribute.stderr deleted file mode 100644 index a8130e8..0000000 --- a/macros/ui/local-shared-attribute.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: attributes are not supported here - --> ui/local-shared-attribute.rs:17:9 - | -17 | / #[test] -18 | | b, // Error - | |_________^ diff --git a/macros/ui/local-shared.rs b/macros/ui/local-shared.rs deleted file mode 100644 index 4e8f9f4..0000000 --- a/macros/ui/local-shared.rs +++ /dev/null @@ -1,28 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local { - l1: u32, - l2: u32, - } - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - // l2 ok - #[idle(local = [l2])] - fn idle(cx: idle::Context) -> ! {} - - // l1 rejected (not local) - #[task(priority = 1, local = [l1])] - async fn uart0(cx: uart0::Context) {} - - // l1 rejected (not lock_free) - #[task(priority = 2, local = [l1])] - async fn uart1(cx: uart1::Context) {} -} diff --git a/macros/ui/local-shared.stderr b/macros/ui/local-shared.stderr deleted file mode 100644 index fceb763..0000000 --- a/macros/ui/local-shared.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: Local resource "l1" is used by multiple tasks or collides with multiple definitions - --> ui/local-shared.rs:22:35 - | -22 | #[task(priority = 1, local = [l1])] - | ^^ - -error: Local resource "l1" is used by multiple tasks or collides with multiple definitions - --> ui/local-shared.rs:26:35 - | -26 | #[task(priority = 2, local = [l1])] - | ^^ diff --git a/macros/ui/shared-lock-free.rs b/macros/ui/shared-lock-free.rs deleted file mode 100644 index b3a4b9c..0000000 --- a/macros/ui/shared-lock-free.rs +++ /dev/null @@ -1,38 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared { - // An exclusive, early resource - #[lock_free] - e1: u32, - - // An exclusive, late resource - #[lock_free] - e2: u32, - } - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - // e2 ok - #[idle(shared = [e2])] - fn idle(cx: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); - loop {} - } - - // e1 rejected (not lock_free) - #[task(binds = UART0, priority = 1, shared = [e1])] - fn uart0(cx: uart0::Context) { - *cx.resources.e1 += 10; - } - - // e1 rejected (not lock_free) - #[task(binds = UART1, priority = 2, shared = [e1])] - fn uart1(cx: uart1::Context) {} -} diff --git a/macros/ui/shared-lock-free.stderr b/macros/ui/shared-lock-free.stderr deleted file mode 100644 index 51e99a0..0000000 --- a/macros/ui/shared-lock-free.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error: Lock free shared resource "e1" is used by tasks at different priorities - --> ui/shared-lock-free.rs:9:9 - | -9 | e1: u32, - | ^^ - -error: Shared resource "e1" is declared lock free but used by tasks at different priorities - --> ui/shared-lock-free.rs:30:51 - | -30 | #[task(binds = UART0, priority = 1, shared = [e1])] - | ^^ - -error: Shared resource "e1" is declared lock free but used by tasks at different priorities - --> ui/shared-lock-free.rs:36:51 - | -36 | #[task(binds = UART1, priority = 2, shared = [e1])] - | ^^ diff --git a/macros/ui/shared-not-declared.rs b/macros/ui/shared-not-declared.rs deleted file mode 100644 index 5fef534..0000000 --- a/macros/ui/shared-not-declared.rs +++ /dev/null @@ -1,16 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[task(shared = [A])] - async fn foo(_: foo::Context) {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} -} diff --git a/macros/ui/shared-not-declared.stderr b/macros/ui/shared-not-declared.stderr deleted file mode 100644 index 7c5fb32..0000000 --- a/macros/ui/shared-not-declared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this shared resource has NOT been declared - --> ui/shared-not-declared.rs:11:22 - | -11 | #[task(shared = [A])] - | ^ diff --git a/macros/ui/shared-pub.rs b/macros/ui/shared-pub.rs deleted file mode 100644 index 10351fd..0000000 --- a/macros/ui/shared-pub.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared { - pub x: u32, - } -} diff --git a/macros/ui/shared-pub.stderr b/macros/ui/shared-pub.stderr deleted file mode 100644 index 7148893..0000000 --- a/macros/ui/shared-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this field must have inherited / private visibility - --> ui/shared-pub.rs:7:13 - | -7 | pub x: u32, - | ^ diff --git a/macros/ui/task-divergent.rs b/macros/ui/task-divergent.rs deleted file mode 100644 index ffe2dc0..0000000 --- a/macros/ui/task-divergent.rs +++ /dev/null @@ -1,9 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - async fn foo(_: foo::Context) -> ! { - loop {} - } -} diff --git a/macros/ui/task-divergent.stderr b/macros/ui/task-divergent.stderr deleted file mode 100644 index dd00208..0000000 --- a/macros/ui/task-divergent.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-divergent.rs:6:14 - | -6 | async fn foo(_: foo::Context) -> ! { - | ^^^ diff --git a/macros/ui/task-double-local.rs b/macros/ui/task-double-local.rs deleted file mode 100644 index c5277e2..0000000 --- a/macros/ui/task-double-local.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(local = [A], local = [B])] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-local.stderr b/macros/ui/task-double-local.stderr deleted file mode 100644 index 91ed844..0000000 --- a/macros/ui/task-double-local.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/task-double-local.rs:5:25 - | -5 | #[task(local = [A], local = [B])] - | ^^^^^ diff --git a/macros/ui/task-double-priority.rs b/macros/ui/task-double-priority.rs deleted file mode 100644 index 5c8bd5b..0000000 --- a/macros/ui/task-double-priority.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(priority = 1, priority = 2)] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-priority.stderr b/macros/ui/task-double-priority.stderr deleted file mode 100644 index b3c814a..0000000 --- a/macros/ui/task-double-priority.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/task-double-priority.rs:5:26 - | -5 | #[task(priority = 1, priority = 2)] - | ^^^^^^^^ diff --git a/macros/ui/task-double-shared.rs b/macros/ui/task-double-shared.rs deleted file mode 100644 index f9812d3..0000000 --- a/macros/ui/task-double-shared.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(shared = [A], shared = [B])] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-double-shared.stderr b/macros/ui/task-double-shared.stderr deleted file mode 100644 index bb90212..0000000 --- a/macros/ui/task-double-shared.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: argument appears more than once - --> ui/task-double-shared.rs:5:26 - | -5 | #[task(shared = [A], shared = [B])] - | ^^^^^^ diff --git a/macros/ui/task-idle.rs b/macros/ui/task-idle.rs deleted file mode 100644 index 353c782..0000000 --- a/macros/ui/task-idle.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[idle] - fn foo(_: foo::Context) -> ! { - loop {} - } - - // name collides with `#[idle]` function - #[task] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-idle.stderr b/macros/ui/task-idle.stderr deleted file mode 100644 index 4ccc113..0000000 --- a/macros/ui/task-idle.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this identifier has already been used - --> ui/task-idle.rs:12:14 - | -12 | async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-init.rs b/macros/ui/task-init.rs deleted file mode 100644 index e58fdce..0000000 --- a/macros/ui/task-init.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn foo(_: foo::Context) -> (Shared, Local) {} - - // name collides with `#[idle]` function - #[task] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-init.stderr b/macros/ui/task-init.stderr deleted file mode 100644 index 161e194..0000000 --- a/macros/ui/task-init.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this identifier has already been used - --> ui/task-init.rs:16:14 - | -16 | async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-interrupt.rs b/macros/ui/task-interrupt.rs deleted file mode 100644 index 3d50bd8..0000000 --- a/macros/ui/task-interrupt.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = SysTick)] - fn foo(_: foo::Context) {} - - #[task] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-interrupt.stderr b/macros/ui/task-interrupt.stderr deleted file mode 100644 index 087b6c6..0000000 --- a/macros/ui/task-interrupt.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task is defined multiple times - --> ui/task-interrupt.rs:9:14 - | -9 | async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-no-context.rs b/macros/ui/task-no-context.rs deleted file mode 100644 index 55e8c3b..0000000 --- a/macros/ui/task-no-context.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - async fn foo() {} -} diff --git a/macros/ui/task-no-context.stderr b/macros/ui/task-no-context.stderr deleted file mode 100644 index 62147aa..0000000 --- a/macros/ui/task-no-context.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-no-context.rs:6:14 - | -6 | async fn foo() {} - | ^^^ diff --git a/macros/ui/task-priority-too-high.rs b/macros/ui/task-priority-too-high.rs deleted file mode 100644 index f33ba56..0000000 --- a/macros/ui/task-priority-too-high.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(priority = 256)] - async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-priority-too-high.stderr b/macros/ui/task-priority-too-high.stderr deleted file mode 100644 index 5790c88..0000000 --- a/macros/ui/task-priority-too-high.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this literal must be in the range 0...255 - --> ui/task-priority-too-high.rs:5:23 - | -5 | #[task(priority = 256)] - | ^^^ diff --git a/macros/ui/task-priority-too-low.rs b/macros/ui/task-priority-too-low.rs deleted file mode 100644 index 16e0557..0000000 --- a/macros/ui/task-priority-too-low.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task(binds = UART0, priority = 0)] - fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-priority-too-low.stderr b/macros/ui/task-priority-too-low.stderr deleted file mode 100644 index 85c8660..0000000 --- a/macros/ui/task-priority-too-low.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: hardware tasks are not allowed to be at priority 0 - --> ui/task-priority-too-low.rs:5:38 - | -5 | #[task(binds = UART0, priority = 0)] - | ^ diff --git a/macros/ui/task-pub.rs b/macros/ui/task-pub.rs deleted file mode 100644 index 1ae533f..0000000 --- a/macros/ui/task-pub.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - pub async fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-pub.stderr b/macros/ui/task-pub.stderr deleted file mode 100644 index 7b9813d..0000000 --- a/macros/ui/task-pub.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-pub.rs:6:18 - | -6 | pub async fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-unsafe.rs b/macros/ui/task-unsafe.rs deleted file mode 100644 index a8383ef..0000000 --- a/macros/ui/task-unsafe.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[task] - async unsafe fn foo(_: foo::Context) {} -} diff --git a/macros/ui/task-unsafe.stderr b/macros/ui/task-unsafe.stderr deleted file mode 100644 index 90ac76f..0000000 --- a/macros/ui/task-unsafe.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-unsafe.rs:6:21 - | -6 | async unsafe fn foo(_: foo::Context) {} - | ^^^ diff --git a/macros/ui/task-zero-prio.rs b/macros/ui/task-zero-prio.rs deleted file mode 100644 index de3c86f..0000000 --- a/macros/ui/task-zero-prio.rs +++ /dev/null @@ -1,19 +0,0 @@ -#![no_main] - -#[rtic_macros::mock_app(device = mock)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local) {} - - #[task(priority = 0)] - fn foo(_: foo::Context) {} - - #[idle] - fn idle(_: idle::Context) -> ! {} -} diff --git a/macros/ui/task-zero-prio.stderr b/macros/ui/task-zero-prio.stderr deleted file mode 100644 index 1ab9aab..0000000 --- a/macros/ui/task-zero-prio.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: this task handler must have type signature `async fn(foo::Context, ..)` - --> ui/task-zero-prio.rs:15:8 - | -15 | fn foo(_: foo::Context) {} - | ^^^ diff --git a/rtic-monotonics/.gitignore b/rtic-monotonics/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic-monotonics/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic-monotonics/Cargo.toml b/rtic-monotonics/Cargo.toml new file mode 100644 index 0000000..24448fb --- /dev/null +++ b/rtic-monotonics/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rtic-timer" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cortex-m = { version = "0.7.6" } +embedded-hal-async = "0.2.0-alpha.0" +fugit = { version = "0.3.6", features = ["defmt"] } +rtic-timer = { version = "1.0.0", path = "../rtic-timer" } diff --git a/rtic-monotonics/rust-toolchain.toml b/rtic-monotonics/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic-monotonics/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic-monotonics/src/lib.rs b/rtic-monotonics/src/lib.rs new file mode 100644 index 0000000..88398ca --- /dev/null +++ b/rtic-monotonics/src/lib.rs @@ -0,0 +1,11 @@ +//! Crate + +#![no_std] +#![no_main] +#![deny(missing_docs)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + +pub use rtic_timer::{Monotonic, TimeoutError, TimerQueue}; + +pub mod systick_monotonic; diff --git a/rtic-monotonics/src/systick_monotonic.rs b/rtic-monotonics/src/systick_monotonic.rs new file mode 100644 index 0000000..491cf81 --- /dev/null +++ b/rtic-monotonics/src/systick_monotonic.rs @@ -0,0 +1 @@ +//! ... diff --git a/rtic-timer/.gitignore b/rtic-timer/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic-timer/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic-timer/Cargo.toml b/rtic-timer/Cargo.toml index 8e2e2ad..b7b3a5f 100644 --- a/rtic-timer/Cargo.toml +++ b/rtic-timer/Cargo.toml @@ -1,11 +1,10 @@ [package] name = "rtic-timer" -version = "0.1.0" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cortex-m = "0.7.6" -rtic-monotonic = "1.0.0" -fugit = "0.3.6" \ No newline at end of file +critical-section = "1" +futures-util = { version = "0.3.25", default-features = false } diff --git a/rtic-timer/rust-toolchain.toml b/rtic-timer/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic-timer/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic-timer/src/lib.rs b/rtic-timer/src/lib.rs index e7051d2..d7faa07 100644 --- a/rtic-timer/src/lib.rs +++ b/rtic-timer/src/lib.rs @@ -1,138 +1,336 @@ +//! Crate + #![no_std] +#![no_main] +#![deny(missing_docs)] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + +pub mod monotonic; + +use core::future::{poll_fn, Future}; +use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use core::task::{Poll, Waker}; +use futures_util::{ + future::{select, Either}, + pin_mut, +}; +pub use monotonic::Monotonic; -use core::sync::atomic::{AtomicU32, Ordering}; -use core::{cmp::Ordering, task::Waker}; -use cortex_m::peripheral::{syst::SystClkSource, SYST}; -pub use fugit::{self, ExtU64}; -pub use rtic_monotonic::Monotonic; +mod linked_list; -mod sll; -use sll::{IntrusiveSortedLinkedList, Min as IsslMin, Node as IntrusiveNode}; +use linked_list::{Link, LinkedList}; -pub struct Timer { - cnt: AtomicU32, - // queue: IntrusiveSortedLinkedList<'static, WakerNotReady, IsslMin>, +/// Holds a waker and at which time instant this waker shall be awoken. +struct WaitingWaker { + waker: Waker, + release_at: Mono::Instant, +} + +impl Clone for WaitingWaker { + fn clone(&self) -> Self { + Self { + waker: self.waker.clone(), + release_at: self.release_at, + } + } } -#[allow(non_snake_case)] -#[no_mangle] -fn SysTick() { - // .. - let cnt = unsafe { - static mut CNT: u32 = 0; - &mut CNT - }; +impl PartialEq for WaitingWaker { + fn eq(&self, other: &Self) -> bool { + self.release_at == other.release_at + } +} - *cnt = cnt.wrapping_add(1); +impl PartialOrd for WaitingWaker { + fn partial_cmp(&self, other: &Self) -> Option { + self.release_at.partial_cmp(&other.release_at) + } } -/// Systick implementing `rtic_monotonic::Monotonic` which runs at a -/// settable rate using the `TIMER_HZ` parameter. -pub struct Systick { - systick: SYST, - cnt: u64, +/// A generic timer queue for async executors. +/// +/// # Blocking +/// +/// The internal priority queue uses global critical sections to manage access. This means that +/// `await`ing a delay will cause a lock of the entire system for O(n) time. In practice the lock +/// duration is ~10 clock cycles per element in the queue. +/// +/// # Safety +/// +/// This timer queue is based on an intrusive linked list, and by extension the links are strored +/// on the async stacks of callers. The links are deallocated on `drop` or when the wait is +/// complete. +/// +/// Do not call `mem::forget` on an awaited future, or there will be dragons! +pub struct TimerQueue { + queue: LinkedList>, + initialized: AtomicBool, } -impl Systick { - /// Provide a new `Monotonic` based on SysTick. +/// This indicates that there was a timeout. +pub struct TimeoutError; + +impl TimerQueue { + /// Make a new queue. + pub const fn new() -> Self { + Self { + queue: LinkedList::new(), + initialized: AtomicBool::new(false), + } + } + + /// Forwards the `Monotonic::now()` method. + #[inline(always)] + pub fn now(&self) -> Mono::Instant { + Mono::now() + } + + /// Takes the initialized monotonic to initialize the TimerQueue. + pub fn initialize(&self, monotonic: Mono) { + self.initialized.store(true, Ordering::SeqCst); + + // Don't run drop on `Mono` + core::mem::forget(monotonic); + } + + /// Call this in the interrupt handler of the hardware timer supporting the `Monotonic` /// - /// The `sysclk` parameter is the speed at which SysTick runs at. This value should come from - /// the clock generation function of the used HAL. + /// # Safety /// - /// Notice that the actual rate of the timer is a best approximation based on the given - /// `sysclk` and `TIMER_HZ`. - pub fn new(mut systick: SYST, sysclk: u32) -> Self { - // + TIMER_HZ / 2 provides round to nearest instead of round to 0. - // - 1 as the counter range is inclusive [0, reload] - let reload = (sysclk + TIMER_HZ / 2) / TIMER_HZ - 1; + /// It's always safe to call, but it must only be called from the interrupt of the + /// monotonic timer for correct operation. + pub unsafe fn on_monotonic_interrupt(&self) { + Mono::clear_compare_flag(); + Mono::on_interrupt(); - assert!(reload <= 0x00ff_ffff); - assert!(reload > 0); + loop { + let mut release_at = None; + let head = self.queue.pop_if(|head| { + release_at = Some(head.release_at); - systick.disable_counter(); - systick.set_clock_source(SystClkSource::Core); - systick.set_reload(reload); + Mono::now() >= head.release_at + }); - Systick { systick, cnt: 0 } - } -} + match (head, release_at) { + (Some(link), _) => { + link.waker.wake(); + } + (None, Some(instant)) => { + Mono::enable_timer(); + Mono::set_compare(instant); -impl Monotonic for Systick { - const DISABLE_INTERRUPT_ON_EMPTY_QUEUE: bool = false; + if Mono::now() >= instant { + // The time for the next instant passed while handling it, + // continue dequeueing + continue; + } - type Instant = fugit::TimerInstantU64; - type Duration = fugit::TimerDurationU64; + break; + } + (None, None) => { + // Queue is empty + Mono::disable_timer(); - fn now(&mut self) -> Self::Instant { - if self.systick.has_wrapped() { - self.cnt = self.cnt.wrapping_add(1); + break; + } + } } - - Self::Instant::from_ticks(self.cnt) } - unsafe fn reset(&mut self) { - self.systick.clear_current(); - self.systick.enable_counter(); - } + /// Timeout at a specific time. + pub async fn timeout_at( + &self, + instant: Mono::Instant, + future: F, + ) -> Result { + let delay = self.delay_until(instant); - #[inline(always)] - fn set_compare(&mut self, _val: Self::Instant) { - // No need to do something here, we get interrupts anyway. + pin_mut!(future); + pin_mut!(delay); + + match select(future, delay).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } } - #[inline(always)] - fn clear_compare_flag(&mut self) { - // NOOP with SysTick interrupt + /// Timeout after a specific duration. + #[inline] + pub async fn timeout_after( + &self, + duration: Mono::Duration, + future: F, + ) -> Result { + self.timeout_at(Mono::now() + duration, future).await } - #[inline(always)] - fn zero() -> Self::Instant { - Self::Instant::from_ticks(0) + /// Delay for some duration of time. + #[inline] + pub async fn delay(&self, duration: Mono::Duration) { + let now = Mono::now(); + + self.delay_until(now + duration).await; } - #[inline(always)] - fn on_interrupt(&mut self) { - if self.systick.has_wrapped() { - self.cnt = self.cnt.wrapping_add(1); + /// Delay to some specific time instant. + pub async fn delay_until(&self, instant: Mono::Instant) { + if !self.initialized.load(Ordering::Relaxed) { + panic!( + "The timer queue is not initialized with a monotonic, you need to run `initialize`" + ); } + + let mut first_run = true; + let queue = &self.queue; + let mut link = Link::new(WaitingWaker { + waker: poll_fn(|cx| Poll::Ready(cx.waker().clone())).await, + release_at: instant, + }); + + let marker = &AtomicUsize::new(0); + + let dropper = OnDrop::new(|| { + queue.delete(marker.load(Ordering::Relaxed)); + }); + + poll_fn(|_| { + if Mono::now() >= instant { + return Poll::Ready(()); + } + + if first_run { + first_run = false; + let (was_empty, addr) = queue.insert(&mut link); + marker.store(addr, Ordering::Relaxed); + + if was_empty { + // Pend the monotonic handler if the queue was empty to setup the timer. + Mono::pend_interrupt(); + } + } + + Poll::Pending + }) + .await; + + // Make sure that our link is deleted from the list before we drop this stack + drop(dropper); } } -struct WakerNotReady -where - Mono: Monotonic, -{ - pub waker: Waker, - pub instant: Mono::Instant, - pub marker: u32, +struct OnDrop { + f: core::mem::MaybeUninit, } -impl Eq for WakerNotReady where Mono: Monotonic {} - -impl Ord for WakerNotReady -where - Mono: Monotonic, -{ - fn cmp(&self, other: &Self) -> Ordering { - self.instant.cmp(&other.instant) +impl OnDrop { + pub fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } } -} -impl PartialEq for WakerNotReady -where - Mono: Monotonic, -{ - fn eq(&self, other: &Self) -> bool { - self.instant == other.instant + #[allow(unused)] + pub fn defuse(self) { + core::mem::forget(self) } } -impl PartialOrd for WakerNotReady -where - Mono: Monotonic, -{ - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } } } + +// -------- Test program --------- +// +// +// use systick_monotonic::{Systick, TimerQueue}; +// +// // same panicking *behavior* as `panic-probe` but doesn't print a panic message +// // this prevents the panic message being printed *twice* when `defmt::panic` is invoked +// #[defmt::panic_handler] +// fn panic() -> ! { +// cortex_m::asm::udf() +// } +// +// /// Terminates the application and makes `probe-run` exit with exit-code = 0 +// pub fn exit() -> ! { +// loop { +// cortex_m::asm::bkpt(); +// } +// } +// +// defmt::timestamp!("{=u64:us}", { +// let time_us: fugit::MicrosDurationU32 = MONO.now().duration_since_epoch().convert(); +// +// time_us.ticks() as u64 +// }); +// +// make_systick_timer_queue!(MONO, Systick<1_000>); +// +// #[rtic::app( +// device = nrf52832_hal::pac, +// dispatchers = [SWI0_EGU0, SWI1_EGU1, SWI2_EGU2, SWI3_EGU3, SWI4_EGU4, SWI5_EGU5], +// )] +// mod app { +// use super::{Systick, MONO}; +// use fugit::ExtU32; +// +// #[shared] +// struct Shared {} +// +// #[local] +// struct Local {} +// +// #[init] +// fn init(cx: init::Context) -> (Shared, Local) { +// defmt::println!("init"); +// +// let systick = Systick::start(cx.core.SYST, 64_000_000); +// +// defmt::println!("initializing monotonic"); +// +// MONO.initialize(systick); +// +// async_task::spawn().ok(); +// async_task2::spawn().ok(); +// async_task3::spawn().ok(); +// +// (Shared {}, Local {}) +// } +// +// #[idle] +// fn idle(_: idle::Context) -> ! { +// defmt::println!("idle"); +// +// loop { +// core::hint::spin_loop(); +// } +// } +// +// #[task] +// async fn async_task(_: async_task::Context) { +// loop { +// defmt::println!("async task waiting for 1 second"); +// MONO.delay(1.secs()).await; +// } +// } +// +// #[task] +// async fn async_task2(_: async_task2::Context) { +// loop { +// defmt::println!(" async task 2 waiting for 0.5 second"); +// MONO.delay(500.millis()).await; +// } +// } +// +// #[task] +// async fn async_task3(_: async_task3::Context) { +// loop { +// defmt::println!(" async task 3 waiting for 0.2 second"); +// MONO.delay(200.millis()).await; +// } +// } +// } +// diff --git a/rtic-timer/src/linked_list.rs b/rtic-timer/src/linked_list.rs new file mode 100644 index 0000000..42ff8cb --- /dev/null +++ b/rtic-timer/src/linked_list.rs @@ -0,0 +1,173 @@ +//! ... + +use core::marker::PhantomPinned; +use core::sync::atomic::{AtomicPtr, Ordering}; +use critical_section as cs; + +/// A sorted linked list for the timer queue. +pub struct LinkedList { + head: AtomicPtr>, +} + +impl LinkedList { + /// Create a new linked list. + pub const fn new() -> Self { + Self { + head: AtomicPtr::new(core::ptr::null_mut()), + } + } +} + +impl LinkedList { + /// Pop the first element in the queue if the closure returns true. + pub fn pop_if bool>(&self, f: F) -> Option { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + if let Some(head) = unsafe { head.as_ref() } { + if f(&head.val) { + // Move head to the next element + self.head + .store(head.next.load(Ordering::Relaxed), Ordering::Relaxed); + + // We read the value at head + let head_val = head.val.clone(); + + return Some(head_val); + } + } + None + }) + } + + /// Delete a link at an address. + pub fn delete(&self, addr: usize) { + cs::with(|_| { + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { + head_ref + } else { + // 1. List is empty, do nothing + return; + }; + + if head as *const _ as usize == addr { + // 2. Replace head with head.next + self.head + .store(head_ref.next.load(Ordering::Relaxed), Ordering::Relaxed); + + return; + } + + // 3. search list for correct node + let mut curr = head_ref; + let mut next = head_ref.next.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(next_link) = unsafe { next.as_ref() } { + // Next is not null + + if next as *const _ as usize == addr { + curr.next + .store(next_link.next.load(Ordering::Relaxed), Ordering::Relaxed); + + return; + } + + // Continue searching + curr = next_link; + next = next_link.next.load(Ordering::Relaxed); + } + }) + } + + /// Insert a new link into the linked list. + /// The return is (was_empty, address), where the address of the link is for use with `delete`. + pub fn insert(&self, val: &mut Link) -> (bool, usize) { + cs::with(|_| { + let addr = val as *const _ as usize; + + // Make sure all previous writes are visible + core::sync::atomic::fence(Ordering::SeqCst); + + let head = self.head.load(Ordering::Relaxed); + + // 3 cases to handle + + // 1. List is empty, write to head + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + let head_ref = if let Some(head_ref) = unsafe { head.as_ref() } { + head_ref + } else { + self.head.store(val, Ordering::Relaxed); + return (true, addr); + }; + + // 2. val needs to go in first + if val.val < head_ref.val { + // Set current head as next of `val` + val.next.store(head, Ordering::Relaxed); + + // `val` is now first in the queue + self.head.store(val, Ordering::Relaxed); + + return (false, addr); + } + + // 3. search list for correct place + let mut curr = head_ref; + let mut next = head_ref.next.load(Ordering::Relaxed); + + // SAFETY: `as_ref` is safe as `insert` requires a valid reference to a link + while let Some(next_link) = unsafe { next.as_ref() } { + // Next is not null + + if val.val < next_link.val { + // Replace next with `val` + val.next.store(next, Ordering::Relaxed); + + // Insert `val` + curr.next.store(val, Ordering::Relaxed); + + return (false, addr); + } + + // Continue searching + curr = next_link; + next = next_link.next.load(Ordering::Relaxed); + } + + // No next, write link to last position in list + curr.next.store(val, Ordering::Relaxed); + + (false, addr) + }) + } +} + +/// A link in the linked list. +pub struct Link { + val: T, + next: AtomicPtr>, + _up: PhantomPinned, +} + +impl Link { + /// Create a new link. + pub const fn new(val: T) -> Self { + Self { + val, + next: AtomicPtr::new(core::ptr::null_mut()), + _up: PhantomPinned, + } + } +} diff --git a/rtic-timer/src/monotonic.rs b/rtic-timer/src/monotonic.rs new file mode 100644 index 0000000..9b3742f --- /dev/null +++ b/rtic-timer/src/monotonic.rs @@ -0,0 +1,60 @@ +//! ... + +/// # A monotonic clock / counter definition. +/// +/// ## Correctness +/// +/// The trait enforces that proper time-math is implemented between `Instant` and `Duration`. This +/// is a requirement on the time library that the user chooses to use. +pub trait Monotonic { + /// The time at time zero. + const ZERO: Self::Instant; + + /// The type for instant, defining an instant in time. + /// + /// **Note:** In all APIs in RTIC that use instants from this monotonic, this type will be used. + type Instant: Ord + + Copy + + core::ops::Add + + core::ops::Sub + + core::ops::Sub; + + /// The type for duration, defining an duration of time. + /// + /// **Note:** In all APIs in RTIC that use duration from this monotonic, this type will be used. + type Duration; + + /// Get the current time. + fn now() -> Self::Instant; + + /// Set the compare value of the timer interrupt. + /// + /// **Note:** This method does not need to handle race conditions of the monotonic, the timer + /// queue in RTIC checks this. + fn set_compare(instant: Self::Instant); + + /// Clear the compare interrupt flag. + fn clear_compare_flag(); + + /// Pend the timer's interrupt. + fn pend_interrupt(); + + /// Optional. Runs on interrupt before any timer queue handling. + fn on_interrupt() {} + + /// Optional. This is used to save power, this is called when the timer queue is not empty. + /// + /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant + /// based of `now()` is still valid. + /// + /// NOTE: This may be called more than once. + fn enable_timer() {} + + /// Optional. This is used to save power, this is called when the timer queue is empty. + /// + /// Enabling and disabling the monotonic needs to propagate to `now` so that an instant + /// based of `now()` is still valid. + /// + /// NOTE: This may be called more than once. + fn disable_timer() {} +} diff --git a/rtic-timer/src/sll.rs b/rtic-timer/src/sll.rs deleted file mode 100644 index 43b53c1..0000000 --- a/rtic-timer/src/sll.rs +++ /dev/null @@ -1,421 +0,0 @@ -//! An intrusive sorted priority linked list, designed for use in `Future`s in RTIC. -use core::cmp::Ordering; -use core::fmt; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; -use core::ptr::NonNull; - -/// Marker for Min sorted [`IntrusiveSortedLinkedList`]. -pub struct Min; - -/// Marker for Max sorted [`IntrusiveSortedLinkedList`]. -pub struct Max; - -/// The linked list kind: min-list or max-list -pub trait Kind: private::Sealed { - #[doc(hidden)] - fn ordering() -> Ordering; -} - -impl Kind for Min { - fn ordering() -> Ordering { - Ordering::Less - } -} - -impl Kind for Max { - fn ordering() -> Ordering { - Ordering::Greater - } -} - -/// Sealed traits -mod private { - pub trait Sealed {} -} - -impl private::Sealed for Max {} -impl private::Sealed for Min {} - -/// A node in the [`IntrusiveSortedLinkedList`]. -pub struct Node { - pub val: T, - next: Option>>, -} - -impl Node { - pub fn new(val: T) -> Self { - Self { val, next: None } - } -} - -/// The linked list. -pub struct IntrusiveSortedLinkedList<'a, T, K> { - head: Option>>, - _kind: PhantomData, - _lt: PhantomData<&'a ()>, -} - -impl<'a, T, K> fmt::Debug for IntrusiveSortedLinkedList<'a, T, K> -where - T: Ord + core::fmt::Debug, - K: Kind, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut l = f.debug_list(); - let mut current = self.head; - - while let Some(head) = current { - let head = unsafe { head.as_ref() }; - current = head.next; - - l.entry(&head.val); - } - - l.finish() - } -} - -impl<'a, T, K> IntrusiveSortedLinkedList<'a, T, K> -where - T: Ord, - K: Kind, -{ - pub const fn new() -> Self { - Self { - head: None, - _kind: PhantomData, - _lt: PhantomData, - } - } - - // Push to the list. - pub fn push(&mut self, new: &'a mut Node) { - unsafe { - if let Some(head) = self.head { - if head.as_ref().val.cmp(&new.val) != K::ordering() { - // This is newer than head, replace head - new.next = self.head; - self.head = Some(NonNull::new_unchecked(new)); - } else { - // It's not head, search the list for the correct placement - let mut current = head; - - while let Some(next) = current.as_ref().next { - if next.as_ref().val.cmp(&new.val) != K::ordering() { - break; - } - - current = next; - } - - new.next = current.as_ref().next; - current.as_mut().next = Some(NonNull::new_unchecked(new)); - } - } else { - // List is empty, place at head - self.head = Some(NonNull::new_unchecked(new)) - } - } - } - - /// Get an iterator over the sorted list. - pub fn iter(&self) -> Iter<'_, T, K> { - Iter { - _list: self, - index: self.head, - } - } - - /// Find an element in the list that can be changed and resorted. - pub fn find_mut(&mut self, mut f: F) -> Option> - where - F: FnMut(&T) -> bool, - { - let head = self.head?; - - // Special-case, first element - if f(&unsafe { head.as_ref() }.val) { - return Some(FindMut { - is_head: true, - prev_index: None, - index: self.head, - list: self, - maybe_changed: false, - }); - } - - let mut current = head; - - while let Some(next) = unsafe { current.as_ref() }.next { - if f(&unsafe { next.as_ref() }.val) { - return Some(FindMut { - is_head: false, - prev_index: Some(current), - index: Some(next), - list: self, - maybe_changed: false, - }); - } - - current = next; - } - - None - } - - /// Peek at the first element. - pub fn peek(&self) -> Option<&T> { - self.head.map(|head| unsafe { &head.as_ref().val }) - } - - /// Pops the first element in the list. - /// - /// Complexity is worst-case `O(1)`. - pub fn pop(&mut self) -> Option<&'a Node> { - if let Some(head) = self.head { - let v = unsafe { head.as_ref() }; - self.head = v.next; - Some(v) - } else { - None - } - } - - /// Checks if the linked list is empty. - #[inline] - pub fn is_empty(&self) -> bool { - self.head.is_none() - } -} - -/// Iterator for the linked list. -pub struct Iter<'a, T, K> -where - T: Ord, - K: Kind, -{ - _list: &'a IntrusiveSortedLinkedList<'a, T, K>, - index: Option>>, -} - -impl<'a, T, K> Iterator for Iter<'a, T, K> -where - T: Ord, - K: Kind, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - let index = self.index?; - - let node = unsafe { index.as_ref() }; - self.index = node.next; - - Some(&node.val) - } -} - -/// Comes from [`IntrusiveSortedLinkedList::find_mut`]. -pub struct FindMut<'a, 'b, T, K> -where - T: Ord + 'b, - K: Kind, -{ - list: &'a mut IntrusiveSortedLinkedList<'b, T, K>, - is_head: bool, - prev_index: Option>>, - index: Option>>, - maybe_changed: bool, -} - -impl<'a, 'b, T, K> FindMut<'a, 'b, T, K> -where - T: Ord, - K: Kind, -{ - unsafe fn pop_internal(&mut self) -> &'b mut Node { - if self.is_head { - // If it is the head element, we can do a normal pop - let mut head = self.list.head.unwrap_unchecked(); - let v = head.as_mut(); - self.list.head = v.next; - v - } else { - // Somewhere in the list - let mut prev = self.prev_index.unwrap_unchecked(); - let mut curr = self.index.unwrap_unchecked(); - - // Re-point the previous index - prev.as_mut().next = curr.as_ref().next; - - curr.as_mut() - } - } - - /// This will pop the element from the list. - /// - /// Complexity is worst-case `O(1)`. - #[inline] - pub fn pop(mut self) -> &'b mut Node { - unsafe { self.pop_internal() } - } - - /// This will resort the element into the correct position in the list if needed. The resorting - /// will only happen if the element has been accessed mutably. - /// - /// Same as calling `drop`. - /// - /// Complexity is worst-case `O(N)`. - #[inline] - pub fn finish(self) { - drop(self) - } -} - -impl<'b, T, K> Drop for FindMut<'_, 'b, T, K> -where - T: Ord + 'b, - K: Kind, -{ - fn drop(&mut self) { - // Only resort the list if the element has changed - if self.maybe_changed { - unsafe { - let val = self.pop_internal(); - self.list.push(val); - } - } - } -} - -impl Deref for FindMut<'_, '_, T, K> -where - T: Ord, - K: Kind, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { &self.index.unwrap_unchecked().as_ref().val } - } -} - -impl DerefMut for FindMut<'_, '_, T, K> -where - T: Ord, - K: Kind, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - self.maybe_changed = true; - unsafe { &mut self.index.unwrap_unchecked().as_mut().val } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn const_new() { - static mut _V1: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - } - - #[test] - fn test_peek() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &2); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &3); - - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &2); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - assert_eq!(ll.peek().unwrap(), &1); - } - - #[test] - fn test_empty() { - let ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - assert!(ll.is_empty()) - } - - #[test] - fn test_updating() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let mut a = Node { val: 2, next: None }; - ll.push(&mut a); - - let mut a = Node { val: 3, next: None }; - ll.push(&mut a); - - let mut find = ll.find_mut(|v| *v == 2).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1002); - - let mut find = ll.find_mut(|v| *v == 3).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1003); - - // Remove largest element - ll.find_mut(|v| *v == 1003).unwrap().pop(); - - assert_eq!(ll.peek().unwrap(), &1002); - } - - #[test] - fn test_updating_1() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let v = ll.pop().unwrap(); - - assert_eq!(v.val, 1); - } - - #[test] - fn test_updating_2() { - let mut ll: IntrusiveSortedLinkedList = IntrusiveSortedLinkedList::new(); - - let mut a = Node { val: 1, next: None }; - ll.push(&mut a); - - let mut find = ll.find_mut(|v| *v == 1).unwrap(); - - *find += 1000; - find.finish(); - - assert_eq!(ll.peek().unwrap(), &1001); - } -} diff --git a/rtic/.cargo/config.toml b/rtic/.cargo/config.toml new file mode 100644 index 0000000..d70faef --- /dev/null +++ b/rtic/.cargo/config.toml @@ -0,0 +1,13 @@ +[alias] +xtask = "run --package xtask --" + +[target.thumbv6m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.thumbv7m-none-eabi] +runner = "qemu-system-arm -cpu cortex-m3 -machine lm3s6965evb -nographic -semihosting-config enable=on,target=native -kernel" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +rustflags = [ + "-C", "link-arg=-Tlink.x", +] \ No newline at end of file diff --git a/rtic/.gitignore b/rtic/.gitignore new file mode 100644 index 0000000..c400256 --- /dev/null +++ b/rtic/.gitignore @@ -0,0 +1,6 @@ +**/*.rs.bk +.#* +.gdb_history +/target +Cargo.lock +*.hex diff --git a/rtic/CHANGELOG.md b/rtic/CHANGELOG.md new file mode 100644 index 0000000..d172278 --- /dev/null +++ b/rtic/CHANGELOG.md @@ -0,0 +1,603 @@ +# Change Log + +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +For each category, *Added*, *Changed*, *Fixed* add new entries at the top! + +## [Unreleased] + +### Added + +### Fixed + +### Changed + +## [v1.1.4] - 2023-02-26 + +### Added + +- CFG: Support #[cfg] on HW task, cleanup for SW tasks +- CFG: Slightly improved support for #[cfg] on Monotonics +- CI: Check examples also for thumbv8.{base,main} +- Allow custom `link_section` attributes for late resources + +### Fixed + +- Attempt to handle docs generation enabling `deny(missing_docs)` +- Book: Editorial review +- Use native GHA rustup and cargo +- Distinguish between thumbv8m.base and thumbv8m.main for basepri usage. + +### Changed + +- Updated dev-dependency cortex-m-semihosting to v0.5 +- CI: Updated to setup-python@v4 +- CI: Updated to checkout@v3 +- Tuned redirect message for rtic.rs/meeting + +## [v1.1.3] - 2022-06-23 + +### Added + +### Fixed + +- Bump cortex-m-rtic-macros to 1.1.5 +- fix ci: use SYST::PTR + +#### cortex-m-rtic-macros v1.1.5 - 2022-06-23 + +- Bump rtic-syntax to 1.0.2 + +#### cortex-m-rtic-macros v1.1.4 - 2022-05-24 + +- Fix macros to Rust 2021 + +#### cortex-m-rtic-macros v1.1.3 - 2022-05-24 + +- Fix clash with defmt + +### Changed + +## [v1.1.2] - 2022-05-09 + +### Added + +### Fixed + +- Generation of masks for the source masking scheduling for thumbv6 + +### Changed + +## [v1.1.1] - 2022-04-13 - YANKED + +### Added + +### Fixed + +- Fixed `marcro` version + +### Changed + +## [v1.1.0] - 2022-04-13 - YANKED + +### Added + +- Improve how CHANGELOG.md merges are handled +- If current $stable and master version matches, dev-book redirects to $stable book +- During deploy stage, merge master branch into current stable IFF cargo package version matches +- Rework branch structure, release/vVERSION +- Cargo clippy in CI +- Use rust-cache Github Action +- Support for NVIC based SPR based scheduling for armv6m. +- CI changelog entry enforcer +- `examples/periodic-at.rs`, an example of a periodic timer without accumulated drift. +- `examples/periodic-at2.rs`, an example of a periodic process with two tasks, with offset timing. + Here we depict two alternative usages of the timer type, explicit and trait based. +- book: Update `Monotonic` tips. + +### Fixed + +- Re-export `rtic_core::prelude` as `rtic::mutex::prelude` to allow glob imports + Clippy +- Fix all except `must_use` lints from clippy::pedantic +- Fix dated migration docs for spawn +- Remove obsolete action-rs tool-cache +- Force mdBook to return error codes +- Readded missing ramfunc output to book + +### Changed + +- Try to detect `target-dir` for rtic-expansion.rs + +## [v1.0.0] - 2021-12-25 + +### Changed + +- Bump RTIC dependencies also updated to v1.0.0 +- Edition 2021 +- Change default `idle` behaviour to be `NOP` instead of `WFI` + +## [v0.6.0-rc.4] - 2021-11-09 + +- Updated to use the new generic `Monotonic` trait + +## [v0.6.0-rc.3] - 2021-11-08 + +### Fixed + +- Match rtic-syntax Analysis-struct updates from https://github.com/rtic-rs/rtic-syntax/pull/61 + +## [v0.6.0-rc.2] - 2021-09-28 + +- Fixed issue with `cortex_m` being used by the codegen instead of using the `rtic::export::...` which could make an app not compile if Systick is used and the user did not have the cortex-m crate as a dependency + +## [v0.6.0-rc.1] - 2021-09-27 + +- Documentation updates +- Monotonic handlers default to maximum priority instead of minimum (to follow RTIC 0.5) +- Better support for `rust-analyzer` + +## [v0.5.9] - 2021-09-27 + +- Removed the `cortex-m-rt` dependency +- Docs updates + +## [v0.5.8] - 2021-08-19 + +- Feature flag was added to support `cortex-m v0.7.x` +- MSRV raised to 1.38. + +## [v0.6.0-alpha.5] - 2021-07-09 + +### Changed + +- The new resources syntax is implemented. + +## [v0.5.7] - 2021-07-05 + +- Backport: "you must enable the rt feature" compile time detection + +## [v0.6.0-alpha.4] - 2021-05-27 + +### Fixed + +- Fixed codegen structure to not have issues with local paths +- Default paths for monotonics now work properly +- New `embedded-time` version to `0.11` + +## [v0.6.0-alpha.3] - 2021-0X-XX + +- Lost in the ether... + +## [v0.6.0-alpha.2] - 2021-04-08 + +### Added + +- Cancel and reschedule support to the monotonics + +### Fixed + +- UB in `spawn_at` +- `#[cfg]` and other attributes now work on hardware tasks +- Type aliases now work in `mod app` + +### Changed + +- The access to monotonic static methods was for example `MyMono::now()`, and is now `monotonics::MyMono::now()` + +## [v0.6.0-alpha.1] - 2021-03-04 + +### Added + +- Support for multi-locks, see `examples/multilock.rs` for syntax. +- New monotonic syntax and support, see `#[monotonic]` + +## [v0.5.6] - 2021-03-03 + +- **Security** Use latest security patched heapless + +## [v0.6.0-alpha.0] - 2020-11-14 + +### Added + +- Allow annotating resources to activate special resource locking behaviour. + - `#[lock_free]`, there might be several tasks with the same priority accessing + the resource without critical section. + - `#[task_local]`, there must be only one task, similar to a task local + resource, but (optionally) set-up by init. This is similar to move. + +- Improved ergonomics allowing separation of task signatures to actual implementation in extern block `extern "Rust" { #[task(..)] fn t(..); }`. + +### Changed + +- [breaking-change] [PR 400] Move dispatchers from extern block to app argument. + +[PR 400]: https://github.com/rtic-rs/cortex-m-rtic/pull/400 + +- [breaking-change] [PR 399] Locking resources are now always required to achieve a symmetric UI. + +[PR 399]: https://github.com/rtic-rs/cortex-m-rtic/pull/399 + +- [breaking-change] [PR 390] Rework whole spawn/schedule, support `foo::spawn( ... )`, + `foo::schedule( ... )`. + +[PR 390]: https://github.com/rtic-rs/cortex-m-rtic/pull/390 + +- [breaking-change] [PR 368] `struct Resources` changed to attribute `#[resources]` on a struct. + +- [breaking-change] [PR 368] Mod over const, instead of `const APP: () = {` use `mod app {`. + +- [breaking-change] [PR 372] Init function always return `LateResources` for a symmetric API. + +- [PR 355] Multi-core support was removed to reduce overall complexity. + +[PR 368]: https://github.com/rtic-rs/cortex-m-rtic/pull/368 +[PR 372]: https://github.com/rtic-rs/cortex-m-rtic/pull/372 +[PR 355]: https://github.com/rtic-rs/cortex-m-rtic/pull/355 + +## [v0.5.5] - 2020-08-27 + +- Includes the previous soundness fix. +- Fixes wrong use of the `cortex_m` crate which can cause some projects to stop compiling. + +## [v0.5.4] - 2020-08-26 - YANKED + +- **Soundness fix in RTIC**, it was previously possible to get the `cortex_m::Peripherals` more than once, causing UB. + +## [v0.5.3] - 2020-06-12 + +- Added migration guide from `cortex-m-rtfm` to `cortex-m-rtic` +- No code changes, only a version compatibility release with `cortex-m-rtfm` to ease the transition +for users. + +## [v0.5.2] - 2020-06-11 + +- Using safe `DWT` interface +- Using GitHub Actions now +- Improved CI speed +- Now `main` can be used as function name +- Fixed so one can `cfg`-out resources when using a newer compiler + +## [v0.5.1] - 2019-11-19 + +- Fixed arithmetic wrapping bug in src/cyccntr.rs + elapsed and duration could cause an internal overflow trap + on subtraction in debug mode. + +- Fixed bug in SysTick implementation where the SysTick could be disabled by + accident + +## [v0.5.0] - 2019-11-14 + +### Added + +- Experimental support for homogeneous and heterogeneous multi-core + microcontrollers has been added. Support is gated behind the `homogeneous` and + `heterogeneous` Cargo features. + +### Changed + +- [breaking-change] [RFC 155] "explicit `Context` parameter" has been + implemented. + +[RFC 155]: https://github.com/rtic-rs/cortex-m-rtic/issues/155 + +- [breaking-change] [RFC 147] "all functions must be safe" has been + implemented. + +[RFC 147]: https://github.com/rtic-rs/cortex-m-rtic/issues/147 + +- All the queues internally used by the framework now use `AtomicU8` indices + instead of `AtomicUsize`; this reduces the static memory used by the + framework. + +- [breaking-change] when the `capacity` argument is omitted, the capacity of + the task is assumed to be `1`. Before, a reasonable (but hard to predict) + capacity was computed based on the number of `spawn` references the task had. + +- [breaking-change] resources that are appear as exclusive references + (`&mut-`) no longer appear behind the `Exclusive` newtype. + +- [breaking-change] the `timer-queue` Cargo feature has been removed. The + `schedule` API can be used without enabling any Cargo feature. + +- [breaking-change] when the `schedule` API is used the type of + `init::Context.core` changes from `cortex_m::Peripherals` to + `rtic::Peripherals`. The fields of `rtic::Peripherals` do not change when + Cargo features are enabled. + +- [breaking-change] the monotonic timer used to implement the `schedule` API + is now user configurable via the `#[app(monotonic = ..)]` argument. IMPORTANT: + it is now the responsibility of the application author to configure and + initialize the chosen `monotonic` timer during the `#[init]` phase. + +- [breaking-change] the `peripherals` field is not include in `init::Context` + by default. One must opt-in using the `#[app(peripherals = ..)]` argument. + +- [breaking-change] the `#[exception]` and `#[interrupt]` attributes have been + removed. Hardware tasks are now declared using the `#[task(binds = ..)]` + attribute. + +- [breaking-change] the syntax to declare resources has changed. Instead of + using a `static [mut]` variable for each resource, all resources must be + declared in a `Resources` structure. + +### Removed + +- [breaking-change] the integration with the `owned_singleton` crate has been + removed. You can use `heapless::Pool` instead of `alloc_singleton`. + +- [breaking-change] late resources can no longer be initialized using the assign + syntax. `init::LateResources` is the only method to initialize late resources. + See [PR #140] for more details. + +[PR #140]: https://github.com/rtic-rs/cortex-m-rtic/pull/140 + +## [v0.4.3] - 2019-04-21 + +### Changed + +- Checking that the specified priorities are supported by the target device is + now done at compile time. + +### Fixed + +- Building this crate with the "nightly" feature and a recent compiler has been + fixed. + +## [v0.4.2] - 2019-02-27 + +### Added + +- `Duration` now has an `as_cycles` method to get the number of clock cycles + contained in it. + +- An opt-in "nightly" feature that reduces static memory usage, shortens + initialization time and reduces runtime overhead has been added. To use this + feature you need a nightly compiler! + +- [RFC 128] has been implemented. The `exception` and `interrupt` have gained a + `binds` argument that lets you give the handler an arbitrary name. For + example: + +[RFC 128]: https://github.com/rtic-rs/cortex-m-rtic/issues/128 + +``` rust +// on v0.4.1 you had to write +#[interrupt] +fn USART0() { .. } + +// on v0.4.2 you can write +#[interrupt(binds = USART0)] +fn on_new_frame() { .. } +``` + +### Changed + +- Builds are now reproducible. `cargo build; cargo clean; cargo build` will + produce binaries that are exactly the same (after `objcopy -O ihex`). This + wasn't the case before because we used randomly generated identifiers for + memory safety but now all the randomness is gone. + +### Fixed + +- Fixed a `non_camel_case_types` warning that showed up when using a recent + nightly. + +- Fixed a bug that allowed you to enter the `capacity` and `priority` arguments + in the `task` attribute more than once. Now all arguments can only be stated + once in the list, as it should be. + +## [v0.4.1] - 2019-02-12 + +### Added + +- The RTIC book has been translated to Russian. You can find the translation + online at https://japaric.github.io/cortex-m-rtic/book/ru/ + +- `Duration` now implements the `Default` trait. + +### Changed + +- [breaking-change] [soundness-fix] `init` can not contain any early return as + that would result in late resources not being initialized and thus undefined + behavior. + +- Use an absolute link to the book so it works when landing from crates.io + documentation page + +- The initialization function can now be written as `fn init() -> + init::LateResources` when late resources are used. This is preferred over the + old `fn init()` form. See the section on late resources (resources chapter) in + the book for more details. + +### Fixed + +- `#[interrupt]` and `#[exception]` no longer produce warnings on recent nightlies. + +## [v0.4.0] - 2018-11-03 - YANKED + +Yanked due to a soundness issue in `init`; the issue has been mostly fixed in v0.4.1. + +### Changed + +- This crate now compiles on stable 1.31. + +- [breaking-change] The `app!` macro has been transformed into an attribute. See + the documentation for details. + +- [breaking-change] Applications that use this library must be written using the + 2018 edition. + +- [breaking-change] The `Resource` trait has been renamed to `Mutex`. + `Resource.claim_mut` has been renamed to `Mutex.lock` and its signature has + changed (no `Threshold` token is required). + +- [breaking-change] The name of the library has changed to `rtic`. The package + name is still `cortex-m-rtic`. + +- [breaking-change] `cortex_m_rtic::set_pending` has been renamed to + `rtic::pend`. + +### Added + +- Software tasks, which can be immediately spawn and scheduled to run in the + future. + +- `Instant` and `Duration` API. + +- Integration with the [`Singleton`] abstraction. + +[`Singleton`]: https://docs.rs/owned-singleton/0.1.0/owned_singleton/ + +### Removed + +- [breaking-change] The `Threshold` token has been removed. + +- [breaking-change] The `bkpt` and `wfi` re-exports have been removed. + +- [breaking-change] `rtic::atomic` has been removed. + +## [v0.3.4] - 2018-08-27 + +### Changed + +- The documentation link to point to GH pages. + +## [v0.3.3] - 2018-08-24 + +### Fixed + +- Compilation with latest nightly + +## [v0.3.2] - 2018-04-16 + +### Added + +- Span information to error messages + +### Changed + +- Some non fatal error messages have become warning messages. For example, specifying an empty list + of resources now produces a warning instead of a hard error. + +## [v0.3.1] - 2018-01-16 + +### Fixed + +- Documentation link + +## [v0.3.0] - 2018-01-15 + +### Added + +- [feat] `&'static mut` references can be safely created by assigning resources to `init`. See the + `init.resources` section of the `app!` macro documentation and the `safe-static-mut-ref` example + for details. + +### Changed + +- [breaking-change] svd2rust dependency has been bumped to v0.12.0 + +- [breaking-change] resources assigned to tasks, or to idle, that were not declared in the top + `resources` field generate compiler errors. Before these were assumed to be peripherals, that's no + longer the case. + +- [breaking-change] the layout of `init::Peripherals` has changed. This struct now has two fields: + `core` and `device`. The value of the `core` field is a struct that owns all the core peripherals + of the device and the value of the `device` field is a struct that owns all the device specific + peripherals of the device. + +## [v0.2.2] - 2017-11-22 + +### Added + +- Support for runtime initialized resources ("late" resources). + +## [v0.2.1] - 2017-07-29 + +### Fixed + +- Link to `app!` macro documentation. + +## [v0.2.0] - 2017-07-29 + +### Added + +- The `app!` macro, a macro to declare the tasks and resources of an + application. + +- The `Resource` trait, which is used to write generic code that deals with + resources. + +- Support for system handlers like SYS_TICK. + +### Changed + +- [breaking-change] The signature of the `atomic` function has changed. + +- [breaking-change] The threshold token has become a concrete type and lost its + `raise` method. + +### Removed + +- [breaking-change] The `tasks!` and `peripherals!` macros. + +- [breaking-change] The ceiling and priority tokens. + +- [breaking-change] The `Local`, `Resource` and `Peripheral` structs. + +- [breaking-change] The traits related to type level integers. + +## [v0.1.1] - 2017-06-05 + +### Changed + +- `peripherals!`: The `register_block` field is now optional + +## v0.1.0 - 2017-05-09 + +- Initial release + +[Unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.4...HEAD +[v1.1.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.3...v1.1.4 +[v1.1.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.2...v1.1.3 +[v1.1.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.1...v1.1.2 +[v1.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.1.0...v1.1.1 +[v1.1.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.4...v1.0.0 +[v0.6.0-rc.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.3...v0.6.0-rc.4 +[v0.6.0-rc.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.2...v0.6.0-rc.3 +[v0.6.0-rc.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.1...v0.6.0-rc.2 +[v0.6.0-rc.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-rc.0...v0.6.0-rc.1 +[v0.6.0-rc.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.5...v0.6.0-rc.0 +[v0.6.0-alpha.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.4...v0.6.0-alpha.5 +[v0.6.0-alpha.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.3...v0.6.0-alpha.4 +[v0.6.0-alpha.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.2...v0.6.0-alpha.3 +[v0.6.0-alpha.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.1...v0.6.0-alpha.2 +[v0.6.0-alpha.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.6.0-alpha.0...v0.6.0-alpha.1 +[v0.6.0-alpha.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.6.0-alpha.0 +[v0.5.x unreleased]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.x +[v0.5.9]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.8...v0.5.9 +[v0.5.8]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.7...v0.5.8 +[v0.5.7]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.6...v0.5.7 +[v0.5.6]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.5...v0.5.6 +[v0.5.5]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.4...v0.5.5 +[v0.5.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.3...v0.5.4 +[v0.5.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.2...v0.5.3 +[v0.5.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.1...v0.5.2 +[v0.5.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.5.0...v0.5.1 +[v0.5.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.3...v0.5.0 +[v0.4.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.2...v0.4.3 +[v0.4.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.1...v0.4.2 +[v0.4.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.4...v0.4.0 +[v0.3.4]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.3...v0.3.4 +[v0.3.3]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.2...v0.3.3 +[v0.3.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.1...v0.3.2 +[v0.3.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.3.0...v0.3.1 +[v0.3.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.2...v0.3.0 +[v0.2.2]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.1...v0.2.2 +[v0.2.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.2.0...v0.2.1 +[v0.2.0]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/rtic-rs/cortex-m-rtic/compare/v0.1.0...v0.1.1 diff --git a/rtic/Cargo.toml b/rtic/Cargo.toml new file mode 100644 index 0000000..c22d023 --- /dev/null +++ b/rtic/Cargo.toml @@ -0,0 +1,80 @@ +[package] +authors = [ + "The Real-Time Interrupt-driven Concurrency developers", + "Emil Fresk ", + "Henrik Tjäder ", + "Jorge Aparicio ", + "Per Lindgren ", +] +categories = ["concurrency", "embedded", "no-std", "asynchronous"] +description = "Real-Time Interrupt-driven Concurrency (RTIC): a concurrency framework for building real-time systems" +documentation = "https://rtic.rs/" +edition = "2021" +keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] +license = "MIT OR Apache-2.0" +name = "rtic" +readme = "README.md" +repository = "https://github.com/rtic-rs/rtic" + +version = "2.0.0-alpha.0" + +[lib] +name = "rtic" + +[dependencies] +cortex-m = "0.7.0" +rtic-macros = { path = "macros", version = "2.0.0-alpha.0" } +rtic-monotonic = "1.0.0" +rtic-core = "1.0.0" +heapless = "0.7.7" +bare-metal = "1.0.0" +#portable-atomic = { version = "0.3.19" } +atomic-polyfill = "1" + +[build-dependencies] +version_check = "0.9" + +[dev-dependencies] +lm3s6965 = "0.1.3" +cortex-m-semihosting = "0.5.0" +systick-monotonic = "1.0.0" + +[dev-dependencies.panic-semihosting] +features = ["exit"] +version = "0.6.0" + +[target.x86_64-unknown-linux-gnu.dev-dependencies] +trybuild = "1" + +[profile.release] +codegen-units = 1 +lto = true + +[workspace] +members = ["macros", "xtask", "rtic-timer"] + +# do not optimize proc-macro deps or build scripts +[profile.dev.build-override] +codegen-units = 16 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + + +[profile.release.build-override] +codegen-units = 16 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[patch.crates-io] +lm3s6965 = { git = "https://github.com/japaric/lm3s6965" } + +[features] +test-critical-section = ["cortex-m/critical-section-single-core"] + +# [[example]] +# name = "pool" +# required-features = ["test-critical-section"] diff --git a/rtic/build.rs b/rtic/build.rs new file mode 100644 index 0000000..35f8303 --- /dev/null +++ b/rtic/build.rs @@ -0,0 +1,25 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + + if version_check::Channel::read().unwrap().is_nightly() { + println!("cargo:rustc-cfg=rustc_is_nightly"); + } + + // These targets all have know support for the BASEPRI register. + if target.starts_with("thumbv7m") + | target.starts_with("thumbv7em") + | target.starts_with("thumbv8m.main") + { + println!("cargo:rustc-cfg=have_basepri"); + + // These targets are all known to _not_ have the BASEPRI register. + } else if target.starts_with("thumb") + && !(target.starts_with("thumbv6m") | target.starts_with("thumbv8m.base")) + { + panic!("Unknown target '{target}'. Need to update BASEPRI logic in build.rs."); + } + + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/rtic/ci/expected/async-delay.run b/rtic/ci/expected/async-delay.run new file mode 100644 index 0000000..61852ab --- /dev/null +++ b/rtic/ci/expected/async-delay.run @@ -0,0 +1,7 @@ +init +hello from bar +hello from baz +hello from foo +bye from foo +bye from bar +bye from baz diff --git a/rtic/ci/expected/async-infinite-loop.run b/rtic/ci/expected/async-infinite-loop.run new file mode 100644 index 0000000..f9fd4e4 --- /dev/null +++ b/rtic/ci/expected/async-infinite-loop.run @@ -0,0 +1,6 @@ +init +hello from async 0 +hello from async 1 +hello from async 2 +hello from async 3 +hello from async 4 diff --git a/rtic/ci/expected/async-task-multiple-prios.run b/rtic/ci/expected/async-task-multiple-prios.run new file mode 100644 index 0000000..0b42df0 --- /dev/null +++ b/rtic/ci/expected/async-task-multiple-prios.run @@ -0,0 +1,6 @@ +init +hello from async 3 a 1 +hello from async 4 a 2 +hello from async 1 a 3 +hello from async 2 a 4 +idle diff --git a/rtic/ci/expected/async-task.run b/rtic/ci/expected/async-task.run new file mode 100644 index 0000000..1f93a4c --- /dev/null +++ b/rtic/ci/expected/async-task.run @@ -0,0 +1,5 @@ +init +hello from async2 +hello from async +hello from async with args a: 1, b: 2 +idle diff --git a/rtic/ci/expected/async-timeout.run b/rtic/ci/expected/async-timeout.run new file mode 100644 index 0000000..a807423 --- /dev/null +++ b/rtic/ci/expected/async-timeout.run @@ -0,0 +1,5 @@ +init +hello from bar +hello from foo +foo no timeout +bar timeout diff --git a/rtic/ci/expected/big-struct-opt.run b/rtic/ci/expected/big-struct-opt.run new file mode 100644 index 0000000..7fdef35 --- /dev/null +++ b/rtic/ci/expected/big-struct-opt.run @@ -0,0 +1,3 @@ +async_task data:[22, 22, 22, 22, 22] +uart0 data:[22, 22, 22, 22, 22] +idle diff --git a/rtic/ci/expected/binds.run b/rtic/ci/expected/binds.run new file mode 100644 index 0000000..f84cff0 --- /dev/null +++ b/rtic/ci/expected/binds.run @@ -0,0 +1,4 @@ +init +foo called 1 time +idle +foo called 2 times diff --git a/rtic/ci/expected/cancel-reschedule.run b/rtic/ci/expected/cancel-reschedule.run new file mode 100644 index 0000000..5a94752 --- /dev/null +++ b/rtic/ci/expected/cancel-reschedule.run @@ -0,0 +1,3 @@ +init +foo +bar diff --git a/rtic/ci/expected/capacity.run b/rtic/ci/expected/capacity.run new file mode 100644 index 0000000..f96815d --- /dev/null +++ b/rtic/ci/expected/capacity.run @@ -0,0 +1,5 @@ +foo(0) +foo(1) +foo(2) +foo(3) +bar diff --git a/rtic/ci/expected/cfg-whole-task.run b/rtic/ci/expected/cfg-whole-task.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/common.run b/rtic/ci/expected/common.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/complex.run b/rtic/ci/expected/complex.run new file mode 100644 index 0000000..5df884d --- /dev/null +++ b/rtic/ci/expected/complex.run @@ -0,0 +1,47 @@ +init +idle p0 started +t2 p4 called 1 time +enter lock s4 0 +t3 p4 exit +idle enter lock s3 0 +idle pend t0 +idle pend t1 +idle pend t2 +t2 p4 called 2 times +enter lock s4 1 +t3 p4 exit +idle still in lock s3 0 +t1 p3 called 1 time +t1 enter lock s4 2 +t1 pend t0 +t1 pend t2 +t1 still in lock s4 2 +t2 p4 called 3 times +enter lock s4 2 +t3 p4 exit +t1 p3 exit +t0 p2 called 1 time +t0 p2 exit + +back in idle +enter lock s2 0 +idle pend t0 +idle pend t1 +t1 p3 called 2 times +t1 enter lock s4 3 +t1 pend t0 +t1 pend t2 +t1 still in lock s4 3 +t2 p4 called 4 times +enter lock s4 3 +t3 p4 exit +t1 p3 exit +idle pend t2 +t2 p4 called 5 times +enter lock s4 4 +t3 p4 exit +idle still in lock s2 0 +t0 p2 called 2 times +t0 p2 exit + +idle exit diff --git a/rtic/ci/expected/declared_locals.run b/rtic/ci/expected/declared_locals.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/destructure.run b/rtic/ci/expected/destructure.run new file mode 100644 index 0000000..25a4b1b --- /dev/null +++ b/rtic/ci/expected/destructure.run @@ -0,0 +1,2 @@ +bar: a = 0, b = 1, c = 2 +foo: a = 0, b = 1, c = 2 diff --git a/rtic/ci/expected/extern_binds.run b/rtic/ci/expected/extern_binds.run new file mode 100644 index 0000000..9d925d5 --- /dev/null +++ b/rtic/ci/expected/extern_binds.run @@ -0,0 +1,4 @@ +init +foo called +idle +foo called diff --git a/rtic/ci/expected/extern_spawn.run b/rtic/ci/expected/extern_spawn.run new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/rtic/ci/expected/extern_spawn.run @@ -0,0 +1 @@ +foo diff --git a/rtic/ci/expected/generics.run b/rtic/ci/expected/generics.run new file mode 100644 index 0000000..fb31731 --- /dev/null +++ b/rtic/ci/expected/generics.run @@ -0,0 +1,6 @@ +UART1(STATE = 0) +shared: 0 -> 1 +UART0(STATE = 0) +shared: 1 -> 2 +UART1(STATE = 1) +shared: 2 -> 4 diff --git a/rtic/ci/expected/hardware.run b/rtic/ci/expected/hardware.run new file mode 100644 index 0000000..ef00864 --- /dev/null +++ b/rtic/ci/expected/hardware.run @@ -0,0 +1,4 @@ +init +UART0 called 1 time +idle +UART0 called 2 times diff --git a/rtic/ci/expected/idle-wfi.run b/rtic/ci/expected/idle-wfi.run new file mode 100644 index 0000000..4307776 --- /dev/null +++ b/rtic/ci/expected/idle-wfi.run @@ -0,0 +1,2 @@ +init +idle diff --git a/rtic/ci/expected/idle.run b/rtic/ci/expected/idle.run new file mode 100644 index 0000000..4307776 --- /dev/null +++ b/rtic/ci/expected/idle.run @@ -0,0 +1,2 @@ +init +idle diff --git a/rtic/ci/expected/init.run b/rtic/ci/expected/init.run new file mode 100644 index 0000000..b1b7161 --- /dev/null +++ b/rtic/ci/expected/init.run @@ -0,0 +1 @@ +init diff --git a/rtic/ci/expected/locals.run b/rtic/ci/expected/locals.run new file mode 100644 index 0000000..4f1d350 --- /dev/null +++ b/rtic/ci/expected/locals.run @@ -0,0 +1,3 @@ +bar: local_to_bar = 1 +foo: local_to_foo = 1 +idle: local_to_idle = 1 diff --git a/rtic/ci/expected/lock-free.run b/rtic/ci/expected/lock-free.run new file mode 100644 index 0000000..18de0ec --- /dev/null +++ b/rtic/ci/expected/lock-free.run @@ -0,0 +1,2 @@ + foo = 1 + bar = 2 diff --git a/rtic/ci/expected/lock.run b/rtic/ci/expected/lock.run new file mode 100644 index 0000000..a987b37 --- /dev/null +++ b/rtic/ci/expected/lock.run @@ -0,0 +1,5 @@ +A +B - shared = 1 +C +D - shared = 2 +E diff --git a/rtic/ci/expected/message.run b/rtic/ci/expected/message.run new file mode 100644 index 0000000..11814db --- /dev/null +++ b/rtic/ci/expected/message.run @@ -0,0 +1,6 @@ +foo +bar(0) +baz(1, 2) +foo +bar(1) +baz(2, 3) diff --git a/rtic/ci/expected/message_passing.run b/rtic/ci/expected/message_passing.run new file mode 100644 index 0000000..a1448d8 --- /dev/null +++ b/rtic/ci/expected/message_passing.run @@ -0,0 +1,3 @@ +foo 1, 1 +foo 1, 2 +foo 2, 3 diff --git a/rtic/ci/expected/multilock.run b/rtic/ci/expected/multilock.run new file mode 100644 index 0000000..dd8c1f2 --- /dev/null +++ b/rtic/ci/expected/multilock.run @@ -0,0 +1 @@ +Multiple locks, s1: 1, s2: 1, s3: 1 diff --git a/rtic/ci/expected/not-sync.run b/rtic/ci/expected/not-sync.run new file mode 100644 index 0000000..cd91476 --- /dev/null +++ b/rtic/ci/expected/not-sync.run @@ -0,0 +1,3 @@ +init +bar a 13 +foo a 13 diff --git a/rtic/ci/expected/only-shared-access.run b/rtic/ci/expected/only-shared-access.run new file mode 100644 index 0000000..dcc73e6 --- /dev/null +++ b/rtic/ci/expected/only-shared-access.run @@ -0,0 +1,2 @@ +bar(key = 0xdeadbeef) +foo(key = 0xdeadbeef) diff --git a/rtic/ci/expected/periodic-at.run b/rtic/ci/expected/periodic-at.run new file mode 100644 index 0000000..bf5bb06 --- /dev/null +++ b/rtic/ci/expected/periodic-at.run @@ -0,0 +1,4 @@ +foo Instant { ticks: 0 } +foo Instant { ticks: 10 } +foo Instant { ticks: 20 } +foo Instant { ticks: 30 } diff --git a/rtic/ci/expected/periodic-at2.run b/rtic/ci/expected/periodic-at2.run new file mode 100644 index 0000000..6e56421 --- /dev/null +++ b/rtic/ci/expected/periodic-at2.run @@ -0,0 +1,7 @@ +foo Instant { ticks: 0 } +bar Instant { ticks: 10 } +foo Instant { ticks: 30 } +bar Instant { ticks: 40 } +foo Instant { ticks: 60 } +bar Instant { ticks: 70 } +foo Instant { ticks: 90 } diff --git a/rtic/ci/expected/periodic.run b/rtic/ci/expected/periodic.run new file mode 100644 index 0000000..a1f8944 --- /dev/null +++ b/rtic/ci/expected/periodic.run @@ -0,0 +1,4 @@ +foo +foo +foo +foo diff --git a/rtic/ci/expected/peripherals-taken.run b/rtic/ci/expected/peripherals-taken.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/pool.run b/rtic/ci/expected/pool.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/preempt.run b/rtic/ci/expected/preempt.run new file mode 100644 index 0000000..932b2b3 --- /dev/null +++ b/rtic/ci/expected/preempt.run @@ -0,0 +1,5 @@ +foo - start + baz - start + baz - end + bar +foo - end diff --git a/rtic/ci/expected/ramfunc.run b/rtic/ci/expected/ramfunc.run new file mode 100644 index 0000000..257cc56 --- /dev/null +++ b/rtic/ci/expected/ramfunc.run @@ -0,0 +1 @@ +foo diff --git a/rtic/ci/expected/ramfunc.run.grep.bar b/rtic/ci/expected/ramfunc.run.grep.bar new file mode 100644 index 0000000..33e002f --- /dev/null +++ b/rtic/ci/expected/ramfunc.run.grep.bar @@ -0,0 +1 @@ +20000000 t ramfunc::bar::h9d6714fe5a3b0c89 diff --git a/rtic/ci/expected/ramfunc.run.grep.foo b/rtic/ci/expected/ramfunc.run.grep.foo new file mode 100644 index 0000000..44e8822 --- /dev/null +++ b/rtic/ci/expected/ramfunc.run.grep.foo @@ -0,0 +1 @@ +00000162 t ramfunc::foo::h30e7789b08c08e19 diff --git a/rtic/ci/expected/resource-user-struct.run b/rtic/ci/expected/resource-user-struct.run new file mode 100644 index 0000000..a587a94 --- /dev/null +++ b/rtic/ci/expected/resource-user-struct.run @@ -0,0 +1,2 @@ +UART0: shared = 1 +UART1: shared = 2 diff --git a/rtic/ci/expected/schedule.run b/rtic/ci/expected/schedule.run new file mode 100644 index 0000000..1dbd445 --- /dev/null +++ b/rtic/ci/expected/schedule.run @@ -0,0 +1,4 @@ +init +foo +bar +baz diff --git a/rtic/ci/expected/shared.run b/rtic/ci/expected/shared.run new file mode 100644 index 0000000..6d3d3e4 --- /dev/null +++ b/rtic/ci/expected/shared.run @@ -0,0 +1 @@ +received message: 42 diff --git a/rtic/ci/expected/smallest.run b/rtic/ci/expected/smallest.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/spawn.run b/rtic/ci/expected/spawn.run new file mode 100644 index 0000000..240cd18 --- /dev/null +++ b/rtic/ci/expected/spawn.run @@ -0,0 +1,2 @@ +init +foo diff --git a/rtic/ci/expected/static.run b/rtic/ci/expected/static.run new file mode 100644 index 0000000..3d3f46f --- /dev/null +++ b/rtic/ci/expected/static.run @@ -0,0 +1,3 @@ +received message: 1 +received message: 2 +received message: 3 diff --git a/rtic/ci/expected/t-binds.run b/rtic/ci/expected/t-binds.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-cfg-resources.run b/rtic/ci/expected/t-cfg-resources.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-htask-main.run b/rtic/ci/expected/t-htask-main.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-idle-main.run b/rtic/ci/expected/t-idle-main.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-late-not-send.run b/rtic/ci/expected/t-late-not-send.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-schedule.run b/rtic/ci/expected/t-schedule.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/t-spawn.run b/rtic/ci/expected/t-spawn.run new file mode 100644 index 0000000..e69de29 diff --git a/rtic/ci/expected/task.run b/rtic/ci/expected/task.run new file mode 100644 index 0000000..de45dce --- /dev/null +++ b/rtic/ci/expected/task.run @@ -0,0 +1,5 @@ +foo - start +foo - middle +baz +foo - end +bar diff --git a/rtic/ci/expected/zero-prio-task.run b/rtic/ci/expected/zero-prio-task.run new file mode 100644 index 0000000..123b0f2 --- /dev/null +++ b/rtic/ci/expected/zero-prio-task.run @@ -0,0 +1,3 @@ +init +hello from async +hello from async2 diff --git a/rtic/examples/async-delay.no_rs b/rtic/examples/async-delay.no_rs new file mode 100644 index 0000000..fb478c3 --- /dev/null +++ b/rtic/examples/async-delay.no_rs @@ -0,0 +1,63 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + baz::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // debug::exit(debug::EXIT_SUCCESS); + loop { + // hprintln!("idle"); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + monotonics::delay(100.millis()).await; + hprintln!("bye from foo").ok(); + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + monotonics::delay(200.millis()).await; + hprintln!("bye from bar").ok(); + } + + #[task] + async fn baz(_cx: baz::Context) { + hprintln!("hello from baz").ok(); + monotonics::delay(300.millis()).await; + hprintln!("bye from baz").ok(); + + debug::exit(debug::EXIT_SUCCESS); + } +} diff --git a/rtic/examples/async-infinite-loop.no_rs b/rtic/examples/async-infinite-loop.no_rs new file mode 100644 index 0000000..a95f998 --- /dev/null +++ b/rtic/examples/async-infinite-loop.no_rs @@ -0,0 +1,53 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + // Infinite loops are not allowed in RTIC, however in async tasks they are - if there is an + // await inside the loop. + #[task] + async fn foo(_cx: foo::Context) { + let mut i = 0; + loop { + if i == 5 { + debug::exit(debug::EXIT_SUCCESS); + } + + hprintln!("hello from async {}", i).ok(); + monotonics::delay(100.millis()).await; // This makes it okey! + + i += 1; + } + } +} diff --git a/rtic/examples/async-task-multiple-prios.rs b/rtic/examples/async-task-multiple-prios.rs new file mode 100644 index 0000000..5c9674d --- /dev/null +++ b/rtic/examples/async-task-multiple-prios.rs @@ -0,0 +1,92 @@ +//! examples/async-task-multiple-prios.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + a: u32, + b: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task1::spawn().ok(); + async_task2::spawn().ok(); + async_task3::spawn().ok(); + async_task4::spawn().ok(); + + (Shared { a: 0, b: 0 }, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + } + } + + #[task(priority = 1, shared = [a, b])] + async fn async_task1(mut cx: async_task1::Context) { + hprintln!( + "hello from async 1 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } + + #[task(priority = 1, shared = [a, b])] + async fn async_task2(mut cx: async_task2::Context) { + hprintln!( + "hello from async 2 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } + + #[task(priority = 2, shared = [a, b])] + async fn async_task3(mut cx: async_task3::Context) { + hprintln!( + "hello from async 3 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } + + #[task(priority = 2, shared = [a, b])] + async fn async_task4(mut cx: async_task4::Context) { + hprintln!( + "hello from async 4 a {}", + cx.shared.a.lock(|a| { + *a += 1; + *a + }) + ); + } +} diff --git a/rtic/examples/async-task.rs b/rtic/examples/async-task.rs new file mode 100644 index 0000000..7730c54 --- /dev/null +++ b/rtic/examples/async-task.rs @@ -0,0 +1,70 @@ +//! examples/async-task.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleave and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + a: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task_args::spawn(1, 2).unwrap(); + async_task2::spawn().unwrap(); + + (Shared { a: 0 }, Local {}) + } + + #[idle(shared = [a])] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task(binds = UART1, shared = [a])] + fn hw_task(cx: hw_task::Context) { + let hw_task::SharedResources { a: _, .. } = cx.shared; + hprintln!("hello from hw"); + } + + #[task(shared = [a])] + async fn async_task(cx: async_task::Context) { + let async_task::SharedResources { a: _, .. } = cx.shared; + hprintln!("hello from async"); + } + + #[task] + async fn async_task_args(_cx: async_task_args::Context, a: u32, b: i32) { + hprintln!("hello from async with args a: {}, b: {}", a, b); + } + + #[task(priority = 2, shared = [a])] + async fn async_task2(cx: async_task2::Context) { + let async_task2::SharedResources { a: _, .. } = cx.shared; + hprintln!("hello from async2"); + } +} diff --git a/rtic/examples/async-timeout.no_rs b/rtic/examples/async-timeout.no_rs new file mode 100644 index 0000000..3f68df7 --- /dev/null +++ b/rtic/examples/async-timeout.no_rs @@ -0,0 +1,87 @@ +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +// NOTES: +// +// - Async tasks cannot have `#[lock_free]` resources, as they can interleve and each async +// task can have a mutable reference stored. +// - Spawning an async task equates to it being polled once. + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, UART0], peripherals = true)] +mod app { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + hprintln!("init").unwrap(); + + foo::spawn().ok(); + bar::spawn().ok(); + + ( + Shared {}, + Local {}, + init::Monotonics(Systick::new(cx.core.SYST, 12_000_000)), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + cortex_m::asm::wfi(); // put the MCU in sleep mode until interrupt occurs + } + } + + #[task] + async fn foo(_cx: foo::Context) { + hprintln!("hello from foo").ok(); + + // This will not timeout + match monotonics::timeout_after(monotonics::delay(100.millis()), 200.millis()).await { + Ok(_) => hprintln!("foo no timeout").ok(), + Err(_) => hprintln!("foo timeout").ok(), + }; + } + + #[task] + async fn bar(_cx: bar::Context) { + hprintln!("hello from bar").ok(); + + // This will timeout + match monotonics::timeout_after(NeverEndingFuture {}, 300.millis()).await { + Ok(_) => hprintln!("bar no timeout").ok(), + Err(_) => hprintln!("bar timeout").ok(), + }; + + debug::exit(debug::EXIT_SUCCESS); + } + + pub struct NeverEndingFuture {} + + impl Future for NeverEndingFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { + // Never finish + Poll::Pending + } + } +} diff --git a/rtic/examples/big-struct-opt.rs b/rtic/examples/big-struct-opt.rs new file mode 100644 index 0000000..408a2de --- /dev/null +++ b/rtic/examples/big-struct-opt.rs @@ -0,0 +1,80 @@ +//! examples/big-struct-opt.rs +//! +//! Example on how to initialize a large struct without needing to copy it via `LateResources`, +//! effectively saving stack space needed for the copies. + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +/// Some big struct +pub struct BigStruct { + /// Big content + pub data: [u8; 2048], +} + +impl BigStruct { + fn new() -> Self { + BigStruct { data: [22; 2048] } + } +} + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use super::BigStruct; + use core::mem::MaybeUninit; + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + big_struct: &'static mut BigStruct, + } + + #[local] + struct Local {} + + #[init(local = [bs: MaybeUninit = MaybeUninit::uninit()])] + fn init(cx: init::Context) -> (Shared, Local) { + let big_struct = unsafe { + // write directly into the static storage + cx.local.bs.as_mut_ptr().write(BigStruct::new()); + &mut *cx.local.bs.as_mut_ptr() + }; + + rtic::pend(Interrupt::UART0); + async_task::spawn().unwrap(); + ( + Shared { + // assign the reference so we can use the resource + big_struct, + }, + Local {}, + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + hprintln!("idle"); + debug::exit(debug::EXIT_SUCCESS); + } + } + + #[task(binds = UART0, shared = [big_struct])] + fn uart0(mut cx: uart0::Context) { + cx.shared + .big_struct + .lock(|b| hprintln!("uart0 data:{:?}", &b.data[0..5])); + } + + #[task(shared = [big_struct], priority = 2)] + async fn async_task(mut cx: async_task::Context) { + cx.shared + .big_struct + .lock(|b| hprintln!("async_task data:{:?}", &b.data[0..5])); + } +} diff --git a/rtic/examples/binds.rs b/rtic/examples/binds.rs new file mode 100644 index 0000000..cf078ff --- /dev/null +++ b/rtic/examples/binds.rs @@ -0,0 +1,54 @@ +//! examples/binds.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// `examples/interrupt.rs` rewritten to use `binds` +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + hprintln!("idle"); + + rtic::pend(Interrupt::UART0); + + loop { + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task(binds = UART0, local = [times: u32 = 0])] + fn foo(cx: foo::Context) { + *cx.local.times += 1; + + hprintln!( + "foo called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + } +} diff --git a/rtic/examples/cancel-reschedule.no_rs b/rtic/examples/cancel-reschedule.no_rs new file mode 100644 index 0000000..a38a9c4 --- /dev/null +++ b/rtic/examples/cancel-reschedule.no_rs @@ -0,0 +1,73 @@ +//! examples/cancel-reschedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + hprintln!("init").ok(); + + // Schedule `foo` to run 1 second in the future + foo::spawn_after(1.secs()).unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(mono), // Give the monotonic to RTIC + ) + } + + #[task] + fn foo(_: foo::Context) { + hprintln!("foo").ok(); + + // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) + let spawn_handle = baz::spawn_after(2.secs()).unwrap(); + bar::spawn_after(1.secs(), spawn_handle, false).unwrap(); // Change to true + } + + #[task] + fn bar(_: bar::Context, baz_handle: baz::SpawnHandle, do_reschedule: bool) { + hprintln!("bar").ok(); + + if do_reschedule { + // Reschedule baz 2 seconds from now, instead of the original 1 second + // from now. + baz_handle.reschedule_after(2.secs()).unwrap(); + // Or baz_handle.reschedule_at(/* time */) + } else { + // Or cancel it + baz_handle.cancel().unwrap(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task] + fn baz(_: baz::Context) { + hprintln!("baz").ok(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/capacity.no_rs b/rtic/examples/capacity.no_rs new file mode 100644 index 0000000..a617269 --- /dev/null +++ b/rtic/examples/capacity.no_rs @@ -0,0 +1,49 @@ +//! examples/capacity.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + rtic::pend(Interrupt::UART0); + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { + foo::spawn(0).unwrap(); + foo::spawn(1).unwrap(); + foo::spawn(2).unwrap(); + foo::spawn(3).unwrap(); + + bar::spawn().unwrap(); + } + + #[task(capacity = 4)] + fn foo(_: foo::Context, x: u32) { + hprintln!("foo({})", x).unwrap(); + } + + #[task] + fn bar(_: bar::Context) { + hprintln!("bar").unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/cfg-whole-task.no_rs b/rtic/examples/cfg-whole-task.no_rs new file mode 100644 index 0000000..f41866d --- /dev/null +++ b/rtic/examples/cfg-whole-task.no_rs @@ -0,0 +1,94 @@ +//! examples/cfg-whole-task.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::debug; + #[cfg(debug_assertions)] + use cortex_m_semihosting::hprintln; + + #[shared] + struct Shared { + count: u32, + #[cfg(never)] + unused: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn().unwrap(); + foo::spawn().unwrap(); + + ( + Shared { + count: 0, + #[cfg(never)] + unused: 1, + }, + Local {}, + init::Monotonics(), + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + #[task(capacity = 2, shared = [count])] + fn foo(mut _cx: foo::Context) { + #[cfg(debug_assertions)] + { + _cx.shared.count.lock(|count| *count += 1); + + log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); + } + + // this wouldn't compile in `release` mode + // *_cx.shared.count += 1; + + // .. + } + + // The whole task should disappear, + // currently still present in the Tasks enum + #[cfg(never)] + #[task(capacity = 2, shared = [count])] + fn foo2(mut _cx: foo2::Context) { + #[cfg(debug_assertions)] + { + _cx.shared.count.lock(|count| *count += 10); + + log::spawn(_cx.shared.count.lock(|count| *count)).unwrap(); + } + + // this wouldn't compile in `release` mode + // *_cx.shared.count += 1; + + // .. + } + + #[cfg(debug_assertions)] + #[task(capacity = 2)] + fn log(_: log::Context, n: u32) { + hprintln!( + "foo has been called {} time{}", + n, + if n == 1 { "" } else { "s" } + ) + .ok(); + } +} diff --git a/rtic/examples/common.no_rs b/rtic/examples/common.no_rs new file mode 100644 index 0000000..1fe671e --- /dev/null +++ b/rtic/examples/common.no_rs @@ -0,0 +1,102 @@ +//! examples/common.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; // Implements the `Monotonic` trait + + // A monotonic timer to enable scheduling in RTIC + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + // Resources shared between tasks + #[shared] + struct Shared { + s1: u32, + s2: i32, + } + + // Local resources to specific tasks (cannot be shared) + #[local] + struct Local { + l1: u8, + l2: i8, + } + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + // Spawn the task `foo` directly after `init` finishes + foo::spawn().unwrap(); + + // Spawn the task `bar` 1 second after `init` finishes, this is enabled + // by the `#[monotonic(..)]` above + bar::spawn_after(1.secs()).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + ( + // Initialization of shared resources + Shared { s1: 0, s2: 1 }, + // Initialization of task local resources + Local { l1: 2, l2: 3 }, + // Move the monotonic timer to the RTIC run-time, this enables + // scheduling + init::Monotonics(mono), + ) + } + + // Background task, runs whenever no other tasks are running + #[idle] + fn idle(_: idle::Context) -> ! { + loop { + continue; + } + } + + // Software task, not bound to a hardware interrupt. + // This task takes the task local resource `l1` + // The resources `s1` and `s2` are shared between all other tasks. + #[task(shared = [s1, s2], local = [l1])] + fn foo(_: foo::Context) { + // This task is only spawned once in `init`, hence this task will run + // only once + + hprintln!("foo").ok(); + } + + // Software task, also not bound to a hardware interrupt + // This task takes the task local resource `l2` + // The resources `s1` and `s2` are shared between all other tasks. + #[task(shared = [s1, s2], local = [l2])] + fn bar(_: bar::Context) { + hprintln!("bar").ok(); + + // Run `bar` once per second + bar::spawn_after(1.secs()).unwrap(); + } + + // Hardware task, bound to a hardware interrupt + // The resources `s1` and `s2` are shared between all other tasks. + #[task(binds = UART0, priority = 3, shared = [s1, s2])] + fn uart0_interrupt(_: uart0_interrupt::Context) { + // This task is bound to the interrupt `UART0` and will run + // whenever the interrupt fires + + // Note that RTIC does NOT clear the interrupt flag, this is up to the + // user + + hprintln!("UART0 interrupt!").ok(); + } +} diff --git a/rtic/examples/complex.rs b/rtic/examples/complex.rs new file mode 100644 index 0000000..c1e9c6c --- /dev/null +++ b/rtic/examples/complex.rs @@ -0,0 +1,129 @@ +//! examples/complex.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + s2: u32, // shared with ceiling 2 + s3: u32, // shared with ceiling 3 + s4: u32, // shared with ceiling 4 + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + ( + Shared { + s2: 0, + s3: 0, + s4: 0, + }, + Local {}, + ) + } + + #[idle(shared = [s2, s3])] + fn idle(mut cx: idle::Context) -> ! { + hprintln!("idle p0 started"); + rtic::pend(Interrupt::GPIOC); + cx.shared.s3.lock(|s| { + hprintln!("idle enter lock s3 {}", s); + hprintln!("idle pend t0"); + rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 3 + hprintln!("idle pend t1"); + rtic::pend(Interrupt::GPIOB); // t1 p3, with shared ceiling 3 + hprintln!("idle pend t2"); + rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing + hprintln!("idle still in lock s3 {}", s); + }); + hprintln!("\nback in idle"); + + cx.shared.s2.lock(|s| { + hprintln!("enter lock s2 {}", s); + hprintln!("idle pend t0"); + rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 + hprintln!("idle pend t1"); + rtic::pend(Interrupt::GPIOB); // t1 p3, no sharing + hprintln!("idle pend t2"); + rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing + hprintln!("idle still in lock s2 {}", s); + }); + hprintln!("\nidle exit"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = GPIOA, priority = 2, local = [times: u32 = 0], shared = [s2, s3])] + fn t0(cx: t0::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "t0 p2 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + hprintln!("t0 p2 exit"); + } + + #[task(binds = GPIOB, priority = 3, local = [times: u32 = 0], shared = [s3, s4])] + fn t1(mut cx: t1::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "t1 p3 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + + cx.shared.s4.lock(|s| { + hprintln!("t1 enter lock s4 {}", s); + hprintln!("t1 pend t0"); + rtic::pend(Interrupt::GPIOA); // t0 p2, with shared ceiling 2 + hprintln!("t1 pend t2"); + rtic::pend(Interrupt::GPIOC); // t2 p4, no sharing + hprintln!("t1 still in lock s4 {}", s); + }); + + hprintln!("t1 p3 exit"); + } + + #[task(binds = GPIOC, priority = 4, local = [times: u32 = 0], shared = [s4])] + fn t2(mut cx: t2::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "t2 p4 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + + cx.shared.s4.lock(|s| { + hprintln!("enter lock s4 {}", s); + *s += 1; + }); + hprintln!("t3 p4 exit"); + } +} diff --git a/rtic/examples/declared_locals.rs b/rtic/examples/declared_locals.rs new file mode 100644 index 0000000..c845191 --- /dev/null +++ b/rtic/examples/declared_locals.rs @@ -0,0 +1,47 @@ +//! examples/declared_locals.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [a: u32 = 0])] + fn init(cx: init::Context) -> (Shared, Local) { + // Locals in `#[init]` have 'static lifetime + let _a: &'static mut u32 = cx.local.a; + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + #[idle(local = [a: u32 = 0])] + fn idle(cx: idle::Context) -> ! { + // Locals in `#[idle]` have 'static lifetime + let _a: &'static mut u32 = cx.local.a; + + loop {} + } + + #[task(binds = UART0, local = [a: u32 = 0])] + fn foo(cx: foo::Context) { + // Locals in `#[task]`s have a local lifetime + let _a: &mut u32 = cx.local.a; + + // error: explicit lifetime required in the type of `cx` + // let _a: &'static mut u32 = cx.local.a; + } +} diff --git a/rtic/examples/destructure.rs b/rtic/examples/destructure.rs new file mode 100644 index 0000000..81eff3b --- /dev/null +++ b/rtic/examples/destructure.rs @@ -0,0 +1,57 @@ +//! examples/destructure.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + a: u32, + b: u32, + c: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + (Shared { a: 0, b: 1, c: 2 }, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + + // Direct destructure + #[task(shared = [&a, &b, &c])] + async fn foo(cx: foo::Context) { + let a = cx.shared.a; + let b = cx.shared.b; + let c = cx.shared.c; + + hprintln!("foo: a = {}, b = {}, c = {}", a, b, c); + } + + // De-structure-ing syntax + #[task(shared = [&a, &b, &c])] + async fn bar(cx: bar::Context) { + let bar::SharedResources { a, b, c, .. } = cx.shared; + + hprintln!("bar: a = {}, b = {}, c = {}", a, b, c); + } +} diff --git a/rtic/examples/extern_binds.rs b/rtic/examples/extern_binds.rs new file mode 100644 index 0000000..142a11d --- /dev/null +++ b/rtic/examples/extern_binds.rs @@ -0,0 +1,54 @@ +//! examples/extern_binds.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use cortex_m_semihosting::hprintln; +use panic_semihosting as _; + +// Free function implementing the interrupt bound task `foo`. +fn foo(_: app::foo::Context) { + hprintln!("foo called"); +} + +#[rtic::app(device = lm3s6965)] +mod app { + use crate::foo; + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + hprintln!("idle"); + + rtic::pend(Interrupt::UART0); + + loop { + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + extern "Rust" { + #[task(binds = UART0)] + fn foo(_: foo::Context); + } +} diff --git a/rtic/examples/extern_spawn.rs b/rtic/examples/extern_spawn.rs new file mode 100644 index 0000000..b2b95b9 --- /dev/null +++ b/rtic/examples/extern_spawn.rs @@ -0,0 +1,41 @@ +//! examples/extern_spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use cortex_m_semihosting::{debug, hprintln}; +use panic_semihosting as _; + +// Free function implementing the spawnable task `foo`. +// Notice, you need to indicate an anonymous lifetime <'a_> +async fn foo(_c: app::foo::Context<'_>) { + hprintln!("foo"); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator +} + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use crate::foo; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + extern "Rust" { + #[task()] + async fn foo(_c: foo::Context); + } +} diff --git a/rtic/examples/generics.rs b/rtic/examples/generics.rs new file mode 100644 index 0000000..2f23cce --- /dev/null +++ b/rtic/examples/generics.rs @@ -0,0 +1,67 @@ +//! examples/generics.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use cortex_m_semihosting::hprintln; +use panic_semihosting as _; +use rtic::Mutex; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + shared: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + rtic::pend(Interrupt::UART1); + + (Shared { shared: 0 }, Local {}) + } + + #[task(binds = UART0, shared = [shared], local = [state: u32 = 0])] + fn uart0(c: uart0::Context) { + hprintln!("UART0(STATE = {})", *c.local.state); + + // second argument has type `shared::shared` + super::advance(c.local.state, c.shared.shared); + + rtic::pend(Interrupt::UART1); + + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(binds = UART1, priority = 2, shared = [shared], local = [state: u32 = 0])] + fn uart1(c: uart1::Context) { + hprintln!("UART1(STATE = {})", *c.local.state); + + // second argument has type `shared::shared` + super::advance(c.local.state, c.shared.shared); + } +} + +// the second parameter is generic: it can be any type that implements the `Mutex` trait +fn advance(state: &mut u32, mut shared: impl Mutex) { + *state += 1; + + let (old, new) = shared.lock(|shared: &mut u32| { + let old = *shared; + *shared += *state; + (old, *shared) + }); + + hprintln!("shared: {} -> {}", old, new); +} diff --git a/rtic/examples/hardware.rs b/rtic/examples/hardware.rs new file mode 100644 index 0000000..62ae0d6 --- /dev/null +++ b/rtic/examples/hardware.rs @@ -0,0 +1,58 @@ +//! examples/hardware.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + // Pends the UART0 interrupt but its handler won't run until *after* + // `init` returns because interrupts are disabled + rtic::pend(Interrupt::UART0); // equivalent to NVIC::pend + + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // interrupts are enabled again; the `UART0` handler runs at this point + + hprintln!("idle"); + + rtic::pend(Interrupt::UART0); + + loop { + cortex_m::asm::nop(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } + + #[task(binds = UART0, local = [times: u32 = 0])] + fn uart0(cx: uart0::Context) { + // Safe access to local `static mut` variable + *cx.local.times += 1; + + hprintln!( + "UART0 called {} time{}", + *cx.local.times, + if *cx.local.times > 1 { "s" } else { "" } + ); + } +} diff --git a/rtic/examples/idle-wfi.rs b/rtic/examples/idle-wfi.rs new file mode 100644 index 0000000..8134ce3 --- /dev/null +++ b/rtic/examples/idle-wfi.rs @@ -0,0 +1,48 @@ +//! examples/idle-wfi.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(mut cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + // Set the ARM SLEEPONEXIT bit to go to sleep after handling interrupts + // See https://developer.arm.com/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit + cx.core.SCB.set_sleepdeep(); + + (Shared {}, Local {}) + } + + #[idle(local = [x: u32 = 0])] + fn idle(cx: idle::Context) -> ! { + // Locals in idle have lifetime 'static + let _x: &'static mut u32 = cx.local.x; + + hprintln!("idle"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + // Now Wait For Interrupt is used instead of a busy-wait loop + // to allow MCU to sleep between interrupts + // https://developer.arm.com/documentation/ddi0406/c/Application-Level-Architecture/Instruction-Details/Alphabetical-list-of-instructions/WFI + rtic::export::wfi() + } + } +} diff --git a/rtic/examples/idle.rs b/rtic/examples/idle.rs new file mode 100644 index 0000000..0c4bd04 --- /dev/null +++ b/rtic/examples/idle.rs @@ -0,0 +1,41 @@ +//! examples/idle.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + (Shared {}, Local {}) + } + + #[idle(local = [x: u32 = 0])] + fn idle(cx: idle::Context) -> ! { + // Locals in idle have lifetime 'static + let _x: &'static mut u32 = cx.local.x; + + hprintln!("idle"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/init.rs b/rtic/examples/init.rs new file mode 100644 index 0000000..c3081bf --- /dev/null +++ b/rtic/examples/init.rs @@ -0,0 +1,42 @@ +//! examples/init.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [x: u32 = 0])] + fn init(cx: init::Context) -> (Shared, Local) { + // Cortex-M peripherals + let _core: cortex_m::Peripherals = cx.core; + + // Device specific peripherals + let _device: lm3s6965::Peripherals = cx.device; + + // Locals in `init` have 'static lifetime + let _x: &'static mut u32 = cx.local.x; + + // Access to the critical section token, + // to indicate that this is a critical section + let _cs_token: bare_metal::CriticalSection = cx.cs; + + hprintln!("init"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } +} diff --git a/rtic/examples/locals.rs b/rtic/examples/locals.rs new file mode 100644 index 0000000..ec3d59d --- /dev/null +++ b/rtic/examples/locals.rs @@ -0,0 +1,87 @@ +//! examples/locals.rs + +#![feature(type_alias_impl_trait)] +#![deny(unsafe_code)] +#![deny(missing_docs)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local { + local_to_foo: i64, + local_to_bar: i64, + local_to_idle: i64, + } + + // `#[init]` cannot access locals from the `#[local]` struct as they are initialized here. + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + ( + Shared {}, + // initial values for the `#[local]` resources + Local { + local_to_foo: 0, + local_to_bar: 0, + local_to_idle: 0, + }, + ) + } + + // `local_to_idle` can only be accessed from this context + #[idle(local = [local_to_idle])] + fn idle(cx: idle::Context) -> ! { + let local_to_idle = cx.local.local_to_idle; + *local_to_idle += 1; + + hprintln!("idle: local_to_idle = {}", local_to_idle); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + // error: no `local_to_foo` field in `idle::LocalResources` + // _cx.local.local_to_foo += 1; + + // error: no `local_to_bar` field in `idle::LocalResources` + // _cx.local.local_to_bar += 1; + + loop { + cortex_m::asm::nop(); + } + } + + // `local_to_foo` can only be accessed from this context + #[task(local = [local_to_foo])] + async fn foo(cx: foo::Context) { + let local_to_foo = cx.local.local_to_foo; + *local_to_foo += 1; + + // error: no `local_to_bar` field in `foo::LocalResources` + // cx.local.local_to_bar += 1; + + hprintln!("foo: local_to_foo = {}", local_to_foo); + } + + // `local_to_bar` can only be accessed from this context + #[task(local = [local_to_bar])] + async fn bar(cx: bar::Context) { + let local_to_bar = cx.local.local_to_bar; + *local_to_bar += 1; + + // error: no `local_to_foo` field in `bar::LocalResources` + // cx.local.local_to_foo += 1; + + hprintln!("bar: local_to_bar = {}", local_to_bar); + } +} diff --git a/rtic/examples/lock-free.no_rs b/rtic/examples/lock-free.no_rs new file mode 100644 index 0000000..053307c --- /dev/null +++ b/rtic/examples/lock-free.no_rs @@ -0,0 +1,50 @@ +//! examples/lock-free.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + #[lock_free] // <- lock-free shared resource + counter: u64, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared { counter: 0 }, Local {}) + } + + #[task(shared = [counter])] // <- same priority + async fn foo(c: foo::Context) { + bar::spawn().unwrap(); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" foo = {}", counter).unwrap(); + } + + #[task(shared = [counter])] // <- same priority + async fn bar(c: bar::Context) { + foo::spawn().unwrap(); + + *c.shared.counter += 1; // <- no lock API required + let counter = *c.shared.counter; + hprintln!(" bar = {}", counter).unwrap(); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/lock.rs b/rtic/examples/lock.rs new file mode 100644 index 0000000..203ae6f --- /dev/null +++ b/rtic/examples/lock.rs @@ -0,0 +1,73 @@ +//! examples/lock.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA, GPIOB, GPIOC])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + shared: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared { shared: 0 }, Local {}) + } + + // when omitted priority is assumed to be `1` + #[task(shared = [shared])] + async fn foo(mut c: foo::Context) { + hprintln!("A"); + + // the lower priority task requires a critical section to access the data + c.shared.shared.lock(|shared| { + // data can only be modified within this critical section (closure) + *shared += 1; + + // bar will *not* run right now due to the critical section + bar::spawn().unwrap(); + + hprintln!("B - shared = {}", *shared); + + // baz does not contend for `shared` so it's allowed to run now + baz::spawn().unwrap(); + }); + + // critical section is over: bar can now start + + hprintln!("E"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2, shared = [shared])] + async fn bar(mut c: bar::Context) { + // the higher priority task does still need a critical section + let shared = c.shared.shared.lock(|shared| { + *shared += 1; + + *shared + }); + + hprintln!("D - shared = {}", shared); + } + + #[task(priority = 3)] + async fn baz(_: baz::Context) { + hprintln!("C"); + } +} diff --git a/rtic/examples/message.no_rs b/rtic/examples/message.no_rs new file mode 100644 index 0000000..76c5675 --- /dev/null +++ b/rtic/examples/message.no_rs @@ -0,0 +1,52 @@ +//! examples/message.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn(/* no message */).unwrap(); + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(local = [count: u32 = 0])] + fn foo(cx: foo::Context) { + hprintln!("foo").unwrap(); + + bar::spawn(*cx.local.count).unwrap(); + *cx.local.count += 1; + } + + #[task] + fn bar(_: bar::Context, x: u32) { + hprintln!("bar({})", x).unwrap(); + + baz::spawn(x + 1, x + 2).unwrap(); + } + + #[task] + fn baz(_: baz::Context, x: u32, y: u32) { + hprintln!("baz({}, {})", x, y).unwrap(); + + if x + y > 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + foo::spawn().unwrap(); + } +} diff --git a/rtic/examples/message_passing.no_rs b/rtic/examples/message_passing.no_rs new file mode 100644 index 0000000..ffa9537 --- /dev/null +++ b/rtic/examples/message_passing.no_rs @@ -0,0 +1,37 @@ +//! examples/message_passing.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + foo::spawn(1, 1).unwrap(); + foo::spawn(1, 2).unwrap(); + foo::spawn(2, 3).unwrap(); + assert!(foo::spawn(1, 4).is_err()); // The capacity of `foo` is reached + + (Shared {}, Local {}, init::Monotonics()) + } + + #[task(capacity = 3)] + fn foo(_c: foo::Context, x: i32, y: u32) { + hprintln!("foo {}, {}", x, y).unwrap(); + if x == 2 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + } +} diff --git a/rtic/examples/multilock.rs b/rtic/examples/multilock.rs new file mode 100644 index 0000000..6208cac --- /dev/null +++ b/rtic/examples/multilock.rs @@ -0,0 +1,57 @@ +//! examples/mutlilock.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [GPIOA])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + shared1: u32, + shared2: u32, + shared3: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + locks::spawn().unwrap(); + + ( + Shared { + shared1: 0, + shared2: 0, + shared3: 0, + }, + Local {}, + ) + } + + // when omitted priority is assumed to be `1` + #[task(shared = [shared1, shared2, shared3])] + async fn locks(c: locks::Context) { + let s1 = c.shared.shared1; + let s2 = c.shared.shared2; + let s3 = c.shared.shared3; + + (s1, s2, s3).lock(|s1, s2, s3| { + *s1 += 1; + *s2 += 1; + *s3 += 1; + + hprintln!("Multiple locks, s1: {}, s2: {}, s3: {}", *s1, *s2, *s3); + }); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/not-sync.rs b/rtic/examples/not-sync.rs new file mode 100644 index 0000000..6d1ddae --- /dev/null +++ b/rtic/examples/not-sync.rs @@ -0,0 +1,69 @@ +//! `examples/not-sync.rs` + +// #![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Not sync +pub struct NotSync { + _0: PhantomData<*const ()>, + data: u32, +} + +unsafe impl Send for NotSync {} + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use super::NotSync; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + shared: NotSync, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + + foo::spawn().unwrap(); + bar::spawn().unwrap(); + ( + Shared { + shared: NotSync { + _0: PhantomData, + data: 13, + }, + }, + Local {}, + ) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop {} + } + + #[task(shared = [&shared])] + async fn foo(c: foo::Context) { + let shared: &NotSync = c.shared.shared; + hprintln!("foo a {}", shared.data); + } + + #[task(shared = [&shared])] + async fn bar(c: bar::Context) { + let shared: &NotSync = c.shared.shared; + hprintln!("bar a {}", shared.data); + } +} diff --git a/rtic/examples/only-shared-access.rs b/rtic/examples/only-shared-access.rs new file mode 100644 index 0000000..1d006e6 --- /dev/null +++ b/rtic/examples/only-shared-access.rs @@ -0,0 +1,44 @@ +//! examples/only-shared-access.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0, UART1])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + key: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + bar::spawn().unwrap(); + + (Shared { key: 0xdeadbeef }, Local {}) + } + + #[task(shared = [&key])] + async fn foo(cx: foo::Context) { + let key: &u32 = cx.shared.key; + hprintln!("foo(key = {:#x})", key); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2, shared = [&key])] + async fn bar(cx: bar::Context) { + hprintln!("bar(key = {:#x})", cx.shared.key); + } +} diff --git a/rtic/examples/periodic-at.no_rs b/rtic/examples/periodic-at.no_rs new file mode 100644 index 0000000..ca68ed5 --- /dev/null +++ b/rtic/examples/periodic-at.no_rs @@ -0,0 +1,49 @@ +//! examples/periodic-at.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mut mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(1.secs(), mono.now()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { + hprintln!("foo {:?}", instant).ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Periodic every 100 milliseconds + let next_instant = instant + 100.millis(); + foo::spawn_at(next_instant, next_instant).unwrap(); + } +} diff --git a/rtic/examples/periodic-at2.no_rs b/rtic/examples/periodic-at2.no_rs new file mode 100644 index 0000000..ec9adcc --- /dev/null +++ b/rtic/examples/periodic-at2.no_rs @@ -0,0 +1,61 @@ +//! examples/periodic-at2.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mut mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(200.millis(), mono.now()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + // Using the explicit type of the timer implementation + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context, instant: fugit::TimerInstantU64<100>) { + hprintln!("foo {:?}", instant).ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Spawn a new message with 100 ms offset to spawned time + let next_instant = instant + 100.millis(); + bar::spawn_at(next_instant, next_instant).unwrap(); + } + + // Using the Instant from the Monotonic trait + // This remains agnostic to the timer implementation + #[task(local = [cnt: u32 = 0])] + fn bar(_cx: bar::Context, instant: ::Instant) { + hprintln!("bar {:?}", instant).ok(); + + // Spawn a new message with 200ms offset to spawned time + let next_instant = instant + 200.millis(); + foo::spawn_at(next_instant, next_instant).unwrap(); + } +} diff --git a/rtic/examples/periodic.no_rs b/rtic/examples/periodic.no_rs new file mode 100644 index 0000000..2f9e8e6 --- /dev/null +++ b/rtic/examples/periodic.no_rs @@ -0,0 +1,48 @@ +//! examples/periodic.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + foo::spawn_after(100.millis()).unwrap(); + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[task(local = [cnt: u32 = 0])] + fn foo(cx: foo::Context) { + hprintln!("foo").ok(); + *cx.local.cnt += 1; + + if *cx.local.cnt == 4 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // Periodic every 100ms + foo::spawn_after(100.millis()).unwrap(); + } +} diff --git a/rtic/examples/peripherals-taken.rs b/rtic/examples/peripherals-taken.rs new file mode 100644 index 0000000..2f710e9 --- /dev/null +++ b/rtic/examples/peripherals-taken.rs @@ -0,0 +1,28 @@ +//! examples/peripherals-taken.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + assert!(cortex_m::Peripherals::take().is_none()); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } +} diff --git a/rtic/examples/pool.no_rs b/rtic/examples/pool.no_rs new file mode 100644 index 0000000..fb8589a --- /dev/null +++ b/rtic/examples/pool.no_rs @@ -0,0 +1,70 @@ +//! examples/pool.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use heapless::{ + pool, + pool::singleton::{Box, Pool}, +}; +use panic_semihosting as _; +use rtic::app; + +// Declare a pool of 128-byte memory blocks +pool!(P: [u8; 128]); + +#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use crate::{Box, Pool}; + use cortex_m_semihosting::debug; + use lm3s6965::Interrupt; + + // Import the memory pool into scope + use super::P; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init(local = [memory: [u8; 512] = [0; 512]])] + fn init(cx: init::Context) -> (Shared, Local) { + // Increase the capacity of the memory pool by ~4 + P::grow(cx.local.memory); + + rtic::pend(Interrupt::I2C0); + + (Shared {}, Local {}) + } + + #[task(binds = I2C0, priority = 2)] + async fn i2c0(_: i2c0::Context) { + // claim a memory block, initialize it and .. + let x = P::alloc().unwrap().init([0u8; 128]); + + // .. send it to the `foo` task + foo::spawn(x).ok().unwrap(); + + // send another block to the task `bar` + bar::spawn(P::alloc().unwrap().init([0u8; 128])) + .ok() + .unwrap(); + } + + #[task] + async fn foo(_: foo::Context, _x: Box

) { + // explicitly return the block to the pool + drop(_x); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn bar(_: bar::Context, _x: Box

) { + // this is done automatically so we can omit the call to `drop` + // drop(_x); + } +} diff --git a/rtic/examples/preempt.rs b/rtic/examples/preempt.rs new file mode 100644 index 0000000..4b11907 --- /dev/null +++ b/rtic/examples/preempt.rs @@ -0,0 +1,47 @@ +//! examples/preempt.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use panic_semihosting as _; +use rtic::app; + +#[app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task(priority = 1)] + async fn foo(_: foo::Context) { + hprintln!("foo - start"); + baz::spawn().unwrap(); + hprintln!("foo - end"); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn bar(_: bar::Context) { + hprintln!(" bar"); + } + + #[task(priority = 2)] + async fn baz(_: baz::Context) { + hprintln!(" baz - start"); + bar::spawn().unwrap(); + hprintln!(" baz - end"); + } +} diff --git a/rtic/examples/ramfunc.rs b/rtic/examples/ramfunc.rs new file mode 100644 index 0000000..e2e7f67 --- /dev/null +++ b/rtic/examples/ramfunc.rs @@ -0,0 +1,50 @@ +//! examples/ramfunc.rs + +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app( + device = lm3s6965, + dispatchers = [ + UART0, + #[link_section = ".data.UART1"] + UART1 + ]) +] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[inline(never)] + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + // run this task from RAM + #[inline(never)] + #[link_section = ".data.bar"] + #[task(priority = 2)] + async fn bar(_: bar::Context) { + foo::spawn().unwrap(); + } +} diff --git a/rtic/examples/resource-user-struct.rs b/rtic/examples/resource-user-struct.rs new file mode 100644 index 0000000..fcbacae --- /dev/null +++ b/rtic/examples/resource-user-struct.rs @@ -0,0 +1,72 @@ +//! examples/resource.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + // A resource + shared: u32, + } + + // Should not collide with the struct above + #[allow(dead_code)] + struct Shared2 { + // A resource + shared: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(Interrupt::UART0); + rtic::pend(Interrupt::UART1); + + (Shared { shared: 0 }, Local {}) + } + + // `shared` cannot be accessed from this context + #[idle] + fn idle(_cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + // error: no `shared` field in `idle::Context` + // _cx.shared.shared += 1; + + loop {} + } + + // `shared` can be accessed from this context + #[task(binds = UART0, shared = [shared])] + fn uart0(mut cx: uart0::Context) { + let shared = cx.shared.shared.lock(|shared| { + *shared += 1; + *shared + }); + + hprintln!("UART0: shared = {}", shared); + } + + // `shared` can be accessed from this context + #[task(binds = UART1, shared = [shared])] + fn uart1(mut cx: uart1::Context) { + let shared = cx.shared.shared.lock(|shared| { + *shared += 1; + *shared + }); + + hprintln!("UART1: shared = {}", shared); + } +} diff --git a/rtic/examples/schedule.no_rs b/rtic/examples/schedule.no_rs new file mode 100644 index 0000000..5bad5a3 --- /dev/null +++ b/rtic/examples/schedule.no_rs @@ -0,0 +1,64 @@ +//! examples/schedule.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + hprintln!("init").ok(); + + // Schedule `foo` to run 1 second in the future + foo::spawn_after(1.secs()).unwrap(); + + ( + Shared {}, + Local {}, + init::Monotonics(mono), // Give the monotonic to RTIC + ) + } + + #[task] + fn foo(_: foo::Context) { + hprintln!("foo").ok(); + + // Schedule `bar` to run 2 seconds in the future (1 second after foo runs) + bar::spawn_after(1.secs()).unwrap(); + } + + #[task] + fn bar(_: bar::Context) { + hprintln!("bar").ok(); + + // Schedule `baz` to run 1 seconds from now, but with a specific time instant. + baz::spawn_at(monotonics::now() + 1.secs()).unwrap(); + } + + #[task] + fn baz(_: baz::Context) { + hprintln!("baz").ok(); + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/shared.rs b/rtic/examples/shared.rs new file mode 100644 index 0000000..d0633fb --- /dev/null +++ b/rtic/examples/shared.rs @@ -0,0 +1,51 @@ +//! examples/late.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use heapless::spsc::{Consumer, Producer, Queue}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + p: Producer<'static, u32, 5>, + c: Consumer<'static, u32, 5>, + } + + #[local] + struct Local {} + + #[init(local = [q: Queue = Queue::new()])] + fn init(cx: init::Context) -> (Shared, Local) { + let (p, c) = cx.local.q.split(); + + // Initialization of shared resources + (Shared { p, c }, Local {}) + } + + #[idle(shared = [c])] + fn idle(mut c: idle::Context) -> ! { + loop { + if let Some(byte) = c.shared.c.lock(|c| c.dequeue()) { + hprintln!("received message: {}", byte); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } else { + rtic::pend(Interrupt::UART0); + } + } + } + + #[task(binds = UART0, shared = [p])] + fn uart0(mut c: uart0::Context) { + c.shared.p.lock(|p| p.enqueue(42).unwrap()); + } +} diff --git a/rtic/examples/smallest.rs b/rtic/examples/smallest.rs new file mode 100644 index 0000000..e54ae44 --- /dev/null +++ b/rtic/examples/smallest.rs @@ -0,0 +1,25 @@ +//! examples/smallest.rs + +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use panic_semihosting as _; // panic handler +use rtic::app; + +#[app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + (Shared {}, Local {}) + } +} diff --git a/rtic/examples/spawn.rs b/rtic/examples/spawn.rs new file mode 100644 index 0000000..d30ecf1 --- /dev/null +++ b/rtic/examples/spawn.rs @@ -0,0 +1,36 @@ +//! examples/spawn.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + hprintln!("init"); + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/static.rs b/rtic/examples/static.rs new file mode 100644 index 0000000..7f656f4 --- /dev/null +++ b/rtic/examples/static.rs @@ -0,0 +1,61 @@ +//! examples/static.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use heapless::spsc::{Consumer, Producer, Queue}; + + #[shared] + struct Shared {} + + #[local] + struct Local { + p: Producer<'static, u32, 5>, + c: Consumer<'static, u32, 5>, + } + + #[init(local = [q: Queue = Queue::new()])] + fn init(cx: init::Context) -> (Shared, Local) { + // q has 'static life-time so after the split and return of `init` + // it will continue to exist and be allocated + let (p, c) = cx.local.q.split(); + + foo::spawn().unwrap(); + + (Shared {}, Local { p, c }) + } + + #[idle(local = [c])] + fn idle(c: idle::Context) -> ! { + loop { + // Lock-free access to the same underlying queue! + if let Some(data) = c.local.c.dequeue() { + hprintln!("received message: {}", data); + + // Run foo until data + if data == 3 { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } else { + foo::spawn().unwrap(); + } + } + } + } + + #[task(local = [p, state: u32 = 0])] + async fn foo(c: foo::Context) { + *c.local.state += 1; + + // Lock-free access to the same underlying queue! + c.local.p.enqueue(*c.local.state).unwrap(); + } +} diff --git a/rtic/examples/t-binds.rs b/rtic/examples/t-binds.rs new file mode 100644 index 0000000..bdeb391 --- /dev/null +++ b/rtic/examples/t-binds.rs @@ -0,0 +1,45 @@ +//! [compile-pass] Check that `binds` works as advertised + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + // Cortex-M exception + #[task(binds = SVCall)] + fn foo(c: foo::Context) { + crate::foo_trampoline(c) + } + + // LM3S6965 interrupt + #[task(binds = UART0)] + fn bar(c: bar::Context) { + crate::bar_trampoline(c) + } +} + +#[allow(dead_code)] +fn foo_trampoline(_: app::foo::Context) {} + +#[allow(dead_code)] +fn bar_trampoline(_: app::bar::Context) {} diff --git a/rtic/examples/t-cfg-resources.rs b/rtic/examples/t-cfg-resources.rs new file mode 100644 index 0000000..0328700 --- /dev/null +++ b/rtic/examples/t-cfg-resources.rs @@ -0,0 +1,42 @@ +//! [compile-pass] check that `#[cfg]` attributes applied on resources work + +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared { + // A conditionally compiled resource behind feature_x + #[cfg(feature = "feature_x")] + x: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + ( + Shared { + #[cfg(feature = "feature_x")] + x: 0, + }, + Local {}, + ) + } + + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/t-htask-main.rs b/rtic/examples/t-htask-main.rs new file mode 100644 index 0000000..8f885bc --- /dev/null +++ b/rtic/examples/t-htask-main.rs @@ -0,0 +1,32 @@ +//! examples/h-task-main.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + rtic::pend(lm3s6965::Interrupt::UART0); + + (Shared {}, Local {}) + } + + #[task(binds = UART0)] + fn taskmain(_: taskmain::Context) { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/examples/t-idle-main.rs b/rtic/examples/t-idle-main.rs new file mode 100644 index 0000000..43215cf --- /dev/null +++ b/rtic/examples/t-idle-main.rs @@ -0,0 +1,33 @@ +//! examples/t-idle-main.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[idle] + fn taskmain(_: taskmain::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/t-late-not-send.rs b/rtic/examples/t-late-not-send.rs new file mode 100644 index 0000000..44d1d85 --- /dev/null +++ b/rtic/examples/t-late-not-send.rs @@ -0,0 +1,48 @@ +//! [compile-pass] shared resources don't need to be `Send` if they are owned by `idle` + +#![no_main] +#![no_std] +#![deny(missing_docs)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Not send +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::debug; + + #[shared] + struct Shared { + x: NotSend, + y: Option, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + ( + Shared { + x: NotSend { _0: PhantomData }, + y: None, + }, + Local {}, + ) + } + + #[idle(shared = [x, y])] + fn idle(_: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + loop { + cortex_m::asm::nop(); + } + } +} diff --git a/rtic/examples/t-schedule.no_rs b/rtic/examples/t-schedule.no_rs new file mode 100644 index 0000000..5ec4208 --- /dev/null +++ b/rtic/examples/t-schedule.no_rs @@ -0,0 +1,136 @@ +//! [compile-pass] Check `schedule` code generation + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::debug; + use systick_monotonic::*; + + #[monotonic(binds = SysTick, default = true)] + type MyMono = Systick<100>; // 100 Hz / 10 ms granularity + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) { + let systick = cx.core.SYST; + + // Initialize the monotonic (SysTick rate in QEMU is 12 MHz) + let mono = Systick::new(systick, 12_000_000); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}, init::Monotonics(mono)) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + // Task without message passing + + // Not default + let _: Result = + foo::MyMono::spawn_at(monotonics::MyMono::now()); + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = foo::MyMono::spawn_after(1.secs()); + let _: Result<(), ()> = handle.unwrap().cancel(); + + // Using default + let _: Result = foo::spawn_at(monotonics::now()); + let handle: Result = foo::spawn_after(1.secs()); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = foo::spawn_after(1.secs()); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = foo::spawn_after(1.secs()); + let _: Result<(), ()> = handle.unwrap().cancel(); + + // Task with single message passing + + // Not default + let _: Result = + bar::MyMono::spawn_at(monotonics::MyMono::now(), 0); + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = bar::MyMono::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().cancel(); + + // Using default + let _: Result = bar::spawn_at(monotonics::MyMono::now(), 0); + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = bar::spawn_after(1.secs(), 1); + let _: Result = handle.unwrap().cancel(); + + // Task with multiple message passing + + // Not default + let _: Result = + baz::MyMono::spawn_at(monotonics::MyMono::now(), 0, 1); + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = + baz::MyMono::spawn_after(1.secs(), 1, 2); + let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); + + // Using default + let _: Result = + baz::spawn_at(monotonics::MyMono::now(), 0, 1); + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result = handle.unwrap().reschedule_after(1.secs()); + + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result = + handle.unwrap().reschedule_at(monotonics::MyMono::now()); + + let handle: Result = baz::spawn_after(1.secs(), 1, 2); + let _: Result<(u32, u32), ()> = handle.unwrap().cancel(); + + loop { + cortex_m::asm::nop(); + } + } + + #[task] + fn foo(_: foo::Context) {} + + #[task] + fn bar(_: bar::Context, _x: u32) {} + + #[task] + fn baz(_: baz::Context, _x: u32, _y: u32) {} +} diff --git a/rtic/examples/t-spawn.no_rs b/rtic/examples/t-spawn.no_rs new file mode 100644 index 0000000..dad0c83 --- /dev/null +++ b/rtic/examples/t-spawn.no_rs @@ -0,0 +1,69 @@ +//! [compile-pass] Check code generation of `spawn` + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0])] +mod app { + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + (Shared {}, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + + loop { + cortex_m::asm::nop(); + } + } + + #[task(binds = SVCall)] + fn svcall(_: svcall::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task(binds = UART0)] + fn uart0(_: uart0::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task] + async fn foo(_: foo::Context) { + let _: Result<(), ()> = foo::spawn(); + let _: Result<(), u32> = bar::spawn(0); + let _: Result<(), (u32, u32)> = baz::spawn(0, 1); + } + + #[task] + async fn bar(_: bar::Context, _x: u32) {} + + #[task] + async fn baz(_: baz::Context, _x: u32, _y: u32) {} +} diff --git a/rtic/examples/task.rs b/rtic/examples/task.rs new file mode 100644 index 0000000..ab6a1e0 --- /dev/null +++ b/rtic/examples/task.rs @@ -0,0 +1,58 @@ +//! examples/task.rs + +#![deny(unsafe_code)] +#![deny(warnings)] +#![deny(missing_docs)] +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0])] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + foo::spawn().unwrap(); + + (Shared {}, Local {}) + } + + #[task] + async fn foo(_: foo::Context) { + hprintln!("foo - start"); + + // spawns `bar` onto the task scheduler + // `foo` and `bar` have the same priority so `bar` will not run until + // after `foo` terminates + bar::spawn().unwrap(); + + hprintln!("foo - middle"); + + // spawns `baz` onto the task scheduler + // `baz` has higher priority than `foo` so it immediately preempts `foo` + baz::spawn().unwrap(); + + hprintln!("foo - end"); + } + + #[task] + async fn bar(_: bar::Context) { + hprintln!("bar"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } + + #[task(priority = 2)] + async fn baz(_: baz::Context) { + hprintln!("baz"); + } +} diff --git a/rtic/examples/zero-prio-task.rs b/rtic/examples/zero-prio-task.rs new file mode 100644 index 0000000..c810e8f --- /dev/null +++ b/rtic/examples/zero-prio-task.rs @@ -0,0 +1,60 @@ +//! examples/zero-prio-task.rs + +#![no_main] +#![no_std] +#![feature(type_alias_impl_trait)] +#![deny(missing_docs)] + +use core::marker::PhantomData; +use panic_semihosting as _; + +/// Does not impl send +pub struct NotSend { + _0: PhantomData<*const ()>, +} + +#[rtic::app(device = lm3s6965, peripherals = true)] +mod app { + use super::NotSend; + use core::marker::PhantomData; + use cortex_m_semihosting::{debug, hprintln}; + + #[shared] + struct Shared { + x: NotSend, + } + + #[local] + struct Local { + y: NotSend, + } + + #[init] + fn init(_cx: init::Context) -> (Shared, Local) { + hprintln!("init"); + + async_task::spawn().unwrap(); + async_task2::spawn().unwrap(); + + ( + Shared { + x: NotSend { _0: PhantomData }, + }, + Local { + y: NotSend { _0: PhantomData }, + }, + ) + } + + #[task(priority = 0, shared = [x], local = [y])] + async fn async_task(_: async_task::Context) { + hprintln!("hello from async"); + } + + #[task(priority = 0, shared = [x])] + async fn async_task2(_: async_task2::Context) { + hprintln!("hello from async2"); + + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + } +} diff --git a/rtic/macros/.gitignore b/rtic/macros/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/rtic/macros/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/rtic/macros/Cargo.toml b/rtic/macros/Cargo.toml new file mode 100644 index 0000000..2041d37 --- /dev/null +++ b/rtic/macros/Cargo.toml @@ -0,0 +1,41 @@ +[package] +authors = [ + "The Real-Time Interrupt-driven Concurrency developers", + "Emil Fresk ", + "Henrik Tjäder ", + "Jorge Aparicio ", + "Per Lindgren ", +] +categories = ["concurrency", "embedded", "no-std", "asynchronous"] +description = "Procedural macros, syntax parsing, and codegen of the RTIC crate" +documentation = "https://rtic-rs.github.io/rtic/api/rtic" +edition = "2021" +keywords = ["arm", "cortex-m", "risc-v", "embedded", "async", "runtime", "futures", "await", "no-std", "rtos", "bare-metal"] +license = "MIT OR Apache-2.0" +name = "rtic-macros" +readme = "../README.md" +repository = "https://github.com/rtic-rs/rtic" + +version = "2.0.0-alpha.0" + +[lib] +proc-macro = true + +[features] +default = [] +debugprint = [] +# list of supported codegen backends +thumbv6 = [] +thumbv7 = [] +# riscv-clic = [] +# riscv-ch32 = [] + +[dependencies] +indexmap = "1.9.2" +proc-macro2 = "1.0.49" +proc-macro-error = "1.0.4" +quote = "1.0.23" +syn = { version = "1.0.107", features = ["extra-traits", "full"] } + +[dev-dependencies] +trybuild = "1.0.73" diff --git a/rtic/macros/src/analyze.rs b/rtic/macros/src/analyze.rs new file mode 100644 index 0000000..65774f6 --- /dev/null +++ b/rtic/macros/src/analyze.rs @@ -0,0 +1,49 @@ +use core::ops; +use std::collections::{BTreeMap, BTreeSet}; + +use crate::syntax::{ + analyze::{self, Priority}, + ast::{App, Dispatcher}, +}; +use syn::Ident; + +/// Extend the upstream `Analysis` struct with our field +pub struct Analysis { + parent: analyze::Analysis, + pub interrupts: BTreeMap, +} + +impl ops::Deref for Analysis { + type Target = analyze::Analysis; + + fn deref(&self) -> &Self::Target { + &self.parent + } +} + +// Assign an interrupt to each priority level +pub fn app(analysis: analyze::Analysis, app: &App) -> Analysis { + let mut available_interrupt = app.args.dispatchers.clone(); + + // the set of priorities (each priority only once) + let priorities = app + .software_tasks + .values() + .map(|task| task.args.priority) + .collect::>(); + + // map from priorities to interrupts (holding name and attributes) + + let interrupts: BTreeMap = priorities + .iter() + .filter(|prio| **prio > 0) // 0 prio tasks are run in main + .copied() + .rev() + .map(|p| (p, available_interrupt.pop().expect("UNREACHABLE"))) + .collect(); + + Analysis { + parent: analysis, + interrupts, + } +} diff --git a/rtic/macros/src/bindings.rs b/rtic/macros/src/bindings.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rtic/macros/src/bindings.rs @@ -0,0 +1 @@ + diff --git a/rtic/macros/src/check.rs b/rtic/macros/src/check.rs new file mode 100644 index 0000000..a05c82e --- /dev/null +++ b/rtic/macros/src/check.rs @@ -0,0 +1,70 @@ +use std::collections::HashSet; + +use crate::syntax::ast::App; +use syn::parse; + +pub fn app(app: &App) -> parse::Result<()> { + // Check that external (device-specific) interrupts are not named after known (Cortex-M) + // exceptions + for name in app.args.dispatchers.keys() { + let name_s = name.to_string(); + + match &*name_s { + "NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault" + | "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => { + return Err(parse::Error::new( + name.span(), + "Cortex-M exceptions can't be used as `extern` interrupts", + )); + } + + _ => {} + } + } + + // Check that there are enough external interrupts to dispatch the software tasks and the timer + // queue handler + let mut first = None; + let priorities = app + .software_tasks + .iter() + .map(|(name, task)| { + first = Some(name); + task.args.priority + }) + .filter(|prio| *prio > 0) + .collect::>(); + + let need = priorities.len(); + let given = app.args.dispatchers.len(); + if need > given { + let s = { + format!( + "not enough interrupts to dispatch \ + all software tasks (need: {need}; given: {given})" + ) + }; + + // If not enough tasks and first still is None, may cause + // "custom attribute panicked" due to unwrap on None + return Err(parse::Error::new(first.unwrap().span(), s)); + } + + // Check that all exceptions are valid; only exceptions with configurable priorities are + // accepted + for (name, task) in &app.hardware_tasks { + let name_s = task.args.binds.to_string(); + match &*name_s { + "NonMaskableInt" | "HardFault" => { + return Err(parse::Error::new( + name.span(), + "only exceptions with configurable priority can be used as hardware tasks", + )); + } + + _ => {} + } + } + + Ok(()) +} diff --git a/rtic/macros/src/codegen.rs b/rtic/macros/src/codegen.rs new file mode 100644 index 0000000..24e98ce --- /dev/null +++ b/rtic/macros/src/codegen.rs @@ -0,0 +1,75 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::analyze::Analysis; +use crate::syntax::ast::App; + +mod assertions; +mod async_dispatchers; +mod hardware_tasks; +mod idle; +mod init; +mod local_resources; +mod local_resources_struct; +mod module; +mod post_init; +mod pre_init; +mod shared_resources; +mod shared_resources_struct; +mod software_tasks; +mod util; + +mod main; + +// TODO: organize codegen to actual parts of code +// so `main::codegen` generates ALL the code for `fn main`, +// `software_tasks::codegen` generates ALL the code for software tasks etc... + +#[allow(clippy::too_many_lines)] +pub fn app(app: &App, analysis: &Analysis) -> TokenStream2 { + // Generate the `main` function + let main = main::codegen(app, analysis); + let init_codegen = init::codegen(app, analysis); + let idle_codegen = idle::codegen(app, analysis); + let shared_resources_codegen = shared_resources::codegen(app, analysis); + let local_resources_codegen = local_resources::codegen(app, analysis); + let hardware_tasks_codegen = hardware_tasks::codegen(app, analysis); + let software_tasks_codegen = software_tasks::codegen(app, analysis); + let async_dispatchers_codegen = async_dispatchers::codegen(app, analysis); + + let user_imports = &app.user_imports; + let user_code = &app.user_code; + let name = &app.name; + let device = &app.args.device; + + let rt_err = util::rt_err_ident(); + + quote!( + /// The RTIC application module + pub mod #name { + /// Always include the device crate which contains the vector table + use #device as #rt_err; + + #(#user_imports)* + + #(#user_code)* + /// User code end + + #init_codegen + + #idle_codegen + + #hardware_tasks_codegen + + #software_tasks_codegen + + #shared_resources_codegen + + #local_resources_codegen + + #async_dispatchers_codegen + + #main + } + ) +} diff --git a/rtic/macros/src/codegen/assertions.rs b/rtic/macros/src/codegen/assertions.rs new file mode 100644 index 0000000..dd94aa6 --- /dev/null +++ b/rtic/macros/src/codegen/assertions.rs @@ -0,0 +1,53 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; + +/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + for ty in &analysis.send_types { + stmts.push(quote!(rtic::export::assert_send::<#ty>();)); + } + + for ty in &analysis.sync_types { + stmts.push(quote!(rtic::export::assert_sync::<#ty>();)); + } + + let device = &app.args.device; + let chunks_name = util::priority_mask_chunks_ident(); + let no_basepri_checks: Vec<_> = app + .hardware_tasks + .iter() + .filter_map(|(_, task)| { + if !util::is_exception(&task.args.binds) { + let interrupt_name = &task.args.binds; + Some(quote!( + if (#device::Interrupt::#interrupt_name as usize) >= (#chunks_name * 32) { + ::core::panic!("An interrupt out of range is used while in armv6 or armv8m.base"); + } + )) + } else { + None + } + }) + .collect(); + + let const_check = quote! { + const _CONST_CHECK: () = { + if !rtic::export::have_basepri() { + #(#no_basepri_checks)* + } else { + // TODO: Add armv7 checks here + } + }; + + let _ = _CONST_CHECK; + }; + + stmts.push(const_check); + + stmts +} diff --git a/rtic/macros/src/codegen/async_dispatchers.rs b/rtic/macros/src/codegen/async_dispatchers.rs new file mode 100644 index 0000000..a12ad32 --- /dev/null +++ b/rtic/macros/src/codegen/async_dispatchers.rs @@ -0,0 +1,89 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates task dispatchers +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut items = vec![]; + + let interrupts = &analysis.interrupts; + + // Generate executor definition and priority in global scope + for (name, _) in app.software_tasks.iter() { + let type_name = util::internal_task_ident(name, "F"); + let exec_name = util::internal_task_ident(name, "EXEC"); + + items.push(quote!( + #[allow(non_camel_case_types)] + type #type_name = impl core::future::Future; + #[allow(non_upper_case_globals)] + static #exec_name: rtic::export::executor::AsyncTaskExecutor<#type_name> = + rtic::export::executor::AsyncTaskExecutor::new(); + )); + } + + for (&level, channel) in &analysis.channels { + let mut stmts = vec![]; + + let dispatcher_name = if level > 0 { + util::suffixed(&interrupts.get(&level).expect("UNREACHABLE").0.to_string()) + } else { + util::zero_prio_dispatcher_ident() + }; + + let pend_interrupt = if level > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + + quote!(rtic::pend(#device::#enum_::#dispatcher_name);) + } else { + // For 0 priority tasks we don't need to pend anything + quote!() + }; + + for name in channel.tasks.iter() { + let exec_name = util::internal_task_ident(name, "EXEC"); + // TODO: Fix cfg + // let task = &app.software_tasks[name]; + // let cfgs = &task.cfgs; + + stmts.push(quote!( + #exec_name.poll(|| { + #exec_name.set_pending(); + #pend_interrupt + }); + )); + } + + if level > 0 { + let doc = format!("Interrupt handler to dispatch async tasks at priority {level}"); + let attribute = &interrupts.get(&level).expect("UNREACHABLE").1.attrs; + items.push(quote!( + #[allow(non_snake_case)] + #[doc = #doc] + #[no_mangle] + #(#attribute)* + unsafe fn #dispatcher_name() { + /// The priority of this interrupt handler + const PRIORITY: u8 = #level; + + rtic::export::run(PRIORITY, || { + #(#stmts)* + }); + } + )); + } else { + items.push(quote!( + #[allow(non_snake_case)] + unsafe fn #dispatcher_name() -> ! { + loop { + #(#stmts)* + } + } + )); + } + } + + quote!(#(#items)*) +} diff --git a/rtic/macros/src/codegen/hardware_tasks.rs b/rtic/macros/src/codegen/hardware_tasks.rs new file mode 100644 index 0000000..8a5a8f6 --- /dev/null +++ b/rtic/macros/src/codegen/hardware_tasks.rs @@ -0,0 +1,87 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s) +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + for (name, task) in &app.hardware_tasks { + let symbol = task.args.binds.clone(); + let priority = task.args.priority; + let cfgs = &task.cfgs; + let attrs = &task.attrs; + + mod_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + unsafe fn #symbol() { + const PRIORITY: u8 = #priority; + + rtic::export::run(PRIORITY, || { + #name( + #name::Context::new() + ) + }); + } + )); + + // `${task}Locals` + if !task.args.local_resources.is_empty() { + let (item, constructor) = + local_resources_struct::codegen(Context::HardwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + // `${task}Resources` + if !task.args.shared_resources.is_empty() { + let (item, constructor) = + shared_resources_struct::codegen(Context::HardwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + // Module generation... + + root.push(module::codegen(Context::HardwareTask(name), app, analysis)); + + // End module generation + + if !task.is_extern { + let attrs = &task.attrs; + let context = &task.context; + let stmts = &task.stmts; + user_tasks.push(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#context: #name::Context) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + } + + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) +} diff --git a/rtic/macros/src/codegen/idle.rs b/rtic/macros/src/codegen/idle.rs new file mode 100644 index 0000000..0c833ef --- /dev/null +++ b/rtic/macros/src/codegen/idle.rs @@ -0,0 +1,58 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates support code for `#[idle]` functions +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + if let Some(idle) = &app.idle { + let mut mod_app = vec![]; + let mut root_idle = vec![]; + + let name = &idle.name; + + if !idle.args.shared_resources.is_empty() { + let (item, constructor) = shared_resources_struct::codegen(Context::Idle, app); + + root_idle.push(item); + mod_app.push(constructor); + } + + if !idle.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen(Context::Idle, app); + + root_idle.push(item); + + mod_app.push(constructor); + } + + root_idle.push(module::codegen(Context::Idle, app, analysis)); + + let attrs = &idle.attrs; + let context = &idle.context; + let stmts = &idle.stmts; + let user_idle = Some(quote!( + #(#attrs)* + #[allow(non_snake_case)] + fn #name(#context: #name::Context) -> ! { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + + quote!( + #(#mod_app)* + + #(#root_idle)* + + #user_idle + ) + } else { + quote!() + } +} diff --git a/rtic/macros/src/codegen/init.rs b/rtic/macros/src/codegen/init.rs new file mode 100644 index 0000000..6e1059f --- /dev/null +++ b/rtic/macros/src/codegen/init.rs @@ -0,0 +1,95 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module}, + syntax::{ast::App, Context}, +}; + +/// Generates support code for `#[init]` functions +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let init = &app.init; + let name = &init.name; + + let mut root_init = vec![]; + + let context = &init.context; + let attrs = &init.attrs; + let stmts = &init.stmts; + let shared = &init.user_shared_struct; + let local = &init.user_local_struct; + + let shared_resources: Vec<_> = app + .shared_resources + .iter() + .map(|(k, v)| { + let ty = &v.ty; + let cfgs = &v.cfgs; + let docs = &v.docs; + quote!( + #(#cfgs)* + #(#docs)* + #k: #ty, + ) + }) + .collect(); + let local_resources: Vec<_> = app + .local_resources + .iter() + .map(|(k, v)| { + let ty = &v.ty; + let cfgs = &v.cfgs; + let docs = &v.docs; + quote!( + #(#cfgs)* + #(#docs)* + #k: #ty, + ) + }) + .collect(); + + root_init.push(quote! { + struct #shared { + #(#shared_resources)* + } + + struct #local { + #(#local_resources)* + } + }); + + // let locals_pat = locals_pat.iter(); + + let user_init_return = quote! {#shared, #local}; + + let user_init = quote!( + #(#attrs)* + #[inline(always)] + #[allow(non_snake_case)] + fn #name(#context: #name::Context) -> (#user_init_return) { + #(#stmts)* + } + ); + + let mut mod_app = None; + + // `${task}Locals` + if !init.args.local_resources.is_empty() { + let (item, constructor) = local_resources_struct::codegen(Context::Init, app); + + root_init.push(item); + + mod_app = Some(constructor); + } + + root_init.push(module::codegen(Context::Init, app, analysis)); + + quote!( + #mod_app + + #(#root_init)* + + #user_init + ) +} diff --git a/rtic/macros/src/codegen/local_resources.rs b/rtic/macros/src/codegen/local_resources.rs new file mode 100644 index 0000000..e6d1553 --- /dev/null +++ b/rtic/macros/src/codegen/local_resources.rs @@ -0,0 +1,65 @@ +use crate::syntax::ast::App; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates `local` variables and local resource proxies +/// +/// I.e. the `static` variables and theirs proxies. +pub fn codegen(app: &App, _analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + + // All local resources declared in the `#[local]' struct + for (name, res) in &app.local_resources { + let cfgs = &res.cfgs; + let ty = &res.ty; + let mangled_name = util::static_local_resource_ident(name); + + let attrs = &res.attrs; + + // late resources in `util::link_section_uninit` + // unless user specifies custom link section + let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { + None + } else { + Some(util::link_section_uninit()) + }; + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + #section + static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + )); + } + + // All declared `local = [NAME: TY = EXPR]` local resources + for (task_name, resource_name, task_local) in app.declared_local_resources() { + let cfgs = &task_local.cfgs; + let ty = &task_local.ty; + let expr = &task_local.expr; + let attrs = &task_local.attrs; + + let mangled_name = util::declared_static_local_resource_ident(resource_name, task_name); + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + static #mangled_name: rtic::RacyCell<#ty> = rtic::RacyCell::new(#expr); + )); + } + + quote!(#(#mod_app)*) +} diff --git a/rtic/macros/src/codegen/local_resources_struct.rs b/rtic/macros/src/codegen/local_resources_struct.rs new file mode 100644 index 0000000..100c3eb --- /dev/null +++ b/rtic/macros/src/codegen/local_resources_struct.rs @@ -0,0 +1,102 @@ +use crate::syntax::{ + ast::{App, TaskLocal}, + Context, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::codegen::util; + +/// Generates local resources structs +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { + let resources = match ctxt { + Context::Init => &app.init.args.local_resources, + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .local_resources + } + Context::HardwareTask(name) => &app.hardware_tasks[name].args.local_resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.local_resources, + }; + + let task_name = util::get_task_name(ctxt, app); + + let mut fields = vec![]; + let mut values = vec![]; + + for (name, task_local) in resources { + let (cfgs, ty, is_declared) = match task_local { + TaskLocal::External => { + let r = app.local_resources.get(name).expect("UNREACHABLE"); + (&r.cfgs, &r.ty, false) + } + TaskLocal::Declared(r) => (&r.cfgs, &r.ty, true), + }; + + let lt = if ctxt.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + let mangled_name = if matches!(task_local, TaskLocal::External) { + util::static_local_resource_ident(name) + } else { + util::declared_static_local_resource_ident(name, &task_name) + }; + + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &#lt mut #ty + )); + + let expr = if is_declared { + // If the local resources is already initialized, we only need to access its value and + // not go through an `MaybeUninit` + quote!(&mut *#mangled_name.get_mut()) + } else { + quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: ::core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__rtic_internal_marker: ::core::marker::PhantomData)); + + let doc = format!("Local resources `{}` has access to", ctxt.ident(app)); + let ident = util::local_resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + #[doc = #doc] + pub struct #ident<'a> { + #(#fields,)* + } + ); + + let constructor = quote!( + impl<'a> #ident<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new() -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/rtic/macros/src/codegen/main.rs b/rtic/macros/src/codegen/main.rs new file mode 100644 index 0000000..2775d25 --- /dev/null +++ b/rtic/macros/src/codegen/main.rs @@ -0,0 +1,52 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use super::{assertions, post_init, pre_init}; + +/// Generates code for `fn main` +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let assertion_stmts = assertions::codegen(app, analysis); + + let pre_init_stmts = pre_init::codegen(app, analysis); + + let post_init_stmts = post_init::codegen(app, analysis); + + let call_idle = if let Some(idle) = &app.idle { + let name = &idle.name; + quote!(#name(#name::Context::new())) + } else if analysis.channels.get(&0).is_some() { + let dispatcher = util::zero_prio_dispatcher_ident(); + quote!(#dispatcher();) + } else { + quote!(loop { + rtic::export::nop() + }) + }; + + let main = util::suffixed("main"); + let init_name = &app.init.name; + quote!( + #[doc(hidden)] + #[no_mangle] + unsafe extern "C" fn #main() -> ! { + #(#assertion_stmts)* + + #(#pre_init_stmts)* + + #[inline(never)] + fn __rtic_init_resources(f: F) where F: FnOnce() { + f(); + } + + // Wrap late_init_stmts in a function to ensure that stack space is reclaimed. + __rtic_init_resources(||{ + let (shared_resources, local_resources) = #init_name(#init_name::Context::new(core.into())); + + #(#post_init_stmts)* + }); + + #call_idle + } + ) +} diff --git a/rtic/macros/src/codegen/module.rs b/rtic/macros/src/codegen/module.rs new file mode 100644 index 0000000..4725b9a --- /dev/null +++ b/rtic/macros/src/codegen/module.rs @@ -0,0 +1,194 @@ +use crate::syntax::{ast::App, Context}; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +#[allow(clippy::too_many_lines)] +pub fn codegen(ctxt: Context, app: &App, analysis: &Analysis) -> TokenStream2 { + let mut items = vec![]; + let mut module_items = vec![]; + let mut fields = vec![]; + let mut values = vec![]; + // Used to copy task cfgs to the whole module + let mut task_cfgs = vec![]; + + let name = ctxt.ident(app); + + match ctxt { + Context::Init => { + fields.push(quote!( + /// Core (Cortex-M) peripherals + pub core: rtic::export::Peripherals + )); + + if app.args.peripherals { + let device = &app.args.device; + + fields.push(quote!( + /// Device peripherals + pub device: #device::Peripherals + )); + + values.push(quote!(device: #device::Peripherals::steal())); + } + + fields.push(quote!( + /// Critical section token for init + pub cs: rtic::export::CriticalSection<'a> + )); + + values.push(quote!(cs: rtic::export::CriticalSection::new())); + + values.push(quote!(core)); + } + + Context::Idle | Context::HardwareTask(_) | Context::SoftwareTask(_) => {} + } + + if ctxt.has_local_resources(app) { + let ident = util::local_resources_ident(ctxt, app); + + module_items.push(quote!( + #[doc(inline)] + pub use super::#ident as LocalResources; + )); + + fields.push(quote!( + /// Local Resources this task has access to + pub local: #name::LocalResources<'a> + )); + + values.push(quote!(local: #name::LocalResources::new())); + } + + if ctxt.has_shared_resources(app) { + let ident = util::shared_resources_ident(ctxt, app); + + module_items.push(quote!( + #[doc(inline)] + pub use super::#ident as SharedResources; + )); + + fields.push(quote!( + /// Shared Resources this task has access to + pub shared: #name::SharedResources<'a> + )); + + values.push(quote!(shared: #name::SharedResources::new())); + } + + let doc = match ctxt { + Context::Idle => "Idle loop", + Context::Init => "Initialization function", + Context::HardwareTask(_) => "Hardware task", + Context::SoftwareTask(_) => "Software task", + }; + + let v = Vec::new(); + let cfgs = match ctxt { + Context::HardwareTask(t) => &app.hardware_tasks[t].cfgs, + Context::SoftwareTask(t) => &app.software_tasks[t].cfgs, + _ => &v, + }; + + let core = if ctxt.is_init() { + Some(quote!(core: rtic::export::Peripherals,)) + } else { + None + }; + + let internal_context_name = util::internal_task_ident(name, "Context"); + let exec_name = util::internal_task_ident(name, "EXEC"); + + items.push(quote!( + #(#cfgs)* + /// Execution context + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + pub struct #internal_context_name<'a> { + #[doc(hidden)] + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, + #(#fields,)* + } + + #(#cfgs)* + impl<'a> #internal_context_name<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new(#core) -> Self { + #internal_context_name { + __rtic_internal_p: ::core::marker::PhantomData, + #(#values,)* + } + } + } + )); + + module_items.push(quote!( + #(#cfgs)* + #[doc(inline)] + pub use super::#internal_context_name as Context; + )); + + if let Context::SoftwareTask(..) = ctxt { + let spawnee = &app.software_tasks[name]; + let priority = spawnee.args.priority; + let cfgs = &spawnee.cfgs; + // Store a copy of the task cfgs + task_cfgs = cfgs.clone(); + + let pend_interrupt = if priority > 0 { + let device = &app.args.device; + let enum_ = util::interrupt_ident(); + let interrupt = &analysis.interrupts.get(&priority).expect("UREACHABLE").0; + quote!(rtic::pend(#device::#enum_::#interrupt);) + } else { + quote!() + }; + + let internal_spawn_ident = util::internal_task_ident(name, "spawn"); + let (input_args, input_tupled, input_untupled, input_ty) = + util::regroup_inputs(&spawnee.inputs); + + // Spawn caller + items.push(quote!( + #(#cfgs)* + /// Spawns the task directly + #[allow(non_snake_case)] + #[doc(hidden)] + pub fn #internal_spawn_ident(#(#input_args,)*) -> Result<(), #input_ty> { + + if #exec_name.spawn(|| #name(unsafe { #name::Context::new() } #(,#input_untupled)*) ) { + + #pend_interrupt + + Ok(()) + } else { + Err(#input_tupled) + } + + } + )); + + module_items.push(quote!( + #(#cfgs)* + #[doc(inline)] + pub use super::#internal_spawn_ident as spawn; + )); + } + + if items.is_empty() { + quote!() + } else { + quote!( + #(#items)* + + #[allow(non_snake_case)] + #(#task_cfgs)* + #[doc = #doc] + pub mod #name { + #(#module_items)* + } + ) + } +} diff --git a/rtic/macros/src/codegen/post_init.rs b/rtic/macros/src/codegen/post_init.rs new file mode 100644 index 0000000..c4e5383 --- /dev/null +++ b/rtic/macros/src/codegen/post_init.rs @@ -0,0 +1,47 @@ +use crate::{analyze::Analysis, codegen::util, syntax::ast::App}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates code that runs after `#[init]` returns +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + // Initialize shared resources + for (name, res) in &app.shared_resources { + let mangled_name = util::static_shared_resource_ident(name); + // If it's live + let cfgs = res.cfgs.clone(); + if analysis.shared_resources.get(name).is_some() { + stmts.push(quote!( + // We include the cfgs + #(#cfgs)* + // Resource is a RacyCell> + // - `get_mut` to obtain a raw pointer to `MaybeUninit` + // - `write` the defined value for the late resource T + #mangled_name.get_mut().write(core::mem::MaybeUninit::new(shared_resources.#name)); + )); + } + } + + // Initialize local resources + for (name, res) in &app.local_resources { + let mangled_name = util::static_local_resource_ident(name); + // If it's live + let cfgs = res.cfgs.clone(); + if analysis.local_resources.get(name).is_some() { + stmts.push(quote!( + // We include the cfgs + #(#cfgs)* + // Resource is a RacyCell> + // - `get_mut` to obtain a raw pointer to `MaybeUninit` + // - `write` the defined value for the late resource T + #mangled_name.get_mut().write(core::mem::MaybeUninit::new(local_resources.#name)); + )); + } + } + + // Enable the interrupts -- this completes the `init`-ialization phase + stmts.push(quote!(rtic::export::interrupt::enable();)); + + stmts +} diff --git a/rtic/macros/src/codegen/pre_init.rs b/rtic/macros/src/codegen/pre_init.rs new file mode 100644 index 0000000..28ba29c --- /dev/null +++ b/rtic/macros/src/codegen/pre_init.rs @@ -0,0 +1,85 @@ +use crate::syntax::ast::App; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::{analyze::Analysis, codegen::util}; + +/// Generates code that runs before `#[init]` +pub fn codegen(app: &App, analysis: &Analysis) -> Vec { + let mut stmts = vec![]; + + let rt_err = util::rt_err_ident(); + + // Disable interrupts -- `init` must run with interrupts disabled + stmts.push(quote!(rtic::export::interrupt::disable();)); + + stmts.push(quote!( + // To set the variable in cortex_m so the peripherals cannot be taken multiple times + let mut core: rtic::export::Peripherals = rtic::export::Peripherals::steal().into(); + )); + + let device = &app.args.device; + let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS); + + // check that all dispatchers exists in the `Interrupt` enumeration regardless of whether + // they are used or not + let interrupt = util::interrupt_ident(); + for name in app.args.dispatchers.keys() { + stmts.push(quote!(let _ = #rt_err::#interrupt::#name;)); + } + + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + // Unmask interrupts and set their priorities + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + // We do exceptions in another pass + None + } else { + Some((&task.args.priority, &task.args.binds)) + } + })) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!( + core.NVIC.set_priority( + #rt_err::#interrupt::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + ); + )); + + // NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended + // interrupt is implementation defined + stmts.push(quote!(rtic::export::NVIC::unmask(#rt_err::#interrupt::#name);)); + } + + // Set exception priorities + for (name, priority) in app.hardware_tasks.values().filter_map(|task| { + if util::is_exception(&task.args.binds) { + Some((&task.args.binds, task.args.priority)) + } else { + None + } + }) { + let es = format!( + "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" + ); + // Compile time assert that this priority is supported by the device + stmts.push(quote!( + const _: () = if (1 << #nvic_prio_bits) < #priority as usize { ::core::panic!(#es); }; + )); + + stmts.push(quote!(core.SCB.set_priority( + rtic::export::SystemHandler::#name, + rtic::export::logical2hw(#priority, #nvic_prio_bits), + );)); + } + + stmts +} diff --git a/rtic/macros/src/codegen/shared_resources.rs b/rtic/macros/src/codegen/shared_resources.rs new file mode 100644 index 0000000..19fd13f --- /dev/null +++ b/rtic/macros/src/codegen/shared_resources.rs @@ -0,0 +1,183 @@ +use crate::syntax::{analyze::Ownership, ast::App}; +use crate::{analyze::Analysis, codegen::util}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashMap; + +/// Generates `static` variables and shared resource proxies +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut mod_resources = vec![]; + + for (name, res) in &app.shared_resources { + let cfgs = &res.cfgs; + let ty = &res.ty; + let mangled_name = &util::static_shared_resource_ident(name); + + let attrs = &res.attrs; + + // late resources in `util::link_section_uninit` + // unless user specifies custom link section + let section = if attrs.iter().any(|attr| attr.path.is_ident("link_section")) { + None + } else { + Some(util::link_section_uninit()) + }; + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + mod_app.push(quote!( + #[allow(non_camel_case_types)] + #[allow(non_upper_case_globals)] + // #[doc = #doc] + #[doc(hidden)] + #(#attrs)* + #(#cfgs)* + #section + static #mangled_name: rtic::RacyCell> = rtic::RacyCell::new(core::mem::MaybeUninit::uninit()); + )); + + // For future use + // let doc = format!(" RTIC internal: {}:{}", file!(), line!()); + + let shared_name = util::need_to_lock_ident(name); + + if !res.properties.lock_free { + mod_resources.push(quote!( + // #[doc = #doc] + #[doc(hidden)] + #[allow(non_camel_case_types)] + #(#cfgs)* + pub struct #shared_name<'a> { + __rtic_internal_p: ::core::marker::PhantomData<&'a ()>, + } + + #(#cfgs)* + impl<'a> #shared_name<'a> { + #[inline(always)] + pub unsafe fn new() -> Self { + #shared_name { __rtic_internal_p: ::core::marker::PhantomData } + } + } + )); + + let ptr = quote!( + #(#cfgs)* + #mangled_name.get_mut() as *mut _ + ); + + let ceiling = match analysis.ownerships.get(name) { + Some(Ownership::Owned { priority } | Ownership::CoOwned { priority }) => *priority, + Some(Ownership::Contended { ceiling }) => *ceiling, + None => 0, + }; + + // For future use + // let doc = format!(" RTIC internal ({} resource): {}:{}", doc, file!(), line!()); + + mod_app.push(util::impl_mutex( + app, + cfgs, + true, + &shared_name, + "e!(#ty), + ceiling, + &ptr, + )); + } + } + + let mod_resources = if mod_resources.is_empty() { + quote!() + } else { + quote!(mod shared_resources { + #(#mod_resources)* + }) + }; + + // Computing mapping of used interrupts to masks + let interrupt_ids = analysis.interrupts.iter().map(|(p, (id, _))| (p, id)); + + let mut prio_to_masks = HashMap::new(); + let device = &app.args.device; + let mut uses_exceptions_with_resources = false; + + let mut mask_ids = Vec::new(); + + for (&priority, name) in interrupt_ids.chain(app.hardware_tasks.values().flat_map(|task| { + if !util::is_exception(&task.args.binds) { + Some((&task.args.priority, &task.args.binds)) + } else { + // If any resource to the exception uses non-lock-free or non-local resources this is + // not allwed on thumbv6. + uses_exceptions_with_resources = uses_exceptions_with_resources + || task + .args + .shared_resources + .iter() + .map(|(ident, access)| { + if access.is_exclusive() { + if let Some(r) = app.shared_resources.get(ident) { + !r.properties.lock_free + } else { + false + } + } else { + false + } + }) + .any(|v| v); + + None + } + })) { + let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); + v.push(quote!(#device::Interrupt::#name as u32)); + mask_ids.push(quote!(#device::Interrupt::#name as u32)); + } + + // Call rtic::export::create_mask([Mask; N]), where the array is the list of shifts + + let mut mask_arr = Vec::new(); + // NOTE: 0..3 assumes max 4 priority levels according to M0, M23 spec + for i in 0..3 { + let v = if let Some(v) = prio_to_masks.get(&i) { + v.clone() + } else { + Vec::new() + }; + + mask_arr.push(quote!( + rtic::export::create_mask([#(#v),*]) + )); + } + + // Generate a constant for the number of chunks needed by Mask. + let chunks_name = util::priority_mask_chunks_ident(); + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const #chunks_name: usize = rtic::export::compute_mask_chunks([#(#mask_ids),*]); + )); + + let masks_name = util::priority_masks_ident(); + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const #masks_name: [rtic::export::Mask<#chunks_name>; 3] = [#(#mask_arr),*]; + )); + + if uses_exceptions_with_resources { + mod_app.push(quote!( + #[doc(hidden)] + #[allow(non_upper_case_globals)] + const __rtic_internal_V6_ERROR: () = rtic::export::no_basepri_panic(); + )); + } + + quote!( + #(#mod_app)* + + #mod_resources + ) +} diff --git a/rtic/macros/src/codegen/shared_resources_struct.rs b/rtic/macros/src/codegen/shared_resources_struct.rs new file mode 100644 index 0000000..fa6f0fc --- /dev/null +++ b/rtic/macros/src/codegen/shared_resources_struct.rs @@ -0,0 +1,119 @@ +use crate::syntax::{ast::App, Context}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +use crate::codegen::util; + +/// Generate shared resources structs +pub fn codegen(ctxt: Context, app: &App) -> (TokenStream2, TokenStream2) { + let resources = match ctxt { + Context::Init => unreachable!("Tried to generate shared resources struct for init"), + Context::Idle => { + &app.idle + .as_ref() + .expect("RTIC-ICE: unable to get idle name") + .args + .shared_resources + } + Context::HardwareTask(name) => &app.hardware_tasks[name].args.shared_resources, + Context::SoftwareTask(name) => &app.software_tasks[name].args.shared_resources, + }; + + let mut fields = vec![]; + let mut values = vec![]; + + for (name, access) in resources { + let res = app.shared_resources.get(name).expect("UNREACHABLE"); + + let cfgs = &res.cfgs; + + // access hold if the resource is [x] (exclusive) or [&x] (shared) + let mut_ = if access.is_exclusive() { + Some(quote!(mut)) + } else { + None + }; + let ty = &res.ty; + let mangled_name = util::static_shared_resource_ident(name); + let shared_name = util::need_to_lock_ident(name); + + if res.properties.lock_free { + // Lock free resources of `idle` and `init` get 'static lifetime + let lt = if ctxt.runs_once() { + quote!('static) + } else { + quote!('a) + }; + + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &#lt #mut_ #ty + )); + } else if access.is_shared() { + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: &'a #ty + )); + } else { + fields.push(quote!( + #(#cfgs)* + #[allow(missing_docs)] + pub #name: shared_resources::#shared_name<'a> + )); + + values.push(quote!( + #(#cfgs)* + #name: shared_resources::#shared_name::new() + + )); + + // continue as the value has been filled, + continue; + } + + let expr = if access.is_exclusive() { + quote!(&mut *(&mut *#mangled_name.get_mut()).as_mut_ptr()) + } else { + quote!(&*(&*#mangled_name.get()).as_ptr()) + }; + + values.push(quote!( + #(#cfgs)* + #name: #expr + )); + } + + fields.push(quote!( + #[doc(hidden)] + pub __rtic_internal_marker: core::marker::PhantomData<&'a ()> + )); + + values.push(quote!(__rtic_internal_marker: core::marker::PhantomData)); + + let doc = format!("Shared resources `{}` has access to", ctxt.ident(app)); + let ident = util::shared_resources_ident(ctxt, app); + let item = quote!( + #[allow(non_snake_case)] + #[allow(non_camel_case_types)] + #[doc = #doc] + pub struct #ident<'a> { + #(#fields,)* + } + ); + + let constructor = quote!( + impl<'a> #ident<'a> { + #[inline(always)] + #[allow(missing_docs)] + pub unsafe fn new() -> Self { + #ident { + #(#values,)* + } + } + } + ); + + (item, constructor) +} diff --git a/rtic/macros/src/codegen/software_tasks.rs b/rtic/macros/src/codegen/software_tasks.rs new file mode 100644 index 0000000..34fc851 --- /dev/null +++ b/rtic/macros/src/codegen/software_tasks.rs @@ -0,0 +1,64 @@ +use crate::syntax::{ast::App, Context}; +use crate::{ + analyze::Analysis, + codegen::{local_resources_struct, module, shared_resources_struct}, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { + let mut mod_app = vec![]; + let mut root = vec![]; + let mut user_tasks = vec![]; + + // Any task + for (name, task) in app.software_tasks.iter() { + if !task.args.local_resources.is_empty() { + let (item, constructor) = + local_resources_struct::codegen(Context::SoftwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + if !task.args.shared_resources.is_empty() { + let (item, constructor) = + shared_resources_struct::codegen(Context::SoftwareTask(name), app); + + root.push(item); + + mod_app.push(constructor); + } + + if !&task.is_extern { + let context = &task.context; + let attrs = &task.attrs; + let cfgs = &task.cfgs; + let stmts = &task.stmts; + let inputs = &task.inputs; + + user_tasks.push(quote!( + #(#attrs)* + #(#cfgs)* + #[allow(non_snake_case)] + async fn #name<'a>(#context: #name::Context<'a> #(,#inputs)*) { + use rtic::Mutex as _; + use rtic::mutex::prelude::*; + + #(#stmts)* + } + )); + } + + root.push(module::codegen(Context::SoftwareTask(name), app, analysis)); + } + + quote!( + #(#mod_app)* + + #(#root)* + + #(#user_tasks)* + ) +} diff --git a/rtic/macros/src/codegen/util.rs b/rtic/macros/src/codegen/util.rs new file mode 100644 index 0000000..e121487 --- /dev/null +++ b/rtic/macros/src/codegen/util.rs @@ -0,0 +1,238 @@ +use crate::syntax::{ast::App, Context}; +use core::sync::atomic::{AtomicUsize, Ordering}; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{Attribute, Ident, PatType}; + +const RTIC_INTERNAL: &str = "__rtic_internal"; + +/// Generates a `Mutex` implementation +pub fn impl_mutex( + app: &App, + cfgs: &[Attribute], + resources_prefix: bool, + name: &Ident, + ty: &TokenStream2, + ceiling: u8, + ptr: &TokenStream2, +) -> TokenStream2 { + let path = if resources_prefix { + quote!(shared_resources::#name) + } else { + quote!(#name) + }; + + let device = &app.args.device; + let masks_name = priority_masks_ident(); + quote!( + #(#cfgs)* + impl<'a> rtic::Mutex for #path<'a> { + type T = #ty; + + #[inline(always)] + fn lock(&mut self, f: impl FnOnce(&mut #ty) -> RTIC_INTERNAL_R) -> RTIC_INTERNAL_R { + /// Priority ceiling + const CEILING: u8 = #ceiling; + + unsafe { + rtic::export::lock( + #ptr, + CEILING, + #device::NVIC_PRIO_BITS, + &#masks_name, + f, + ) + } + } + } + ) +} + +pub fn interrupt_ident() -> Ident { + let span = Span::call_site(); + Ident::new("interrupt", span) +} + +/// Whether `name` is an exception with configurable priority +pub fn is_exception(name: &Ident) -> bool { + let s = name.to_string(); + + matches!( + &*s, + "MemoryManagement" + | "BusFault" + | "UsageFault" + | "SecureFault" + | "SVCall" + | "DebugMonitor" + | "PendSV" + | "SysTick" + ) +} + +/// Mark a name as internal +pub fn mark_internal_name(name: &str) -> Ident { + Ident::new(&format!("{RTIC_INTERNAL}_{name}"), Span::call_site()) +} + +/// Generate an internal identifier for tasks +pub fn internal_task_ident(task: &Ident, ident_name: &str) -> Ident { + mark_internal_name(&format!("{task}_{ident_name}")) +} + +fn link_section_index() -> usize { + static INDEX: AtomicUsize = AtomicUsize::new(0); + + INDEX.fetch_add(1, Ordering::Relaxed) +} + +/// Add `link_section` attribute +pub fn link_section_uninit() -> TokenStream2 { + let section = format!(".uninit.rtic{}", link_section_index()); + + quote!(#[link_section = #section]) +} + +/// Regroups the inputs of a task +/// +/// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`] +pub fn regroup_inputs( + inputs: &[PatType], +) -> ( + // args e.g. &[`_0`], &[`_0: i32`, `_1: i64`] + Vec, + // tupled e.g. `_0`, `(_0, _1)` + TokenStream2, + // untupled e.g. &[`_0`], &[`_0`, `_1`] + Vec, + // ty e.g. `Foo`, `(i32, i64)` + TokenStream2, +) { + if inputs.len() == 1 { + let ty = &inputs[0].ty; + + ( + vec![quote!(_0: #ty)], + quote!(_0), + vec![quote!(_0)], + quote!(#ty), + ) + } else { + let mut args = vec![]; + let mut pats = vec![]; + let mut tys = vec![]; + + for (i, input) in inputs.iter().enumerate() { + let i = Ident::new(&format!("_{}", i), Span::call_site()); + let ty = &input.ty; + + args.push(quote!(#i: #ty)); + + pats.push(quote!(#i)); + + tys.push(quote!(#ty)); + } + + let tupled = { + let pats = pats.clone(); + quote!((#(#pats,)*)) + }; + let ty = quote!((#(#tys,)*)); + (args, tupled, pats, ty) + } +} + +/// Get the ident for the name of the task +pub fn get_task_name(ctxt: Context, app: &App) -> Ident { + let s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + Ident::new(&s, Span::call_site()) +} + +/// Generates a pre-reexport identifier for the "shared resources" struct +pub fn shared_resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("SharedResources"); + + mark_internal_name(&s) +} + +/// Generates a pre-reexport identifier for the "local resources" struct +pub fn local_resources_ident(ctxt: Context, app: &App) -> Ident { + let mut s = match ctxt { + Context::Init => app.init.name.to_string(), + Context::Idle => app + .idle + .as_ref() + .expect("RTIC-ICE: unable to find idle name") + .name + .to_string(), + Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(), + }; + + s.push_str("LocalResources"); + + mark_internal_name(&s) +} + +/// Suffixed identifier +pub fn suffixed(name: &str) -> Ident { + let span = Span::call_site(); + Ident::new(name, span) +} + +pub fn static_shared_resource_ident(name: &Ident) -> Ident { + mark_internal_name(&format!("shared_resource_{name}")) +} + +/// Generates an Ident for the number of 32 bit chunks used for Mask storage. +pub fn priority_mask_chunks_ident() -> Ident { + mark_internal_name("MASK_CHUNKS") +} + +pub fn priority_masks_ident() -> Ident { + mark_internal_name("MASKS") +} + +pub fn static_local_resource_ident(name: &Ident) -> Ident { + mark_internal_name(&format!("local_resource_{name}")) +} + +pub fn declared_static_local_resource_ident(name: &Ident, task_name: &Ident) -> Ident { + mark_internal_name(&format!("local_{task_name}_{name}")) +} + +pub fn need_to_lock_ident(name: &Ident) -> Ident { + Ident::new(&format!("{name}_that_needs_to_be_locked"), name.span()) +} + +pub fn zero_prio_dispatcher_ident() -> Ident { + Ident::new("__rtic_internal_async_0_prio_dispatcher", Span::call_site()) +} + +/// The name to get better RT flag errors +pub fn rt_err_ident() -> Ident { + Ident::new( + "you_must_enable_the_rt_feature_for_the_pac_in_your_cargo_toml", + Span::call_site(), + ) +} diff --git a/rtic/macros/src/lib.rs b/rtic/macros/src/lib.rs new file mode 100644 index 0000000..a8422d0 --- /dev/null +++ b/rtic/macros/src/lib.rs @@ -0,0 +1,92 @@ +#![doc( + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/rtic/master/book/en/src/RTIC.svg" +)] + +//deny_warnings_placeholder_for_ci + +use proc_macro::TokenStream; +use std::{env, fs, path::Path}; + +mod analyze; +mod bindings; +mod check; +mod codegen; +mod syntax; + +// Used for mocking the API in testing +#[doc(hidden)] +#[proc_macro_attribute] +pub fn mock_app(args: TokenStream, input: TokenStream) -> TokenStream { + if let Err(e) = syntax::parse(args, input) { + e.to_compile_error().into() + } else { + "fn main() {}".parse().unwrap() + } +} + +/// Attribute used to declare a RTIC application +/// +/// For user documentation see the [RTIC book](https://rtic.rs) +/// +/// # Panics +/// +/// Should never panic, cargo feeds a path which is later converted to a string +#[proc_macro_attribute] +pub fn app(args: TokenStream, input: TokenStream) -> TokenStream { + let (app, analysis) = match syntax::parse(args, input) { + Err(e) => return e.to_compile_error().into(), + Ok(x) => x, + }; + + if let Err(e) = check::app(&app) { + return e.to_compile_error().into(); + } + + let analysis = analyze::app(analysis, &app); + + let ts = codegen::app(&app, &analysis); + + // Default output path: /target/ + let mut out_dir = Path::new("target"); + + // Get output directory from Cargo environment + // TODO don't want to break builds if OUT_DIR is not set, is this ever the case? + let out_str = env::var("OUT_DIR").unwrap_or_else(|_| "".to_string()); + + if !out_dir.exists() { + // Set out_dir to OUT_DIR + out_dir = Path::new(&out_str); + + // Default build path, annotated below: + // $(pwd)/target/thumbv7em-none-eabihf/debug/build/cortex-m-rtic-/out/ + // ///debug/build/cortex-m-rtic-/out/ + // + // traverse up to first occurrence of TARGET, approximated with starts_with("thumbv") + // and use the parent() of this path + // + // If no "target" directory is found, / is used + for path in out_dir.ancestors() { + if let Some(dir) = path.components().last() { + let dir = dir.as_os_str().to_str().unwrap(); + + if dir.starts_with("thumbv") || dir.starts_with("riscv") { + if let Some(out) = path.parent() { + out_dir = out; + break; + } + // If no parent, just use it + out_dir = path; + break; + } + } + } + } + + // Try to write the expanded code to disk + if let Some(out_str) = out_dir.to_str() { + fs::write(format!("{out_str}/rtic-expansion.rs"), ts.to_string()).ok(); + } + + ts.into() +} diff --git a/rtic/macros/src/syntax.rs b/rtic/macros/src/syntax.rs new file mode 100644 index 0000000..d6f5a47 --- /dev/null +++ b/rtic/macros/src/syntax.rs @@ -0,0 +1,121 @@ +#[allow(unused_extern_crates)] +extern crate proc_macro; + +use proc_macro::TokenStream; + +use indexmap::{IndexMap, IndexSet}; +use proc_macro2::TokenStream as TokenStream2; +use syn::Ident; + +use crate::syntax::ast::App; + +mod accessors; +pub mod analyze; +pub mod ast; +mod check; +mod parse; + +/// An ordered map keyed by identifier +pub type Map = IndexMap; + +/// An order set +pub type Set = IndexSet; + +/// Execution context +#[derive(Clone, Copy)] +pub enum Context<'a> { + /// The `idle` context + Idle, + + /// The `init`-ialization function + Init, + + /// A async software task + SoftwareTask(&'a Ident), + + /// A hardware task + HardwareTask(&'a Ident), +} + +impl<'a> Context<'a> { + /// The identifier of this context + pub fn ident(&self, app: &'a App) -> &'a Ident { + match self { + Context::HardwareTask(ident) => ident, + Context::Idle => &app.idle.as_ref().unwrap().name, + Context::Init => &app.init.name, + Context::SoftwareTask(ident) => ident, + } + } + + /// Is this the `idle` context? + pub fn is_idle(&self) -> bool { + matches!(self, Context::Idle) + } + + /// Is this the `init`-ialization context? + pub fn is_init(&self) -> bool { + matches!(self, Context::Init) + } + + /// Whether this context runs only once + pub fn runs_once(&self) -> bool { + self.is_init() || self.is_idle() + } + + /// Whether this context has shared resources + pub fn has_shared_resources(&self, app: &App) -> bool { + match *self { + Context::HardwareTask(name) => { + !app.hardware_tasks[name].args.shared_resources.is_empty() + } + Context::Idle => !app.idle.as_ref().unwrap().args.shared_resources.is_empty(), + Context::Init => false, + Context::SoftwareTask(name) => { + !app.software_tasks[name].args.shared_resources.is_empty() + } + } + } + + /// Whether this context has local resources + pub fn has_local_resources(&self, app: &App) -> bool { + match *self { + Context::HardwareTask(name) => { + !app.hardware_tasks[name].args.local_resources.is_empty() + } + Context::Idle => !app.idle.as_ref().unwrap().args.local_resources.is_empty(), + Context::Init => !app.init.args.local_resources.is_empty(), + Context::SoftwareTask(name) => { + !app.software_tasks[name].args.local_resources.is_empty() + } + } + } +} + +/// Parses the input of the `#[app]` attribute +pub fn parse( + args: TokenStream, + input: TokenStream, +) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { + parse2(args.into(), input.into()) +} + +/// `proc_macro2::TokenStream` version of `parse` +pub fn parse2( + args: TokenStream2, + input: TokenStream2, +) -> Result<(ast::App, analyze::Analysis), syn::parse::Error> { + let app = parse::app(args, input)?; + check::app(&app)?; + + match analyze::app(&app) { + Err(e) => Err(e), + // If no errors, return the app and analysis results + Ok(analysis) => Ok((app, analysis)), + } +} + +enum Either { + Left(A), + Right(B), +} diff --git a/rtic/macros/src/syntax/.github/bors.toml b/rtic/macros/src/syntax/.github/bors.toml new file mode 100644 index 0000000..aee6042 --- /dev/null +++ b/rtic/macros/src/syntax/.github/bors.toml @@ -0,0 +1,3 @@ +block_labels = ["S-blocked"] +delete_merged_branches = true +status = ["ci"] diff --git a/rtic/macros/src/syntax/.github/workflows/build.yml b/rtic/macros/src/syntax/.github/workflows/build.yml new file mode 100644 index 0000000..29971b1 --- /dev/null +++ b/rtic/macros/src/syntax/.github/workflows/build.yml @@ -0,0 +1,213 @@ +name: Build +on: + pull_request: + push: + branches: + - master + - staging + - trying + - bors/staging + - bors/trying + +env: + CARGO_TERM_COLOR: always + +jobs: + # Run cargo fmt --check + style: + name: style + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: cargo fmt --check + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + # Compilation check + check: + name: check + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: cargo check + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: check + args: --target=${{ matrix.target }} + + # Clippy + clippy: + name: Cargo clippy + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + override: true + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: cargo clippy + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: clippy + + # Verify all examples + testexamples: + name: testexamples + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --examples + + # Run test suite for UI + testui: + name: testui + runs-on: ubuntu-20.04 + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + toolchain: + - stable + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust ${{ matrix.toolchain }} with target (${{ matrix.target }}) + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --test ui + + # Run test suite + test: + name: test + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: thumbv7m-none-eabi + override: true + + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1 + + - name: Fail on warnings + run: sed -i 's,//deny_warnings_placeholder_for_ci,#![deny(warnings)],' src/lib.rs + + - uses: actions-rs/cargo@v1 + with: + use-cross: false + command: test + args: --lib + + # Refs: https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 + # + # ALL THE PREVIOUS JOBS NEEDS TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + + ci-success: + name: ci + if: github.event_name == 'push' && success() + needs: + - style + - check + - clippy + - testexamples + - test + - testui + runs-on: ubuntu-20.04 + steps: + - name: Mark the job as a success + run: exit 0 diff --git a/rtic/macros/src/syntax/.github/workflows/changelog.yml b/rtic/macros/src/syntax/.github/workflows/changelog.yml new file mode 100644 index 0000000..ccf6eb9 --- /dev/null +++ b/rtic/macros/src/syntax/.github/workflows/changelog.yml @@ -0,0 +1,28 @@ +# Check that the changelog is updated for all changes. +# +# This is only run for PRs. + +on: + pull_request: + # opened, reopened, synchronize are the default types for pull_request. + # labeled, unlabeled ensure this check is also run if a label is added or removed. + types: [opened, reopened, labeled, unlabeled, synchronize] + +name: Changelog + +jobs: + changelog: + name: Changelog + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Check that changelog updated + uses: dangoslen/changelog-enforcer@v3 + with: + changeLogPath: CHANGELOG.md + skipLabels: 'needs-changelog, skip-changelog' + missingUpdateErrorMessage: 'Please add a changelog entry in the CHANGELOG.md file.' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json b/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json new file mode 100644 index 0000000..fd3eed3 --- /dev/null +++ b/rtic/macros/src/syntax/.github/workflows/properties/build.properties.json @@ -0,0 +1,6 @@ +{ + "name": "Build", + "description": "RTIC Test Suite", + "iconName": "rust", + "categories": ["Rust"] +} diff --git a/rtic/macros/src/syntax/.gitignore b/rtic/macros/src/syntax/.gitignore new file mode 100644 index 0000000..f8d7c8b --- /dev/null +++ b/rtic/macros/src/syntax/.gitignore @@ -0,0 +1,4 @@ +**/*.rs.bk +.#* +/target/ +Cargo.lock diff --git a/rtic/macros/src/syntax/.travis.yml b/rtic/macros/src/syntax/.travis.yml new file mode 100644 index 0000000..52d1ffd --- /dev/null +++ b/rtic/macros/src/syntax/.travis.yml @@ -0,0 +1,31 @@ +language: rust + +matrix: + include: + # MSRV + - env: TARGET=x86_64-unknown-linux-gnu + rust: 1.36.0 + + - env: TARGET=x86_64-unknown-linux-gnu + rust: stable + +before_install: set -e + +script: + - bash ci/script.sh + +after_script: set +e + +cache: cargo + +before_cache: + - chmod -R a+r $HOME/.cargo; + +branches: + only: + - staging + - trying + +notifications: + email: + on_success: never diff --git a/rtic/macros/src/syntax/accessors.rs b/rtic/macros/src/syntax/accessors.rs new file mode 100644 index 0000000..e75dde6 --- /dev/null +++ b/rtic/macros/src/syntax/accessors.rs @@ -0,0 +1,113 @@ +use syn::Ident; + +use crate::syntax::{ + analyze::Priority, + ast::{Access, App, Local, TaskLocal}, +}; + +impl App { + pub(crate) fn shared_resource_accesses( + &self, + ) -> impl Iterator, &Ident, Access)> { + self.idle + .iter() + .flat_map(|idle| { + idle.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(0), name, *access)) + }) + .chain(self.hardware_tasks.values().flat_map(|task| { + task.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(task.args.priority), name, *access)) + })) + .chain(self.software_tasks.values().flat_map(|task| { + task.args + .shared_resources + .iter() + .map(move |(name, access)| (Some(task.args.priority), name, *access)) + })) + } + + fn is_external(task_local: &TaskLocal) -> bool { + matches!(task_local, TaskLocal::External) + } + + pub(crate) fn local_resource_accesses(&self) -> impl Iterator { + self.init + .args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + .chain(self.idle.iter().flat_map(|idle| { + idle.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + .chain(self.hardware_tasks.values().flat_map(|task| { + task.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + .chain(self.software_tasks.values().flat_map(|task| { + task.args + .local_resources + .iter() + .filter(|(_, task_local)| Self::is_external(task_local)) // Only check the resources declared in `#[local]` + .map(move |(name, _)| name) + })) + } + + fn get_declared_local(tl: &TaskLocal) -> Option<&Local> { + match tl { + TaskLocal::External => None, + TaskLocal::Declared(l) => Some(l), + } + } + + /// Get all declared local resources, i.e. `local = [NAME: TYPE = EXPR]`. + /// + /// Returns a vector of (task name, resource name, `Local` struct) + pub fn declared_local_resources(&self) -> Vec<(&Ident, &Ident, &Local)> { + self.init + .args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (&self.init.name, name, l)) + }) + .chain(self.idle.iter().flat_map(|idle| { + idle.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl) + .map(|l| (&self.idle.as_ref().unwrap().name, name, l)) + }) + })) + .chain(self.hardware_tasks.iter().flat_map(|(task_name, task)| { + task.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (task_name, name, l)) + }) + })) + .chain(self.software_tasks.iter().flat_map(|(task_name, task)| { + task.args + .local_resources + .iter() + .filter_map(move |(name, tl)| { + Self::get_declared_local(tl).map(|l| (task_name, name, l)) + }) + })) + .collect() + } +} diff --git a/rtic/macros/src/syntax/analyze.rs b/rtic/macros/src/syntax/analyze.rs new file mode 100644 index 0000000..3ed1487 --- /dev/null +++ b/rtic/macros/src/syntax/analyze.rs @@ -0,0 +1,417 @@ +//! RTIC application analysis + +use core::cmp; +use std::collections::{BTreeMap, BTreeSet, HashMap}; + +use indexmap::{IndexMap, IndexSet}; +use syn::{Ident, Type}; + +use crate::syntax::{ + ast::{App, LocalResources, TaskLocal}, + Set, +}; + +pub(crate) fn app(app: &App) -> Result { + // Collect all tasks into a vector + type TaskName = Ident; + type Priority = u8; + + // The task list is a Tuple (Name, Shared Resources, Local Resources, Priority) + let task_resources_list: Vec<(TaskName, Vec<&Ident>, &LocalResources, Priority)> = + Some(&app.init) + .iter() + .map(|ht| (ht.name.clone(), Vec::new(), &ht.args.local_resources, 0)) + .chain(app.idle.iter().map(|ht| { + ( + ht.name.clone(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + 0, + ) + })) + .chain(app.software_tasks.iter().map(|(name, ht)| { + ( + name.clone(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + ht.args.priority, + ) + })) + .chain(app.hardware_tasks.iter().map(|(name, ht)| { + ( + name.clone(), + ht.args + .shared_resources + .iter() + .map(|(v, _)| v) + .collect::>(), + &ht.args.local_resources, + ht.args.priority, + ) + })) + .collect(); + + let mut error = vec![]; + let mut lf_res_with_error = vec![]; + let mut lf_hash = HashMap::new(); + + // Collect lock free resources + let lock_free: Vec<&Ident> = app + .shared_resources + .iter() + .filter(|(_, r)| r.properties.lock_free) + .map(|(i, _)| i) + .collect(); + + // Check that lock_free resources are correct + for lf_res in lock_free.iter() { + for (task, tr, _, priority) in task_resources_list.iter() { + for r in tr { + // Get all uses of resources annotated lock_free + if lf_res == r { + // Check so async tasks do not use lock free resources + if app.software_tasks.get(task).is_some() { + error.push(syn::Error::new( + r.span(), + format!( + "Lock free shared resource {:?} is used by an async tasks, which is forbidden", + r.to_string(), + ), + )); + } + + // HashMap returns the previous existing object if old.key == new.key + if let Some(lf_res) = lf_hash.insert(r.to_string(), (task, r, priority)) { + // Check if priority differ, if it does, append to + // list of resources which will be annotated with errors + if priority != lf_res.2 { + lf_res_with_error.push(lf_res.1); + lf_res_with_error.push(r); + } + + // If the resource already violates lock free properties + if lf_res_with_error.contains(&r) { + lf_res_with_error.push(lf_res.1); + lf_res_with_error.push(r); + } + } + } + } + } + } + + // Add error message in the resource struct + for r in lock_free { + if lf_res_with_error.contains(&&r) { + error.push(syn::Error::new( + r.span(), + format!( + "Lock free shared resource {:?} is used by tasks at different priorities", + r.to_string(), + ), + )); + } + } + + // Add error message for each use of the shared resource + for resource in lf_res_with_error.clone() { + error.push(syn::Error::new( + resource.span(), + format!( + "Shared resource {:?} is declared lock free but used by tasks at different priorities", + resource.to_string(), + ), + )); + } + + // Collect local resources + let local: Vec<&Ident> = app.local_resources.iter().map(|(i, _)| i).collect(); + + let mut lr_with_error = vec![]; + let mut lr_hash = HashMap::new(); + + // Check that local resources are not shared + for lr in local { + for (task, _, local_resources, _) in task_resources_list.iter() { + for (name, res) in local_resources.iter() { + // Get all uses of resources annotated lock_free + if lr == name { + match res { + TaskLocal::External => { + // HashMap returns the previous existing object if old.key == new.key + if let Some(lr) = lr_hash.insert(name.to_string(), (task, name)) { + lr_with_error.push(lr.1); + lr_with_error.push(name); + } + } + // If a declared local has the same name as the `#[local]` struct, it's an + // direct error + TaskLocal::Declared(_) => { + lr_with_error.push(lr); + lr_with_error.push(name); + } + } + } + } + } + } + + // Add error message for each use of the local resource + for resource in lr_with_error.clone() { + error.push(syn::Error::new( + resource.span(), + format!( + "Local resource {:?} is used by multiple tasks or collides with multiple definitions", + resource.to_string(), + ), + )); + } + + // Check 0-priority async software tasks and idle dependency + for (name, task) in &app.software_tasks { + if task.args.priority == 0 { + // If there is a 0-priority task, there must be no idle + if app.idle.is_some() { + error.push(syn::Error::new( + name.span(), + format!( + "Async task {:?} has priority 0, but `#[idle]` is defined. 0-priority async tasks are only allowed if there is no `#[idle]`.", + name.to_string(), + ) + )); + } + } + } + + // Collect errors if any and return/halt + if !error.is_empty() { + let mut err = error.get(0).unwrap().clone(); + error.iter().for_each(|e| err.combine(e.clone())); + return Err(err); + } + + // e. Location of resources + let mut used_shared_resource = IndexSet::new(); + let mut ownerships = Ownerships::new(); + let mut sync_types = SyncTypes::new(); + for (prio, name, access) in app.shared_resource_accesses() { + let res = app.shared_resources.get(name).expect("UNREACHABLE"); + + // (e) + // This shared resource is used + used_shared_resource.insert(name.clone()); + + // (c) + if let Some(priority) = prio { + if let Some(ownership) = ownerships.get_mut(name) { + match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } + if priority != ceiling => + { + *ownership = Ownership::Contended { + ceiling: cmp::max(ceiling, priority), + }; + + if access.is_shared() { + sync_types.insert(res.ty.clone()); + } + } + + Ownership::Owned { priority: ceil } if ceil == priority => { + *ownership = Ownership::CoOwned { priority }; + } + + _ => {} + } + } else { + ownerships.insert(name.clone(), Ownership::Owned { priority }); + } + } + } + + // Create the list of used local resource Idents + let mut used_local_resource = IndexSet::new(); + + for (_, _, locals, _) in task_resources_list { + for (local, _) in locals { + used_local_resource.insert(local.clone()); + } + } + + // Most shared resources need to be `Send`, only 0 prio does not need it + let mut send_types = SendTypes::new(); + + for (name, res) in app.shared_resources.iter() { + if ownerships + .get(name) + .map(|ownership| match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } => ceiling != 0, + }) + .unwrap_or(false) + { + send_types.insert(res.ty.clone()); + } + } + + // Most local resources need to be `Send` as well, only 0 prio does not need it + for (name, res) in app.local_resources.iter() { + if ownerships + .get(name) + .map(|ownership| match *ownership { + Ownership::Owned { priority: ceiling } + | Ownership::CoOwned { priority: ceiling } + | Ownership::Contended { ceiling } => ceiling != 0, + }) + .unwrap_or(false) + { + send_types.insert(res.ty.clone()); + } + } + + let mut channels = Channels::new(); + + for (name, spawnee) in &app.software_tasks { + let spawnee_prio = spawnee.args.priority; + + let channel = channels.entry(spawnee_prio).or_default(); + channel.tasks.insert(name.clone()); + + // All inputs are send as we do not know from where they may be spawned. + spawnee.inputs.iter().for_each(|input| { + send_types.insert(input.ty.clone()); + }); + } + + // No channel should ever be empty + debug_assert!(channels.values().all(|channel| !channel.tasks.is_empty())); + + Ok(Analysis { + channels, + shared_resources: used_shared_resource, + local_resources: used_local_resource, + ownerships, + send_types, + sync_types, + }) +} + +// /// Priority ceiling +// pub type Ceiling = Option; + +/// Task priority +pub type Priority = u8; + +/// Resource name +pub type Resource = Ident; + +/// Task name +pub type Task = Ident; + +/// The result of analyzing an RTIC application +pub struct Analysis { + /// SPSC message channels + pub channels: Channels, + + /// Shared resources + /// + /// If a resource is not listed here it means that's a "dead" (never + /// accessed) resource and the backend should not generate code for it + pub shared_resources: UsedSharedResource, + + /// Local resources + /// + /// If a resource is not listed here it means that's a "dead" (never + /// accessed) resource and the backend should not generate code for it + pub local_resources: UsedLocalResource, + + /// Resource ownership + pub ownerships: Ownerships, + + /// These types must implement the `Send` trait + pub send_types: SendTypes, + + /// These types must implement the `Sync` trait + pub sync_types: SyncTypes, +} + +/// All channels, keyed by dispatch priority +pub type Channels = BTreeMap; + +/// Location of all *used* shared resources +pub type UsedSharedResource = IndexSet; + +/// Location of all *used* local resources +pub type UsedLocalResource = IndexSet; + +/// Resource ownership +pub type Ownerships = IndexMap; + +/// These types must implement the `Send` trait +pub type SendTypes = Set>; + +/// These types must implement the `Sync` trait +pub type SyncTypes = Set>; + +/// A channel used to send messages +#[derive(Debug, Default)] +pub struct Channel { + /// The channel capacity + pub capacity: u8, + + /// Tasks that can be spawned on this channel + pub tasks: BTreeSet, +} + +/// Resource ownership +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Ownership { + /// Owned by a single task + Owned { + /// Priority of the task that owns this resource + priority: u8, + }, + + /// "Co-owned" by more than one task; all of them have the same priority + CoOwned { + /// Priority of the tasks that co-own this resource + priority: u8, + }, + + /// Contended by more than one task; the tasks have different priorities + Contended { + /// Priority ceiling + ceiling: u8, + }, +} + +// impl Ownership { +// /// Whether this resource needs to a lock at this priority level +// pub fn needs_lock(&self, priority: u8) -> bool { +// match self { +// Ownership::Owned { .. } | Ownership::CoOwned { .. } => false, +// +// Ownership::Contended { ceiling } => { +// debug_assert!(*ceiling >= priority); +// +// priority < *ceiling +// } +// } +// } +// +// /// Whether this resource is exclusively owned +// pub fn is_owned(&self) -> bool { +// matches!(self, Ownership::Owned { .. }) +// } +// } diff --git a/rtic/macros/src/syntax/ast.rs b/rtic/macros/src/syntax/ast.rs new file mode 100644 index 0000000..27e6773 --- /dev/null +++ b/rtic/macros/src/syntax/ast.rs @@ -0,0 +1,335 @@ +//! Abstract Syntax Tree + +use syn::{Attribute, Expr, Ident, Item, ItemUse, Pat, PatType, Path, Stmt, Type}; + +use crate::syntax::Map; + +/// The `#[app]` attribute +#[derive(Debug)] +#[non_exhaustive] +pub struct App { + /// The arguments to the `#[app]` attribute + pub args: AppArgs, + + /// The name of the `const` item on which the `#[app]` attribute has been placed + pub name: Ident, + + /// The `#[init]` function + pub init: Init, + + /// The `#[idle]` function + pub idle: Option, + + /// Resources shared between tasks defined in `#[shared]` + pub shared_resources: Map, + + /// Task local resources defined in `#[local]` + pub local_resources: Map, + + /// User imports + pub user_imports: Vec, + + /// User code + pub user_code: Vec, + + /// Hardware tasks: `#[task(binds = ..)]`s + pub hardware_tasks: Map, + + /// Async software tasks: `#[task]` + pub software_tasks: Map, +} + +/// Interrupts used to dispatch software tasks +pub type Dispatchers = Map; + +/// Interrupt that could be used to dispatch software tasks +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Dispatcher { + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, +} + +/// The arguments of the `#[app]` attribute +#[derive(Debug)] +pub struct AppArgs { + /// Device + pub device: Path, + + /// Peripherals + pub peripherals: bool, + + /// Interrupts used to dispatch software tasks + pub dispatchers: Dispatchers, +} + +/// The `init`-ialization function +#[derive(Debug)] +#[non_exhaustive] +pub struct Init { + /// `init` context metadata + pub args: InitArgs, + + /// Attributes that will apply to this `init` function + pub attrs: Vec, + + /// The name of the `#[init]` function + pub name: Ident, + + /// The context argument + pub context: Box, + + /// The statements that make up this `init` function + pub stmts: Vec, + + /// The name of the user provided shared resources struct + pub user_shared_struct: Ident, + + /// The name of the user provided local resources struct + pub user_local_struct: Ident, +} + +/// `init` context metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct InitArgs { + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, +} + +impl Default for InitArgs { + fn default() -> Self { + Self { + local_resources: LocalResources::new(), + } + } +} + +/// The `idle` context +#[derive(Debug)] +#[non_exhaustive] +pub struct Idle { + /// `idle` context metadata + pub args: IdleArgs, + + /// Attributes that will apply to this `idle` function + pub attrs: Vec, + + /// The name of the `#[idle]` function + pub name: Ident, + + /// The context argument + pub context: Box, + + /// The statements that make up this `idle` function + pub stmts: Vec, +} + +/// `idle` context metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct IdleArgs { + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +impl Default for IdleArgs { + fn default() -> Self { + Self { + local_resources: LocalResources::new(), + shared_resources: SharedResources::new(), + } + } +} + +/// Shared resource properties +#[derive(Debug)] +pub struct SharedResourceProperties { + /// A lock free (exclusive resource) + pub lock_free: bool, +} + +/// A shared resource, defined in `#[shared]` +#[derive(Debug)] +#[non_exhaustive] +pub struct SharedResource { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// `#[doc]` attributes like `/// this is a docstring` + pub docs: Vec, + + /// Attributes that will apply to this resource + pub attrs: Vec, + + /// The type of this resource + pub ty: Box, + + /// Shared resource properties + pub properties: SharedResourceProperties, +} + +/// A local resource, defined in `#[local]` +#[derive(Debug)] +#[non_exhaustive] +pub struct LocalResource { + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// `#[doc]` attributes like `/// this is a docstring` + pub docs: Vec, + + /// Attributes that will apply to this resource + pub attrs: Vec, + + /// The type of this resource + pub ty: Box, +} + +/// An async software task +#[derive(Debug)] +#[non_exhaustive] +pub struct SoftwareTask { + /// Software task metadata + pub args: SoftwareTaskArgs, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, + + /// The context argument + pub context: Box, + + /// The inputs of this software task + pub inputs: Vec, + + /// The statements that make up the task handler + pub stmts: Vec, + + /// The task is declared externally + pub is_extern: bool, +} + +/// Software task metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct SoftwareTaskArgs { + /// The priority of this task + pub priority: u8, + + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +impl Default for SoftwareTaskArgs { + fn default() -> Self { + Self { + priority: 1, + local_resources: LocalResources::new(), + shared_resources: SharedResources::new(), + } + } +} + +/// A hardware task +#[derive(Debug)] +#[non_exhaustive] +pub struct HardwareTask { + /// Hardware task metadata + pub args: HardwareTaskArgs, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Attributes that will apply to this interrupt handler + pub attrs: Vec, + + /// The context argument + pub context: Box, + + /// The statements that make up the task handler + pub stmts: Vec, + + /// The task is declared externally + pub is_extern: bool, +} + +/// Hardware task metadata +#[derive(Debug)] +#[non_exhaustive] +pub struct HardwareTaskArgs { + /// The interrupt or exception that this task is bound to + pub binds: Ident, + + /// The priority of this task + pub priority: u8, + + /// Local resources that can be accessed from this context + pub local_resources: LocalResources, + + /// Shared resources that can be accessed from this context + pub shared_resources: SharedResources, +} + +/// A `static mut` variable local to and owned by a context +#[derive(Debug)] +#[non_exhaustive] +pub struct Local { + /// Attributes like `#[link_section]` + pub attrs: Vec, + + /// `#[cfg]` attributes like `#[cfg(debug_assertions)]` + pub cfgs: Vec, + + /// Type + pub ty: Box, + + /// Initial value + pub expr: Box, +} + +/// A wrapper of the 2 kinds of locals that tasks can have +#[derive(Debug)] +#[non_exhaustive] +pub enum TaskLocal { + /// The local is declared externally (i.e. `#[local]` struct) + External, + /// The local is declared in the task + Declared(Local), +} + +/// Resource access +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Access { + /// `[x]`, a mutable resource + Exclusive, + + /// `[&x]`, a static non-mutable resource + Shared, +} + +impl Access { + /// Is this enum in the `Exclusive` variant? + pub fn is_exclusive(&self) -> bool { + *self == Access::Exclusive + } + + /// Is this enum in the `Shared` variant? + pub fn is_shared(&self) -> bool { + *self == Access::Shared + } +} + +/// Shared resource access list in task attribute +pub type SharedResources = Map; + +/// Local resource access/declaration list in task attribute +pub type LocalResources = Map; diff --git a/rtic/macros/src/syntax/check.rs b/rtic/macros/src/syntax/check.rs new file mode 100644 index 0000000..989d418 --- /dev/null +++ b/rtic/macros/src/syntax/check.rs @@ -0,0 +1,66 @@ +use std::collections::HashSet; + +use syn::parse; + +use crate::syntax::ast::App; + +pub fn app(app: &App) -> parse::Result<()> { + // Check that all referenced resources have been declared + // Check that resources are NOT `Exclusive`-ly shared + let mut owners = HashSet::new(); + for (_, name, access) in app.shared_resource_accesses() { + if app.shared_resources.get(name).is_none() { + return Err(parse::Error::new( + name.span(), + "this shared resource has NOT been declared", + )); + } + + if access.is_exclusive() { + owners.insert(name); + } + } + + for name in app.local_resource_accesses() { + if app.local_resources.get(name).is_none() { + return Err(parse::Error::new( + name.span(), + "this local resource has NOT been declared", + )); + } + } + + // Check that no resource has both types of access (`Exclusive` & `Shared`) + let exclusive_accesses = app + .shared_resource_accesses() + .filter_map(|(priority, name, access)| { + if priority.is_some() && access.is_exclusive() { + Some(name) + } else { + None + } + }) + .collect::>(); + for (_, name, access) in app.shared_resource_accesses() { + if access.is_shared() && exclusive_accesses.contains(name) { + return Err(parse::Error::new( + name.span(), + "this implementation doesn't support shared (`&-`) - exclusive (`&mut-`) locks; use `x` instead of `&x`", + )); + } + } + + // check that dispatchers are not used as hardware tasks + for task in app.hardware_tasks.values() { + let binds = &task.args.binds; + + if app.args.dispatchers.contains_key(binds) { + return Err(parse::Error::new( + binds.span(), + "dispatcher interrupts can't be used as hardware tasks", + )); + } + } + + Ok(()) +} diff --git a/rtic/macros/src/syntax/optimize.rs b/rtic/macros/src/syntax/optimize.rs new file mode 100644 index 0000000..e83ba31 --- /dev/null +++ b/rtic/macros/src/syntax/optimize.rs @@ -0,0 +1,36 @@ +use std::collections::{BTreeSet, HashMap}; + +use crate::syntax::ast::App; + +pub fn app(app: &mut App, settings: &Settings) { + // "compress" priorities + // If the user specified, for example, task priorities of "1, 3, 6", + // compress them into "1, 2, 3" as to leave no gaps + if settings.optimize_priorities { + // all task priorities ordered in ascending order + let priorities = app + .hardware_tasks + .values() + .map(|task| Some(task.args.priority)) + .chain( + app.software_tasks + .values() + .map(|task| Some(task.args.priority)), + ) + .collect::>(); + + let map = priorities + .iter() + .cloned() + .zip(1..) + .collect::>(); + + for task in app.hardware_tasks.values_mut() { + task.args.priority = map[&Some(task.args.priority)]; + } + + for task in app.software_tasks.values_mut() { + task.args.priority = map[&Some(task.args.priority)]; + } + } +} diff --git a/rtic/macros/src/syntax/parse.rs b/rtic/macros/src/syntax/parse.rs new file mode 100644 index 0000000..c78453a --- /dev/null +++ b/rtic/macros/src/syntax/parse.rs @@ -0,0 +1,363 @@ +mod app; +mod hardware_task; +mod idle; +mod init; +mod resource; +mod software_task; +mod util; + +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + braced, parenthesized, + parse::{self, Parse, ParseStream, Parser}, + token::Brace, + Ident, Item, LitInt, Token, +}; + +use crate::syntax::{ + ast::{App, AppArgs, HardwareTaskArgs, IdleArgs, InitArgs, SoftwareTaskArgs, TaskLocal}, + Either, +}; + +// Parse the app, both app arguments and body (input) +pub fn app(args: TokenStream2, input: TokenStream2) -> parse::Result { + let args = AppArgs::parse(args)?; + let input: Input = syn::parse2(input)?; + + App::parse(args, input) +} + +pub(crate) struct Input { + _mod_token: Token![mod], + pub ident: Ident, + _brace_token: Brace, + pub items: Vec, +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> parse::Result { + fn parse_items(input: ParseStream<'_>) -> parse::Result> { + let mut items = vec![]; + + while !input.is_empty() { + items.push(input.parse()?); + } + + Ok(items) + } + + let content; + + let _mod_token = input.parse()?; + let ident = input.parse()?; + let _brace_token = braced!(content in input); + let items = content.call(parse_items)?; + + Ok(Input { + _mod_token, + ident, + _brace_token, + items, + }) + } +} + +fn init_args(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + if input.is_empty() { + return Ok(InitArgs::default()); + } + + let mut local_resources = None; + + let content; + parenthesized!(content in input); + + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if content.is_empty() { + break; + } + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + if let Some(locals) = &local_resources { + for (ident, task_local) in locals { + if let TaskLocal::External = task_local { + return Err(parse::Error::new( + ident.span(), + "only declared local resources are allowed in init", + )); + } + } + } + + Ok(InitArgs { + local_resources: local_resources.unwrap_or_default(), + }) + }) + .parse2(tokens) +} + +fn idle_args(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + if input.is_empty() { + return Ok(IdleArgs::default()); + } + + let mut shared_resources = None; + let mut local_resources = None; + + let content; + parenthesized!(content in input); + if !content.is_empty() { + loop { + // Parse identifier name + let ident: Ident = content.parse()?; + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident.to_string() { + "shared" => { + if shared_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + shared_resources = Some(util::parse_shared_resources(&content)?); + } + + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + } + + Ok(IdleArgs { + shared_resources: shared_resources.unwrap_or_default(), + local_resources: local_resources.unwrap_or_default(), + }) + }) + .parse2(tokens) +} + +fn task_args(tokens: TokenStream2) -> parse::Result> { + (|input: ParseStream<'_>| -> parse::Result> { + if input.is_empty() { + return Ok(Either::Right(SoftwareTaskArgs::default())); + } + + let mut binds = None; + let mut capacity = None; + let mut priority = None; + let mut shared_resources = None; + let mut local_resources = None; + let mut prio_span = None; + + let content; + parenthesized!(content in input); + loop { + if content.is_empty() { + break; + } + + // Parse identifier name + let ident: Ident = content.parse()?; + let ident_s = ident.to_string(); + + // Handle equal sign + let _: Token![=] = content.parse()?; + + match &*ident_s { + "binds" => { + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + if capacity.is_some() { + return Err(parse::Error::new( + ident.span(), + "hardware tasks can't use the `capacity` argument", + )); + } + + // Parse identifier name + let ident = content.parse()?; + + binds = Some(ident); + } + + "capacity" => { + if capacity.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + if binds.is_some() { + return Err(parse::Error::new( + ident.span(), + "hardware tasks can't use the `capacity` argument", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() || value == Some(0) { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 1...255", + )); + } + + capacity = Some(value.unwrap()); + } + + "priority" => { + if priority.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + // #lit + let lit: LitInt = content.parse()?; + + if !lit.suffix().is_empty() { + return Err(parse::Error::new( + lit.span(), + "this literal must be unsuffixed", + )); + } + + let value = lit.base10_parse::().ok(); + if value.is_none() { + return Err(parse::Error::new( + lit.span(), + "this literal must be in the range 0...255", + )); + } + + prio_span = Some(lit.span()); + priority = Some(value.unwrap()); + } + + "shared" => { + if shared_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + shared_resources = Some(util::parse_shared_resources(&content)?); + } + + "local" => { + if local_resources.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + local_resources = Some(util::parse_local_resources(&content)?); + } + + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if content.is_empty() { + break; + } + + // Handle comma: , + let _: Token![,] = content.parse()?; + } + let priority = priority.unwrap_or(1); + let shared_resources = shared_resources.unwrap_or_default(); + let local_resources = local_resources.unwrap_or_default(); + + Ok(if let Some(binds) = binds { + if priority == 0 { + return Err(parse::Error::new( + prio_span.unwrap(), + "hardware tasks are not allowed to be at priority 0", + )); + } + + Either::Left(HardwareTaskArgs { + binds, + priority, + shared_resources, + local_resources, + }) + } else { + Either::Right(SoftwareTaskArgs { + priority, + shared_resources, + local_resources, + }) + }) + }) + .parse2(tokens) +} diff --git a/rtic/macros/src/syntax/parse/app.rs b/rtic/macros/src/syntax/parse/app.rs new file mode 100644 index 0000000..e797f75 --- /dev/null +++ b/rtic/macros/src/syntax/parse/app.rs @@ -0,0 +1,480 @@ +use std::collections::HashSet; + +// use indexmap::map::Entry; +use proc_macro2::TokenStream as TokenStream2; +use syn::{ + parse::{self, ParseStream, Parser}, + spanned::Spanned, + Expr, ExprArray, Fields, ForeignItem, Ident, Item, LitBool, Path, Token, Visibility, +}; + +use super::Input; +use crate::syntax::{ + ast::{ + App, AppArgs, Dispatcher, Dispatchers, HardwareTask, Idle, IdleArgs, Init, InitArgs, + LocalResource, SharedResource, SoftwareTask, + }, + parse::{self as syntax_parse, util}, + Either, Map, Set, +}; + +impl AppArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + (|input: ParseStream<'_>| -> parse::Result { + let mut custom = Set::new(); + let mut device = None; + let mut peripherals = true; + let mut dispatchers = Dispatchers::new(); + + loop { + if input.is_empty() { + break; + } + + // #ident = .. + let ident: Ident = input.parse()?; + let _eq_token: Token![=] = input.parse()?; + + if custom.contains(&ident) { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + custom.insert(ident.clone()); + + let ks = ident.to_string(); + + match &*ks { + "device" => { + if let Ok(p) = input.parse::() { + device = Some(p); + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a path", + )); + } + } + + "peripherals" => { + if let Ok(p) = input.parse::() { + peripherals = p.value; + } else { + return Err(parse::Error::new( + ident.span(), + "unexpected argument value; this should be a boolean", + )); + } + } + + "dispatchers" => { + if let Ok(p) = input.parse::() { + for e in p.elems { + match e { + Expr::Path(ep) => { + let path = ep.path; + let ident = if path.leading_colon.is_some() + || path.segments.len() != 1 + { + return Err(parse::Error::new( + path.span(), + "interrupt must be an identifier, not a path", + )); + } else { + path.segments[0].ident.clone() + }; + let span = ident.span(); + if dispatchers.contains_key(&ident) { + return Err(parse::Error::new( + span, + "this extern interrupt is listed more than once", + )); + } else { + dispatchers + .insert(ident, Dispatcher { attrs: ep.attrs }); + } + } + _ => { + return Err(parse::Error::new( + e.span(), + "interrupt must be an identifier", + )); + } + } + } + } else { + return Err(parse::Error::new( + ident.span(), + // increasing the length of the error message will break rustfmt + "unexpected argument value; expected an array", + )); + } + } + _ => { + return Err(parse::Error::new(ident.span(), "unexpected argument")); + } + } + + if input.is_empty() { + break; + } + + // , + let _: Token![,] = input.parse()?; + } + + let device = if let Some(device) = device { + device + } else { + return Err(parse::Error::new(input.span(), "missing `device = ...`")); + }; + + Ok(AppArgs { + device, + peripherals, + dispatchers, + }) + }) + .parse2(tokens) + } +} + +impl App { + pub(crate) fn parse(args: AppArgs, input: Input) -> parse::Result { + let mut init = None; + let mut idle = None; + + let mut shared_resources_ident = None; + let mut shared_resources = Map::new(); + let mut local_resources_ident = None; + let mut local_resources = Map::new(); + let mut hardware_tasks = Map::new(); + let mut software_tasks = Map::new(); + let mut user_imports = vec![]; + let mut user_code = vec![]; + + let mut seen_idents = HashSet::::new(); + let mut bindings = HashSet::::new(); + + let mut check_binding = |ident: &Ident| { + if bindings.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this interrupt is already bound", + )); + } else { + bindings.insert(ident.clone()); + } + + Ok(()) + }; + + let mut check_ident = |ident: &Ident| { + if seen_idents.contains(ident) { + return Err(parse::Error::new( + ident.span(), + "this identifier has already been used", + )); + } else { + seen_idents.insert(ident.clone()); + } + + Ok(()) + }; + + for mut item in input.items { + match item { + Item::Fn(mut item) => { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "init")) + { + let args = InitArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an init function already exists, error + if init.is_some() { + return Err(parse::Error::new( + span, + "`#[init]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + init = Some(Init::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "idle")) + { + let args = IdleArgs::parse(item.attrs.remove(pos).tokens)?; + + // If an idle function already exists, error + if idle.is_some() { + return Err(parse::Error::new( + span, + "`#[idle]` function must appear at most once", + )); + } + + check_ident(&item.sig.ident)?; + + idle = Some(Idle::parse(args, item)?); + } else if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse(args, item)?, + ); + } + } + } else { + // Forward normal functions + user_code.push(Item::Fn(item.clone())); + } + } + + Item::Struct(ref mut struct_item) => { + // Match structures with the attribute #[shared], name of structure is not + // important + if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "shared")) + { + let span = struct_item.ident.span(); + + shared_resources_ident = Some(struct_item.ident.clone()); + + if !shared_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[shared]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if shared_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + shared_resources.insert( + ident.clone(), + SharedResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else if let Some(_pos) = struct_item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "local")) + { + let span = struct_item.ident.span(); + + local_resources_ident = Some(struct_item.ident.clone()); + + if !local_resources.is_empty() { + return Err(parse::Error::new( + span, + "`#[local]` struct must appear at most once", + )); + } + + if struct_item.vis != Visibility::Inherited { + return Err(parse::Error::new( + struct_item.span(), + "this item must have inherited / private visibility", + )); + } + + if let Fields::Named(fields) = &mut struct_item.fields { + for field in &mut fields.named { + let ident = field.ident.as_ref().expect("UNREACHABLE"); + + if local_resources.contains_key(ident) { + return Err(parse::Error::new( + ident.span(), + "this resource is listed more than once", + )); + } + + local_resources.insert( + ident.clone(), + LocalResource::parse(field, ident.span())?, + ); + } + } else { + return Err(parse::Error::new( + struct_item.span(), + "this `struct` must have named fields", + )); + } + } else { + // Structure without the #[resources] attribute should just be passed along + user_code.push(item.clone()); + } + } + + Item::ForeignMod(mod_) => { + if !util::abi_is_rust(&mod_.abi) { + return Err(parse::Error::new( + mod_.abi.extern_token.span(), + "this `extern` block must use the \"Rust\" ABI", + )); + } + + for item in mod_.items { + if let ForeignItem::Fn(mut item) = item { + let span = item.sig.ident.span(); + if let Some(pos) = item + .attrs + .iter() + .position(|attr| util::attr_eq(attr, "task")) + { + if hardware_tasks.contains_key(&item.sig.ident) + || software_tasks.contains_key(&item.sig.ident) + { + return Err(parse::Error::new( + span, + "this task is defined multiple times", + )); + } + + if item.attrs.len() != 1 { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + + match syntax_parse::task_args(item.attrs.remove(pos).tokens)? { + Either::Left(args) => { + check_binding(&args.binds)?; + check_ident(&item.sig.ident)?; + + hardware_tasks.insert( + item.sig.ident.clone(), + HardwareTask::parse_foreign(args, item)?, + ); + } + + Either::Right(args) => { + check_ident(&item.sig.ident)?; + + software_tasks.insert( + item.sig.ident.clone(), + SoftwareTask::parse_foreign(args, item)?, + ); + } + } + } else { + return Err(parse::Error::new( + span, + "`extern` task required `#[task(..)]` attribute", + )); + } + } else { + return Err(parse::Error::new( + item.span(), + "this item must live outside the `#[app]` module", + )); + } + } + } + Item::Use(itemuse_) => { + // Store the user provided use-statements + user_imports.push(itemuse_.clone()); + } + _ => { + // Anything else within the module should not make any difference + user_code.push(item.clone()); + } + } + } + + let shared_resources_ident = + shared_resources_ident.expect("No `#[shared]` resource struct defined"); + let local_resources_ident = + local_resources_ident.expect("No `#[local]` resource struct defined"); + let init = init.expect("No `#[init]` function defined"); + + if shared_resources_ident != init.user_shared_struct { + return Err(parse::Error::new( + init.user_shared_struct.span(), + format!( + "This name and the one defined on `#[shared]` are not the same. Should this be `{shared_resources_ident}`?" + ), + )); + } + + if local_resources_ident != init.user_local_struct { + return Err(parse::Error::new( + init.user_local_struct.span(), + format!( + "This name and the one defined on `#[local]` are not the same. Should this be `{local_resources_ident}`?" + ), + )); + } + + Ok(App { + args, + name: input.ident, + init, + idle, + shared_resources, + local_resources, + user_imports, + user_code, + hardware_tasks, + software_tasks, + }) + } +} diff --git a/rtic/macros/src/syntax/parse/hardware_task.rs b/rtic/macros/src/syntax/parse/hardware_task.rs new file mode 100644 index 0000000..7f6dfbe --- /dev/null +++ b/rtic/macros/src/syntax/parse/hardware_task.rs @@ -0,0 +1,76 @@ +use syn::{parse, ForeignItemFn, ItemFn, Stmt}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{HardwareTask, HardwareTaskArgs}, + parse::util, +}; + +impl HardwareTask { + pub(crate) fn parse(args: HardwareTaskArgs, item: ItemFn) -> parse::Result { + let span = item.sig.ident.span(); + let valid_signature = util::check_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_unit(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: item.block.stmts, + is_extern: false, + }); + } + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `fn({name}::Context)`"), + )) + } +} + +impl HardwareTask { + pub(crate) fn parse_foreign( + args: HardwareTaskArgs, + item: ForeignItemFn, + ) -> parse::Result { + let span = item.sig.ident.span(); + let valid_signature = util::check_foreign_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_unit(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(HardwareTask { + args, + cfgs, + attrs, + context, + stmts: Vec::::new(), + is_extern: true, + }); + } + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `fn({name}::Context)`"), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/idle.rs b/rtic/macros/src/syntax/parse/idle.rs new file mode 100644 index 0000000..124c136 --- /dev/null +++ b/rtic/macros/src/syntax/parse/idle.rs @@ -0,0 +1,42 @@ +use proc_macro2::TokenStream as TokenStream2; +use syn::{parse, ItemFn}; + +use crate::syntax::{ + ast::{Idle, IdleArgs}, + parse::util, +}; + +impl IdleArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + crate::syntax::parse::idle_args(tokens) + } +} + +impl Idle { + pub(crate) fn parse(args: IdleArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, false) + && item.sig.inputs.len() == 1 + && util::type_is_bottom(&item.sig.output); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Idle { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + }); + } + } + } + + Err(parse::Error::new( + item.sig.ident.span(), + format!("this `#[idle]` function must have signature `fn({name}::Context) -> !`"), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/init.rs b/rtic/macros/src/syntax/parse/init.rs new file mode 100644 index 0000000..0aea20b --- /dev/null +++ b/rtic/macros/src/syntax/parse/init.rs @@ -0,0 +1,51 @@ +use proc_macro2::TokenStream as TokenStream2; + +use syn::{parse, ItemFn}; + +use crate::syntax::{ + ast::{Init, InitArgs}, + parse::{self as syntax_parse, util}, +}; + +impl InitArgs { + pub(crate) fn parse(tokens: TokenStream2) -> parse::Result { + syntax_parse::init_args(tokens) + } +} + +impl Init { + pub(crate) fn parse(args: InitArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, false) && item.sig.inputs.len() == 1; + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Ok((user_shared_struct, user_local_struct)) = + util::type_is_init_return(&item.sig.output) + { + if let Some((context, Ok(rest))) = util::parse_inputs(item.sig.inputs, &name) { + if rest.is_empty() { + return Ok(Init { + args, + attrs: item.attrs, + context, + name: item.sig.ident, + stmts: item.block.stmts, + user_shared_struct, + user_local_struct, + }); + } + } + } + } + + Err(parse::Error::new( + span, + format!( + "the `#[init]` function must have signature `fn({name}::Context) -> (Shared resources struct, Local resources struct)`" + ), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/resource.rs b/rtic/macros/src/syntax/parse/resource.rs new file mode 100644 index 0000000..ff10057 --- /dev/null +++ b/rtic/macros/src/syntax/parse/resource.rs @@ -0,0 +1,55 @@ +use proc_macro2::Span; +use syn::{parse, Field, Visibility}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{LocalResource, SharedResource, SharedResourceProperties}, + parse::util, +}; + +impl SharedResource { + pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { + cfgs, + mut attrs, + docs, + } = util::filter_attributes(item.attrs.clone()); + + let lock_free = util::extract_lock_free(&mut attrs)?; + + Ok(SharedResource { + cfgs, + attrs, + docs, + ty: Box::new(item.ty.clone()), + properties: SharedResourceProperties { lock_free }, + }) + } +} + +impl LocalResource { + pub(crate) fn parse(item: &Field, span: Span) -> parse::Result { + if item.vis != Visibility::Inherited { + return Err(parse::Error::new( + span, + "this field must have inherited / private visibility", + )); + } + + let FilterAttrs { cfgs, attrs, docs } = util::filter_attributes(item.attrs.clone()); + + Ok(LocalResource { + cfgs, + attrs, + docs, + ty: Box::new(item.ty.clone()), + }) + } +} diff --git a/rtic/macros/src/syntax/parse/software_task.rs b/rtic/macros/src/syntax/parse/software_task.rs new file mode 100644 index 0000000..769aa65 --- /dev/null +++ b/rtic/macros/src/syntax/parse/software_task.rs @@ -0,0 +1,76 @@ +use syn::{parse, ForeignItemFn, ItemFn, Stmt}; + +use crate::syntax::parse::util::FilterAttrs; +use crate::syntax::{ + ast::{SoftwareTask, SoftwareTaskArgs}, + parse::util, +}; + +impl SoftwareTask { + pub(crate) fn parse(args: SoftwareTaskArgs, item: ItemFn) -> parse::Result { + let valid_signature = util::check_fn_signature(&item, true) + && util::type_is_unit(&item.sig.output) + && item.sig.asyncness.is_some(); + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(SoftwareTask { + args, + attrs, + cfgs, + context, + inputs, + stmts: item.block.stmts, + is_extern: false, + }); + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `async fn({name}::Context, ..)`"), + )) + } +} + +impl SoftwareTask { + pub(crate) fn parse_foreign( + args: SoftwareTaskArgs, + item: ForeignItemFn, + ) -> parse::Result { + let valid_signature = util::check_foreign_fn_signature(&item, true) + && util::type_is_unit(&item.sig.output) + && item.sig.asyncness.is_some(); + + let span = item.sig.ident.span(); + + let name = item.sig.ident.to_string(); + + if valid_signature { + if let Some((context, Ok(inputs))) = util::parse_inputs(item.sig.inputs, &name) { + let FilterAttrs { cfgs, attrs, .. } = util::filter_attributes(item.attrs); + + return Ok(SoftwareTask { + args, + attrs, + cfgs, + context, + inputs, + stmts: Vec::::new(), + is_extern: true, + }); + } + } + + Err(parse::Error::new( + span, + format!("this task handler must have type signature `async fn({name}::Context, ..)`"), + )) + } +} diff --git a/rtic/macros/src/syntax/parse/util.rs b/rtic/macros/src/syntax/parse/util.rs new file mode 100644 index 0000000..5a5e0c0 --- /dev/null +++ b/rtic/macros/src/syntax/parse/util.rs @@ -0,0 +1,338 @@ +use syn::{ + bracketed, + parse::{self, ParseStream}, + punctuated::Punctuated, + spanned::Spanned, + Abi, AttrStyle, Attribute, Expr, FnArg, ForeignItemFn, Ident, ItemFn, Pat, PatType, Path, + PathArguments, ReturnType, Token, Type, Visibility, +}; + +use crate::syntax::{ + ast::{Access, Local, LocalResources, SharedResources, TaskLocal}, + Map, +}; + +pub fn abi_is_rust(abi: &Abi) -> bool { + match &abi.name { + None => true, + Some(s) => s.value() == "Rust", + } +} + +pub fn attr_eq(attr: &Attribute, name: &str) -> bool { + attr.style == AttrStyle::Outer && attr.path.segments.len() == 1 && { + let segment = attr.path.segments.first().unwrap(); + segment.arguments == PathArguments::None && *segment.ident.to_string() == *name + } +} + +/// checks that a function signature +/// +/// - has no bounds (like where clauses) +/// - is not `async` +/// - is not `const` +/// - is not `unsafe` +/// - is not generic (has no type parameters) +/// - is not variadic +/// - uses the Rust ABI (and not e.g. "C") +pub fn check_fn_signature(item: &ItemFn, allow_async: bool) -> bool { + item.vis == Visibility::Inherited + && item.sig.constness.is_none() + && (item.sig.asyncness.is_none() || allow_async) + && item.sig.abi.is_none() + && item.sig.unsafety.is_none() + && item.sig.generics.params.is_empty() + && item.sig.generics.where_clause.is_none() + && item.sig.variadic.is_none() +} + +#[allow(dead_code)] +pub fn check_foreign_fn_signature(item: &ForeignItemFn, allow_async: bool) -> bool { + item.vis == Visibility::Inherited + && item.sig.constness.is_none() + && (item.sig.asyncness.is_none() || allow_async) + && item.sig.abi.is_none() + && item.sig.unsafety.is_none() + && item.sig.generics.params.is_empty() + && item.sig.generics.where_clause.is_none() + && item.sig.variadic.is_none() +} + +pub struct FilterAttrs { + pub cfgs: Vec, + pub docs: Vec, + pub attrs: Vec, +} + +pub fn filter_attributes(input_attrs: Vec) -> FilterAttrs { + let mut cfgs = vec![]; + let mut docs = vec![]; + let mut attrs = vec![]; + + for attr in input_attrs { + if attr_eq(&attr, "cfg") { + cfgs.push(attr); + } else if attr_eq(&attr, "doc") { + docs.push(attr); + } else { + attrs.push(attr); + } + } + + FilterAttrs { cfgs, docs, attrs } +} + +pub fn extract_lock_free(attrs: &mut Vec) -> parse::Result { + if let Some(pos) = attrs.iter().position(|attr| attr_eq(attr, "lock_free")) { + attrs.remove(pos); + Ok(true) + } else { + Ok(false) + } +} + +pub fn parse_shared_resources(content: ParseStream<'_>) -> parse::Result { + let inner; + bracketed!(inner in content); + + let mut resources = Map::new(); + for e in inner.call(Punctuated::::parse_terminated)? { + let err = Err(parse::Error::new( + e.span(), + "identifier appears more than once in list", + )); + let (access, path) = match e { + Expr::Path(e) => (Access::Exclusive, e.path), + + Expr::Reference(ref r) if r.mutability.is_none() => match &*r.expr { + Expr::Path(e) => (Access::Shared, e.path.clone()), + + _ => return err, + }, + + _ => return err, + }; + + let ident = extract_resource_name_ident(path)?; + + if resources.contains_key(&ident) { + return Err(parse::Error::new( + ident.span(), + "resource appears more than once in list", + )); + } + + resources.insert(ident, access); + } + + Ok(resources) +} + +fn extract_resource_name_ident(path: Path) -> parse::Result { + if path.leading_colon.is_some() + || path.segments.len() != 1 + || path.segments[0].arguments != PathArguments::None + { + Err(parse::Error::new( + path.span(), + "resource must be an identifier, not a path", + )) + } else { + Ok(path.segments[0].ident.clone()) + } +} + +pub fn parse_local_resources(content: ParseStream<'_>) -> parse::Result { + let inner; + bracketed!(inner in content); + + let mut resources = Map::new(); + + for e in inner.call(Punctuated::::parse_terminated)? { + let err = Err(parse::Error::new( + e.span(), + "identifier appears more than once in list", + )); + + let (name, local) = match e { + // local = [IDENT], + Expr::Path(path) => { + if !path.attrs.is_empty() { + return Err(parse::Error::new( + path.span(), + "attributes are not supported here", + )); + } + + let ident = extract_resource_name_ident(path.path)?; + // let (cfgs, attrs) = extract_cfgs(path.attrs); + + (ident, TaskLocal::External) + } + + // local = [IDENT: TYPE = EXPR] + Expr::Assign(e) => { + let (name, ty, cfgs, attrs) = match *e.left { + Expr::Type(t) => { + // Extract name and attributes + let (name, cfgs, attrs) = match *t.expr { + Expr::Path(path) => { + let name = extract_resource_name_ident(path.path)?; + let FilterAttrs { cfgs, attrs, .. } = filter_attributes(path.attrs); + + (name, cfgs, attrs) + } + _ => return err, + }; + + let ty = t.ty; + + // Error check + match &*ty { + Type::Array(_) => {} + Type::Path(_) => {} + Type::Ptr(_) => {} + Type::Tuple(_) => {} + _ => return Err(parse::Error::new( + ty.span(), + "unsupported type, must be an array, tuple, pointer or type path", + )), + }; + + (name, ty, cfgs, attrs) + } + e => return Err(parse::Error::new(e.span(), "malformed, expected a type")), + }; + + let expr = e.right; // Expr + + ( + name, + TaskLocal::Declared(Local { + attrs, + cfgs, + ty, + expr, + }), + ) + } + + expr => { + return Err(parse::Error::new( + expr.span(), + "malformed, expected 'IDENT: TYPE = EXPR'", + )) + } + }; + + resources.insert(name, local); + } + + Ok(resources) +} + +type ParseInputResult = Option<(Box, Result, FnArg>)>; + +pub fn parse_inputs(inputs: Punctuated, name: &str) -> ParseInputResult { + let mut inputs = inputs.into_iter(); + + match inputs.next() { + Some(FnArg::Typed(first)) => { + if type_is_path(&first.ty, &[name, "Context"]) { + let rest = inputs + .map(|arg| match arg { + FnArg::Typed(arg) => Ok(arg), + _ => Err(arg), + }) + .collect::, _>>(); + + Some((first.pat, rest)) + } else { + None + } + } + + _ => None, + } +} + +pub fn type_is_bottom(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + matches!(**ty, Type::Never(_)) + } else { + false + } +} + +fn extract_init_resource_name_ident(ty: Type) -> Result { + match ty { + Type::Path(path) => { + let path = path.path; + + if path.leading_colon.is_some() + || path.segments.len() != 1 + || path.segments[0].arguments != PathArguments::None + { + Err(()) + } else { + Ok(path.segments[0].ident.clone()) + } + } + _ => Err(()), + } +} + +/// Checks Init's return type, return the user provided types for analysis +pub fn type_is_init_return(ty: &ReturnType) -> Result<(Ident, Ident), ()> { + match ty { + ReturnType::Default => Err(()), + + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(t) => { + // return should be: + // fn -> (User's #[shared] struct, User's #[local] struct) + // + // We check the length and the last one here, analysis checks that the user + // provided structs are correct. + if t.elems.len() == 2 { + return Ok(( + extract_init_resource_name_ident(t.elems[0].clone())?, + extract_init_resource_name_ident(t.elems[1].clone())?, + )); + } + + Err(()) + } + + _ => Err(()), + }, + } +} + +pub fn type_is_path(ty: &Type, segments: &[&str]) -> bool { + match ty { + Type::Path(tpath) if tpath.qself.is_none() => { + tpath.path.segments.len() == segments.len() + && tpath + .path + .segments + .iter() + .zip(segments) + .all(|(lhs, rhs)| lhs.ident == **rhs) + } + + _ => false, + } +} + +pub fn type_is_unit(ty: &ReturnType) -> bool { + if let ReturnType::Type(_, ty) = ty { + if let Type::Tuple(ref tuple) = **ty { + tuple.elems.is_empty() + } else { + false + } + } else { + true + } +} diff --git a/rtic/macros/tests/ui.rs b/rtic/macros/tests/ui.rs new file mode 100644 index 0000000..9fb88a1 --- /dev/null +++ b/rtic/macros/tests/ui.rs @@ -0,0 +1,7 @@ +use trybuild::TestCases; + +#[test] +fn ui() { + let t = TestCases::new(); + t.compile_fail("ui/*.rs"); +} diff --git a/rtic/macros/ui/extern-interrupt-used.rs b/rtic/macros/ui/extern-interrupt-used.rs new file mode 100644 index 0000000..6346a7d --- /dev/null +++ b/rtic/macros/ui/extern-interrupt-used.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock, dispatchers = [EXTI0])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(binds = EXTI0)] + fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/extern-interrupt-used.stderr b/rtic/macros/ui/extern-interrupt-used.stderr new file mode 100644 index 0000000..970d39b --- /dev/null +++ b/rtic/macros/ui/extern-interrupt-used.stderr @@ -0,0 +1,5 @@ +error: dispatcher interrupts can't be used as hardware tasks + --> ui/extern-interrupt-used.rs:14:20 + | +14 | #[task(binds = EXTI0)] + | ^^^^^ diff --git a/rtic/macros/ui/idle-double-local.rs b/rtic/macros/ui/idle-double-local.rs new file mode 100644 index 0000000..54e67d3 --- /dev/null +++ b/rtic/macros/ui/idle-double-local.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle(local = [A], local = [B])] + fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-double-local.stderr b/rtic/macros/ui/idle-double-local.stderr new file mode 100644 index 0000000..b558136 --- /dev/null +++ b/rtic/macros/ui/idle-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/idle-double-local.rs:5:25 + | +5 | #[idle(local = [A], local = [B])] + | ^^^^^ diff --git a/rtic/macros/ui/idle-double-shared.rs b/rtic/macros/ui/idle-double-shared.rs new file mode 100644 index 0000000..f66cb93 --- /dev/null +++ b/rtic/macros/ui/idle-double-shared.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle(shared = [A], shared = [B])] + fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-double-shared.stderr b/rtic/macros/ui/idle-double-shared.stderr new file mode 100644 index 0000000..6f62ad2 --- /dev/null +++ b/rtic/macros/ui/idle-double-shared.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/idle-double-shared.rs:5:26 + | +5 | #[idle(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/rtic/macros/ui/idle-input.rs b/rtic/macros/ui/idle-input.rs new file mode 100644 index 0000000..c896b1c --- /dev/null +++ b/rtic/macros/ui/idle-input.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context, _undef: u32) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-input.stderr b/rtic/macros/ui/idle-input.stderr new file mode 100644 index 0000000..34c38fc --- /dev/null +++ b/rtic/macros/ui/idle-input.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-input.rs:6:8 + | +6 | fn idle(_: idle::Context, _undef: u32) -> ! { + | ^^^^ diff --git a/rtic/macros/ui/idle-no-context.rs b/rtic/macros/ui/idle-no-context.rs new file mode 100644 index 0000000..bab4680 --- /dev/null +++ b/rtic/macros/ui/idle-no-context.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle() -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-no-context.stderr b/rtic/macros/ui/idle-no-context.stderr new file mode 100644 index 0000000..c9f4b3d --- /dev/null +++ b/rtic/macros/ui/idle-no-context.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-no-context.rs:6:8 + | +6 | fn idle() -> ! { + | ^^^^ diff --git a/rtic/macros/ui/idle-not-divergent.rs b/rtic/macros/ui/idle-not-divergent.rs new file mode 100644 index 0000000..d1ae8b1 --- /dev/null +++ b/rtic/macros/ui/idle-not-divergent.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context) {} +} diff --git a/rtic/macros/ui/idle-not-divergent.stderr b/rtic/macros/ui/idle-not-divergent.stderr new file mode 100644 index 0000000..e318f58 --- /dev/null +++ b/rtic/macros/ui/idle-not-divergent.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-not-divergent.rs:6:8 + | +6 | fn idle(_: idle::Context) {} + | ^^^^ diff --git a/rtic/macros/ui/idle-output.rs b/rtic/macros/ui/idle-output.rs new file mode 100644 index 0000000..1662157 --- /dev/null +++ b/rtic/macros/ui/idle-output.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn idle(_: idle::Context) -> u32 { + 0 + } +} diff --git a/rtic/macros/ui/idle-output.stderr b/rtic/macros/ui/idle-output.stderr new file mode 100644 index 0000000..7070e25 --- /dev/null +++ b/rtic/macros/ui/idle-output.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-output.rs:6:8 + | +6 | fn idle(_: idle::Context) -> u32 { + | ^^^^ diff --git a/rtic/macros/ui/idle-pub.rs b/rtic/macros/ui/idle-pub.rs new file mode 100644 index 0000000..0d8dd01 --- /dev/null +++ b/rtic/macros/ui/idle-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + pub fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-pub.stderr b/rtic/macros/ui/idle-pub.stderr new file mode 100644 index 0000000..aa46ac3 --- /dev/null +++ b/rtic/macros/ui/idle-pub.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-pub.rs:6:12 + | +6 | pub fn idle(_: idle::Context) -> ! { + | ^^^^ diff --git a/rtic/macros/ui/idle-unsafe.rs b/rtic/macros/ui/idle-unsafe.rs new file mode 100644 index 0000000..3422ef2 --- /dev/null +++ b/rtic/macros/ui/idle-unsafe.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + unsafe fn idle(_: idle::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/idle-unsafe.stderr b/rtic/macros/ui/idle-unsafe.stderr new file mode 100644 index 0000000..a416800 --- /dev/null +++ b/rtic/macros/ui/idle-unsafe.stderr @@ -0,0 +1,5 @@ +error: this `#[idle]` function must have signature `fn(idle::Context) -> !` + --> ui/idle-unsafe.rs:6:15 + | +6 | unsafe fn idle(_: idle::Context) -> ! { + | ^^^^ diff --git a/rtic/macros/ui/init-divergent.rs b/rtic/macros/ui/init-divergent.rs new file mode 100644 index 0000000..5e4e96a --- /dev/null +++ b/rtic/macros/ui/init-divergent.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> ! {} +} diff --git a/rtic/macros/ui/init-divergent.stderr b/rtic/macros/ui/init-divergent.stderr new file mode 100644 index 0000000..9f6acf6 --- /dev/null +++ b/rtic/macros/ui/init-divergent.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-divergent.rs:12:8 + | +12 | fn init(_: init::Context) -> ! {} + | ^^^^ diff --git a/rtic/macros/ui/init-double-local.rs b/rtic/macros/ui/init-double-local.rs new file mode 100644 index 0000000..5f6d7ac --- /dev/null +++ b/rtic/macros/ui/init-double-local.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init(local = [A], local = [B])] + fn init(_: init::Context) {} +} diff --git a/rtic/macros/ui/init-double-local.stderr b/rtic/macros/ui/init-double-local.stderr new file mode 100644 index 0000000..07c3b50 --- /dev/null +++ b/rtic/macros/ui/init-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/init-double-local.rs:5:25 + | +5 | #[init(local = [A], local = [B])] + | ^^^^^ diff --git a/rtic/macros/ui/init-double-shared.rs b/rtic/macros/ui/init-double-shared.rs new file mode 100644 index 0000000..4503c87 --- /dev/null +++ b/rtic/macros/ui/init-double-shared.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init(shared = [A], shared = [B])] + fn init(_: init::Context) {} +} diff --git a/rtic/macros/ui/init-double-shared.stderr b/rtic/macros/ui/init-double-shared.stderr new file mode 100644 index 0000000..af2a97b --- /dev/null +++ b/rtic/macros/ui/init-double-shared.stderr @@ -0,0 +1,5 @@ +error: unexpected argument + --> ui/init-double-shared.rs:5:12 + | +5 | #[init(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/rtic/macros/ui/init-input.rs b/rtic/macros/ui/init-input.rs new file mode 100644 index 0000000..d41a503 --- /dev/null +++ b/rtic/macros/ui/init-input.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-input.stderr b/rtic/macros/ui/init-input.stderr new file mode 100644 index 0000000..e236043 --- /dev/null +++ b/rtic/macros/ui/init-input.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-input.rs:12:8 + | +12 | fn init(_: init::Context, _undef: u32) -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/init-no-context.rs b/rtic/macros/ui/init-no-context.rs new file mode 100644 index 0000000..cdce4c5 --- /dev/null +++ b/rtic/macros/ui/init-no-context.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init() -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-no-context.stderr b/rtic/macros/ui/init-no-context.stderr new file mode 100644 index 0000000..28e1fd4 --- /dev/null +++ b/rtic/macros/ui/init-no-context.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-no-context.rs:12:8 + | +12 | fn init() -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/init-output.rs b/rtic/macros/ui/init-output.rs new file mode 100644 index 0000000..7057c95 --- /dev/null +++ b/rtic/macros/ui/init-output.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init] + fn init(_: init::Context) -> u32 { + 0 + } +} diff --git a/rtic/macros/ui/init-output.stderr b/rtic/macros/ui/init-output.stderr new file mode 100644 index 0000000..8bc3c83 --- /dev/null +++ b/rtic/macros/ui/init-output.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-output.rs:6:8 + | +6 | fn init(_: init::Context) -> u32 { + | ^^^^ diff --git a/rtic/macros/ui/init-pub.rs b/rtic/macros/ui/init-pub.rs new file mode 100644 index 0000000..dd59aa1 --- /dev/null +++ b/rtic/macros/ui/init-pub.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + pub fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-pub.stderr b/rtic/macros/ui/init-pub.stderr new file mode 100644 index 0000000..b1610ed --- /dev/null +++ b/rtic/macros/ui/init-pub.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-pub.rs:12:12 + | +12 | pub fn init(_: init::Context) -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/init-unsafe.rs b/rtic/macros/ui/init-unsafe.rs new file mode 100644 index 0000000..4f89baf --- /dev/null +++ b/rtic/macros/ui/init-unsafe.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[init] + unsafe fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/init-unsafe.stderr b/rtic/macros/ui/init-unsafe.stderr new file mode 100644 index 0000000..fd0b8f3 --- /dev/null +++ b/rtic/macros/ui/init-unsafe.stderr @@ -0,0 +1,5 @@ +error: the `#[init]` function must have signature `fn(init::Context) -> (Shared resources struct, Local resources struct)` + --> ui/init-unsafe.rs:6:15 + | +6 | unsafe fn init(_: init::Context) -> (Shared, Local) {} + | ^^^^ diff --git a/rtic/macros/ui/interrupt-double.rs b/rtic/macros/ui/interrupt-double.rs new file mode 100644 index 0000000..e2addc7 --- /dev/null +++ b/rtic/macros/ui/interrupt-double.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = UART0)] + fn foo(_: foo::Context) {} + + #[task(binds = UART0)] + fn bar(_: bar::Context) {} +} diff --git a/rtic/macros/ui/interrupt-double.stderr b/rtic/macros/ui/interrupt-double.stderr new file mode 100644 index 0000000..8db34e2 --- /dev/null +++ b/rtic/macros/ui/interrupt-double.stderr @@ -0,0 +1,5 @@ +error: this interrupt is already bound + --> ui/interrupt-double.rs:8:20 + | +8 | #[task(binds = UART0)] + | ^^^^^ diff --git a/rtic/macros/ui/local-collision-2.rs b/rtic/macros/ui/local-collision-2.rs new file mode 100644 index 0000000..08bc8e5 --- /dev/null +++ b/rtic/macros/ui/local-collision-2.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + a: u32, + } + + #[task(local = [a: u8 = 3])] + async fn bar(_: bar::Context) {} + + #[init(local = [a: u16 = 2])] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-collision-2.stderr b/rtic/macros/ui/local-collision-2.stderr new file mode 100644 index 0000000..47dbbe3 --- /dev/null +++ b/rtic/macros/ui/local-collision-2.stderr @@ -0,0 +1,17 @@ +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision-2.rs:10:9 + | +10 | a: u32, + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision-2.rs:16:21 + | +16 | #[init(local = [a: u16 = 2])] + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision-2.rs:13:21 + | +13 | #[task(local = [a: u8 = 3])] + | ^ diff --git a/rtic/macros/ui/local-collision.rs b/rtic/macros/ui/local-collision.rs new file mode 100644 index 0000000..0e4eef7 --- /dev/null +++ b/rtic/macros/ui/local-collision.rs @@ -0,0 +1,21 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + a: u32, + } + + #[task(local = [a])] + async fn foo(_: foo::Context) {} + + #[task(local = [a: u8 = 3])] + async fn bar(_: bar::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-collision.stderr b/rtic/macros/ui/local-collision.stderr new file mode 100644 index 0000000..47fbb6e --- /dev/null +++ b/rtic/macros/ui/local-collision.stderr @@ -0,0 +1,11 @@ +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision.rs:10:9 + | +10 | a: u32, + | ^ + +error: Local resource "a" is used by multiple tasks or collides with multiple definitions + --> ui/local-collision.rs:16:21 + | +16 | #[task(local = [a: u8 = 3])] + | ^ diff --git a/rtic/macros/ui/local-malformed-1.rs b/rtic/macros/ui/local-malformed-1.rs new file mode 100644 index 0000000..219eef5 --- /dev/null +++ b/rtic/macros/ui/local-malformed-1.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a:])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-1.stderr b/rtic/macros/ui/local-malformed-1.stderr new file mode 100644 index 0000000..d15c324 --- /dev/null +++ b/rtic/macros/ui/local-malformed-1.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> ui/local-malformed-1.rs:11:23 + | +11 | #[task(local = [a:])] + | ^ diff --git a/rtic/macros/ui/local-malformed-2.rs b/rtic/macros/ui/local-malformed-2.rs new file mode 100644 index 0000000..d691453 --- /dev/null +++ b/rtic/macros/ui/local-malformed-2.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a: u32])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-2.stderr b/rtic/macros/ui/local-malformed-2.stderr new file mode 100644 index 0000000..0b448f0 --- /dev/null +++ b/rtic/macros/ui/local-malformed-2.stderr @@ -0,0 +1,5 @@ +error: malformed, expected 'IDENT: TYPE = EXPR' + --> ui/local-malformed-2.rs:11:21 + | +11 | #[task(local = [a: u32])] + | ^^^^^^ diff --git a/rtic/macros/ui/local-malformed-3.rs b/rtic/macros/ui/local-malformed-3.rs new file mode 100644 index 0000000..7eddfa4 --- /dev/null +++ b/rtic/macros/ui/local-malformed-3.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a: u32 =])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-3.stderr b/rtic/macros/ui/local-malformed-3.stderr new file mode 100644 index 0000000..61af4f3 --- /dev/null +++ b/rtic/macros/ui/local-malformed-3.stderr @@ -0,0 +1,5 @@ +error: unexpected end of input, expected expression + --> ui/local-malformed-3.rs:11:29 + | +11 | #[task(local = [a: u32 =])] + | ^ diff --git a/rtic/macros/ui/local-malformed-4.rs b/rtic/macros/ui/local-malformed-4.rs new file mode 100644 index 0000000..b913947 --- /dev/null +++ b/rtic/macros/ui/local-malformed-4.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [a = u32])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-malformed-4.stderr b/rtic/macros/ui/local-malformed-4.stderr new file mode 100644 index 0000000..0f7d9e7 --- /dev/null +++ b/rtic/macros/ui/local-malformed-4.stderr @@ -0,0 +1,5 @@ +error: malformed, expected a type + --> ui/local-malformed-4.rs:11:21 + | +11 | #[task(local = [a = u32])] + | ^ diff --git a/rtic/macros/ui/local-not-declared.rs b/rtic/macros/ui/local-not-declared.rs new file mode 100644 index 0000000..7c087e4 --- /dev/null +++ b/rtic/macros/ui/local-not-declared.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(local = [A])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-not-declared.stderr b/rtic/macros/ui/local-not-declared.stderr new file mode 100644 index 0000000..10d4b04 --- /dev/null +++ b/rtic/macros/ui/local-not-declared.stderr @@ -0,0 +1,5 @@ +error: this local resource has NOT been declared + --> ui/local-not-declared.rs:11:21 + | +11 | #[task(local = [A])] + | ^ diff --git a/rtic/macros/ui/local-pub.rs b/rtic/macros/ui/local-pub.rs new file mode 100644 index 0000000..42da4f4 --- /dev/null +++ b/rtic/macros/ui/local-pub.rs @@ -0,0 +1,15 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + pub x: u32, + } + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/local-pub.stderr b/rtic/macros/ui/local-pub.stderr new file mode 100644 index 0000000..e4814ca --- /dev/null +++ b/rtic/macros/ui/local-pub.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> ui/local-pub.rs:10:13 + | +10 | pub x: u32, + | ^ diff --git a/rtic/macros/ui/local-shared-attribute.rs b/rtic/macros/ui/local-shared-attribute.rs new file mode 100644 index 0000000..c594b5f --- /dev/null +++ b/rtic/macros/ui/local-shared-attribute.rs @@ -0,0 +1,21 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(local = [ + #[test] + a: u32 = 0, // Ok + #[test] + b, // Error + ])] + fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/local-shared-attribute.stderr b/rtic/macros/ui/local-shared-attribute.stderr new file mode 100644 index 0000000..a8130e8 --- /dev/null +++ b/rtic/macros/ui/local-shared-attribute.stderr @@ -0,0 +1,6 @@ +error: attributes are not supported here + --> ui/local-shared-attribute.rs:17:9 + | +17 | / #[test] +18 | | b, // Error + | |_________^ diff --git a/rtic/macros/ui/local-shared.rs b/rtic/macros/ui/local-shared.rs new file mode 100644 index 0000000..4e8f9f4 --- /dev/null +++ b/rtic/macros/ui/local-shared.rs @@ -0,0 +1,28 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local { + l1: u32, + l2: u32, + } + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + // l2 ok + #[idle(local = [l2])] + fn idle(cx: idle::Context) -> ! {} + + // l1 rejected (not local) + #[task(priority = 1, local = [l1])] + async fn uart0(cx: uart0::Context) {} + + // l1 rejected (not lock_free) + #[task(priority = 2, local = [l1])] + async fn uart1(cx: uart1::Context) {} +} diff --git a/rtic/macros/ui/local-shared.stderr b/rtic/macros/ui/local-shared.stderr new file mode 100644 index 0000000..fceb763 --- /dev/null +++ b/rtic/macros/ui/local-shared.stderr @@ -0,0 +1,11 @@ +error: Local resource "l1" is used by multiple tasks or collides with multiple definitions + --> ui/local-shared.rs:22:35 + | +22 | #[task(priority = 1, local = [l1])] + | ^^ + +error: Local resource "l1" is used by multiple tasks or collides with multiple definitions + --> ui/local-shared.rs:26:35 + | +26 | #[task(priority = 2, local = [l1])] + | ^^ diff --git a/rtic/macros/ui/shared-lock-free.rs b/rtic/macros/ui/shared-lock-free.rs new file mode 100644 index 0000000..b3a4b9c --- /dev/null +++ b/rtic/macros/ui/shared-lock-free.rs @@ -0,0 +1,38 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + // An exclusive, early resource + #[lock_free] + e1: u32, + + // An exclusive, late resource + #[lock_free] + e2: u32, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + // e2 ok + #[idle(shared = [e2])] + fn idle(cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); + loop {} + } + + // e1 rejected (not lock_free) + #[task(binds = UART0, priority = 1, shared = [e1])] + fn uart0(cx: uart0::Context) { + *cx.resources.e1 += 10; + } + + // e1 rejected (not lock_free) + #[task(binds = UART1, priority = 2, shared = [e1])] + fn uart1(cx: uart1::Context) {} +} diff --git a/rtic/macros/ui/shared-lock-free.stderr b/rtic/macros/ui/shared-lock-free.stderr new file mode 100644 index 0000000..51e99a0 --- /dev/null +++ b/rtic/macros/ui/shared-lock-free.stderr @@ -0,0 +1,17 @@ +error: Lock free shared resource "e1" is used by tasks at different priorities + --> ui/shared-lock-free.rs:9:9 + | +9 | e1: u32, + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> ui/shared-lock-free.rs:30:51 + | +30 | #[task(binds = UART0, priority = 1, shared = [e1])] + | ^^ + +error: Shared resource "e1" is declared lock free but used by tasks at different priorities + --> ui/shared-lock-free.rs:36:51 + | +36 | #[task(binds = UART1, priority = 2, shared = [e1])] + | ^^ diff --git a/rtic/macros/ui/shared-not-declared.rs b/rtic/macros/ui/shared-not-declared.rs new file mode 100644 index 0000000..5fef534 --- /dev/null +++ b/rtic/macros/ui/shared-not-declared.rs @@ -0,0 +1,16 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[task(shared = [A])] + async fn foo(_: foo::Context) {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} +} diff --git a/rtic/macros/ui/shared-not-declared.stderr b/rtic/macros/ui/shared-not-declared.stderr new file mode 100644 index 0000000..7c5fb32 --- /dev/null +++ b/rtic/macros/ui/shared-not-declared.stderr @@ -0,0 +1,5 @@ +error: this shared resource has NOT been declared + --> ui/shared-not-declared.rs:11:22 + | +11 | #[task(shared = [A])] + | ^ diff --git a/rtic/macros/ui/shared-pub.rs b/rtic/macros/ui/shared-pub.rs new file mode 100644 index 0000000..10351fd --- /dev/null +++ b/rtic/macros/ui/shared-pub.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared { + pub x: u32, + } +} diff --git a/rtic/macros/ui/shared-pub.stderr b/rtic/macros/ui/shared-pub.stderr new file mode 100644 index 0000000..7148893 --- /dev/null +++ b/rtic/macros/ui/shared-pub.stderr @@ -0,0 +1,5 @@ +error: this field must have inherited / private visibility + --> ui/shared-pub.rs:7:13 + | +7 | pub x: u32, + | ^ diff --git a/rtic/macros/ui/task-divergent.rs b/rtic/macros/ui/task-divergent.rs new file mode 100644 index 0000000..ffe2dc0 --- /dev/null +++ b/rtic/macros/ui/task-divergent.rs @@ -0,0 +1,9 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + async fn foo(_: foo::Context) -> ! { + loop {} + } +} diff --git a/rtic/macros/ui/task-divergent.stderr b/rtic/macros/ui/task-divergent.stderr new file mode 100644 index 0000000..dd00208 --- /dev/null +++ b/rtic/macros/ui/task-divergent.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-divergent.rs:6:14 + | +6 | async fn foo(_: foo::Context) -> ! { + | ^^^ diff --git a/rtic/macros/ui/task-double-local.rs b/rtic/macros/ui/task-double-local.rs new file mode 100644 index 0000000..c5277e2 --- /dev/null +++ b/rtic/macros/ui/task-double-local.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(local = [A], local = [B])] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-double-local.stderr b/rtic/macros/ui/task-double-local.stderr new file mode 100644 index 0000000..91ed844 --- /dev/null +++ b/rtic/macros/ui/task-double-local.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/task-double-local.rs:5:25 + | +5 | #[task(local = [A], local = [B])] + | ^^^^^ diff --git a/rtic/macros/ui/task-double-priority.rs b/rtic/macros/ui/task-double-priority.rs new file mode 100644 index 0000000..5c8bd5b --- /dev/null +++ b/rtic/macros/ui/task-double-priority.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(priority = 1, priority = 2)] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-double-priority.stderr b/rtic/macros/ui/task-double-priority.stderr new file mode 100644 index 0000000..b3c814a --- /dev/null +++ b/rtic/macros/ui/task-double-priority.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/task-double-priority.rs:5:26 + | +5 | #[task(priority = 1, priority = 2)] + | ^^^^^^^^ diff --git a/rtic/macros/ui/task-double-shared.rs b/rtic/macros/ui/task-double-shared.rs new file mode 100644 index 0000000..f9812d3 --- /dev/null +++ b/rtic/macros/ui/task-double-shared.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(shared = [A], shared = [B])] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-double-shared.stderr b/rtic/macros/ui/task-double-shared.stderr new file mode 100644 index 0000000..bb90212 --- /dev/null +++ b/rtic/macros/ui/task-double-shared.stderr @@ -0,0 +1,5 @@ +error: argument appears more than once + --> ui/task-double-shared.rs:5:26 + | +5 | #[task(shared = [A], shared = [B])] + | ^^^^^^ diff --git a/rtic/macros/ui/task-idle.rs b/rtic/macros/ui/task-idle.rs new file mode 100644 index 0000000..353c782 --- /dev/null +++ b/rtic/macros/ui/task-idle.rs @@ -0,0 +1,13 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[idle] + fn foo(_: foo::Context) -> ! { + loop {} + } + + // name collides with `#[idle]` function + #[task] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-idle.stderr b/rtic/macros/ui/task-idle.stderr new file mode 100644 index 0000000..4ccc113 --- /dev/null +++ b/rtic/macros/ui/task-idle.stderr @@ -0,0 +1,5 @@ +error: this identifier has already been used + --> ui/task-idle.rs:12:14 + | +12 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-init.rs b/rtic/macros/ui/task-init.rs new file mode 100644 index 0000000..e58fdce --- /dev/null +++ b/rtic/macros/ui/task-init.rs @@ -0,0 +1,17 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn foo(_: foo::Context) -> (Shared, Local) {} + + // name collides with `#[idle]` function + #[task] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-init.stderr b/rtic/macros/ui/task-init.stderr new file mode 100644 index 0000000..161e194 --- /dev/null +++ b/rtic/macros/ui/task-init.stderr @@ -0,0 +1,5 @@ +error: this identifier has already been used + --> ui/task-init.rs:16:14 + | +16 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-interrupt.rs b/rtic/macros/ui/task-interrupt.rs new file mode 100644 index 0000000..3d50bd8 --- /dev/null +++ b/rtic/macros/ui/task-interrupt.rs @@ -0,0 +1,10 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = SysTick)] + fn foo(_: foo::Context) {} + + #[task] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-interrupt.stderr b/rtic/macros/ui/task-interrupt.stderr new file mode 100644 index 0000000..087b6c6 --- /dev/null +++ b/rtic/macros/ui/task-interrupt.stderr @@ -0,0 +1,5 @@ +error: this task is defined multiple times + --> ui/task-interrupt.rs:9:14 + | +9 | async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-no-context.rs b/rtic/macros/ui/task-no-context.rs new file mode 100644 index 0000000..55e8c3b --- /dev/null +++ b/rtic/macros/ui/task-no-context.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + async fn foo() {} +} diff --git a/rtic/macros/ui/task-no-context.stderr b/rtic/macros/ui/task-no-context.stderr new file mode 100644 index 0000000..62147aa --- /dev/null +++ b/rtic/macros/ui/task-no-context.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-no-context.rs:6:14 + | +6 | async fn foo() {} + | ^^^ diff --git a/rtic/macros/ui/task-priority-too-high.rs b/rtic/macros/ui/task-priority-too-high.rs new file mode 100644 index 0000000..f33ba56 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-high.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(priority = 256)] + async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-priority-too-high.stderr b/rtic/macros/ui/task-priority-too-high.stderr new file mode 100644 index 0000000..5790c88 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-high.stderr @@ -0,0 +1,5 @@ +error: this literal must be in the range 0...255 + --> ui/task-priority-too-high.rs:5:23 + | +5 | #[task(priority = 256)] + | ^^^ diff --git a/rtic/macros/ui/task-priority-too-low.rs b/rtic/macros/ui/task-priority-too-low.rs new file mode 100644 index 0000000..16e0557 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-low.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task(binds = UART0, priority = 0)] + fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-priority-too-low.stderr b/rtic/macros/ui/task-priority-too-low.stderr new file mode 100644 index 0000000..85c8660 --- /dev/null +++ b/rtic/macros/ui/task-priority-too-low.stderr @@ -0,0 +1,5 @@ +error: hardware tasks are not allowed to be at priority 0 + --> ui/task-priority-too-low.rs:5:38 + | +5 | #[task(binds = UART0, priority = 0)] + | ^ diff --git a/rtic/macros/ui/task-pub.rs b/rtic/macros/ui/task-pub.rs new file mode 100644 index 0000000..1ae533f --- /dev/null +++ b/rtic/macros/ui/task-pub.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + pub async fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-pub.stderr b/rtic/macros/ui/task-pub.stderr new file mode 100644 index 0000000..7b9813d --- /dev/null +++ b/rtic/macros/ui/task-pub.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-pub.rs:6:18 + | +6 | pub async fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-unsafe.rs b/rtic/macros/ui/task-unsafe.rs new file mode 100644 index 0000000..a8383ef --- /dev/null +++ b/rtic/macros/ui/task-unsafe.rs @@ -0,0 +1,7 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[task] + async unsafe fn foo(_: foo::Context) {} +} diff --git a/rtic/macros/ui/task-unsafe.stderr b/rtic/macros/ui/task-unsafe.stderr new file mode 100644 index 0000000..90ac76f --- /dev/null +++ b/rtic/macros/ui/task-unsafe.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-unsafe.rs:6:21 + | +6 | async unsafe fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/macros/ui/task-zero-prio.rs b/rtic/macros/ui/task-zero-prio.rs new file mode 100644 index 0000000..de3c86f --- /dev/null +++ b/rtic/macros/ui/task-zero-prio.rs @@ -0,0 +1,19 @@ +#![no_main] + +#[rtic_macros::mock_app(device = mock)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) {} + + #[task(priority = 0)] + fn foo(_: foo::Context) {} + + #[idle] + fn idle(_: idle::Context) -> ! {} +} diff --git a/rtic/macros/ui/task-zero-prio.stderr b/rtic/macros/ui/task-zero-prio.stderr new file mode 100644 index 0000000..1ab9aab --- /dev/null +++ b/rtic/macros/ui/task-zero-prio.stderr @@ -0,0 +1,5 @@ +error: this task handler must have type signature `async fn(foo::Context, ..)` + --> ui/task-zero-prio.rs:15:8 + | +15 | fn foo(_: foo::Context) {} + | ^^^ diff --git a/rtic/rust-toolchain.toml b/rtic/rust-toolchain.toml new file mode 100644 index 0000000..e28b55d --- /dev/null +++ b/rtic/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] +targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/rtic/src/export.rs b/rtic/src/export.rs new file mode 100644 index 0000000..cdca972 --- /dev/null +++ b/rtic/src/export.rs @@ -0,0 +1,324 @@ +pub use bare_metal::CriticalSection; +pub use cortex_m::{ + asm::nop, + asm::wfi, + interrupt, + peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, + Peripherals, +}; +//pub use portable_atomic as atomic; +pub use atomic_polyfill as atomic; + +pub mod executor; + +/// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). +/// It needs to be large enough to cover all the relevant interrupts in use. +/// For M0/M0+ there are only 32 interrupts so we only need one u32 value. +/// For M23 there can be as many as 480 interrupts. +/// Rather than providing space for all possible interrupts, we just detect the highest interrupt in +/// use at compile time and allocate enough u32 chunks to cover them. +#[derive(Copy, Clone)] +pub struct Mask([u32; M]); + +impl core::ops::BitOrAssign for Mask { + fn bitor_assign(&mut self, rhs: Self) { + for i in 0..M { + self.0[i] |= rhs.0[i]; + } + } +} + +#[cfg(not(have_basepri))] +impl Mask { + /// Set a bit inside a Mask. + const fn set_bit(mut self, bit: u32) -> Self { + let block = bit / 32; + + if block as usize >= M { + panic!("Generating masks for thumbv6/thumbv8m.base failed! Are you compiling for thumbv6 on an thumbv7 MCU or using an unsupported thumbv8m.base MCU?"); + } + + let offset = bit - (block * 32); + self.0[block as usize] |= 1 << offset; + self + } +} + +#[cfg(have_basepri)] +use cortex_m::register::basepri; + +#[cfg(have_basepri)] +#[inline(always)] +pub fn run(priority: u8, f: F) +where + F: FnOnce(), +{ + if priority == 1 { + // If the priority of this interrupt is `1` then BASEPRI can only be `0` + f(); + unsafe { basepri::write(0) } + } else { + let initial = basepri::read(); + f(); + unsafe { basepri::write(initial) } + } +} + +#[cfg(not(have_basepri))] +#[inline(always)] +pub fn run(_priority: u8, f: F) +where + F: FnOnce(), +{ + f(); +} + +/// Const helper to check architecture +pub const fn have_basepri() -> bool { + #[cfg(have_basepri)] + { + true + } + + #[cfg(not(have_basepri))] + { + false + } +} + +#[inline(always)] +pub fn assert_send() +where + T: Send, +{ +} + +#[inline(always)] +pub fn assert_sync() +where + T: Sync, +{ +} + +/// Lock implementation using BASEPRI and global Critical Section (CS) +/// +/// # Safety +/// +/// The system ceiling is raised from current to ceiling +/// by either +/// - raising the BASEPRI to the ceiling value, or +/// - disable all interrupts in case we want to +/// mask interrupts with maximum priority +/// +/// Dereferencing a raw pointer inside CS +/// +/// The priority.set/priority.get can safely be outside the CS +/// as being a context local cell (not affected by preemptions). +/// It is merely used in order to omit masking in case current +/// priority is current priority >= ceiling. +/// +/// Lock Efficiency: +/// Experiments validate (sub)-zero cost for CS implementation +/// (Sub)-zero as: +/// - Either zero OH (lock optimized out), or +/// - Amounting to an optimal assembly implementation +/// - The BASEPRI value is folded to a constant at compile time +/// - CS entry, single assembly instruction to write BASEPRI +/// - CS exit, single assembly instruction to write BASEPRI +/// - priority.set/get optimized out (their effect not) +/// - On par or better than any handwritten implementation of SRP +/// +/// Limitations: +/// The current implementation reads/writes BASEPRI once +/// even in some edge cases where this may be omitted. +/// Total OH of per task is max 2 clock cycles, negligible in practice +/// but can in theory be fixed. +/// +#[cfg(have_basepri)] +#[inline(always)] +pub unsafe fn lock( + ptr: *mut T, + ceiling: u8, + nvic_prio_bits: u8, + _mask: &[Mask; 3], + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling == (1 << nvic_prio_bits) { + let r = interrupt::free(|_| f(&mut *ptr)); + r + } else { + let current = basepri::read(); + basepri::write(logical2hw(ceiling, nvic_prio_bits)); + let r = f(&mut *ptr); + basepri::write(current); + r + } +} + +/// Lock implementation using interrupt masking +/// +/// # Safety +/// +/// The system ceiling is raised from current to ceiling +/// by computing a 32 bit `mask` (1 bit per interrupt) +/// 1: ceiling >= priority > current +/// 0: else +/// +/// On CS entry, `clear_enable_mask(mask)` disables interrupts +/// On CS exit, `set_enable_mask(mask)` re-enables interrupts +/// +/// The priority.set/priority.get can safely be outside the CS +/// as being a context local cell (not affected by preemptions). +/// It is merely used in order to omit masking in case +/// current priority >= ceiling. +/// +/// Dereferencing a raw pointer is done safely inside the CS +/// +/// Lock Efficiency: +/// Early experiments validate (sub)-zero cost for CS implementation +/// (Sub)-zero as: +/// - Either zero OH (lock optimized out), or +/// - Amounting to an optimal assembly implementation +/// - if ceiling == (1 << nvic_prio_bits) +/// - we execute the closure in a global critical section (interrupt free) +/// - CS entry cost, single write to core register +/// - CS exit cost, single write to core register +/// else +/// - The `mask` value is folded to a constant at compile time +/// - CS entry, single write of the 32 bit `mask` to the `icer` register +/// - CS exit, single write of the 32 bit `mask` to the `iser` register +/// - priority.set/get optimized out (their effect not) +/// - On par or better than any hand written implementation of SRP +/// +/// Limitations: +/// Current implementation does not allow for tasks with shared resources +/// to be bound to exception handlers, as these cannot be masked in HW. +/// +/// Possible solutions: +/// - Mask exceptions by global critical sections (interrupt::free) +/// - Temporary lower exception priority +/// +/// These possible solutions are set goals for future work +#[cfg(not(have_basepri))] +#[inline(always)] +pub unsafe fn lock( + ptr: *mut T, + ceiling: u8, + _nvic_prio_bits: u8, + masks: &[Mask; 3], + f: impl FnOnce(&mut T) -> R, +) -> R { + if ceiling >= 4 { + // safe to manipulate outside critical section + // execute closure under protection of raised system ceiling + + // safe to manipulate outside critical section + interrupt::free(|_| f(&mut *ptr)) + } else { + // safe to manipulate outside critical section + let mask = compute_mask(0, ceiling, masks); + clear_enable_mask(mask); + + // execute closure under protection of raised system ceiling + let r = f(&mut *ptr); + + set_enable_mask(mask); + + // safe to manipulate outside critical section + r + } +} + +#[cfg(not(have_basepri))] +#[inline(always)] +fn compute_mask(from_prio: u8, to_prio: u8, masks: &[Mask; 3]) -> Mask { + let mut res = Mask([0; M]); + masks[from_prio as usize..to_prio as usize] + .iter() + .for_each(|m| res |= *m); + res +} + +// enables interrupts +#[cfg(not(have_basepri))] +#[inline(always)] +unsafe fn set_enable_mask(mask: Mask) { + for i in 0..M { + // This check should involve compile time constants and be optimized out. + if mask.0[i] != 0 { + (*NVIC::PTR).iser[i].write(mask.0[i]); + } + } +} + +// disables interrupts +#[cfg(not(have_basepri))] +#[inline(always)] +unsafe fn clear_enable_mask(mask: Mask) { + for i in 0..M { + // This check should involve compile time constants and be optimized out. + if mask.0[i] != 0 { + (*NVIC::PTR).icer[i].write(mask.0[i]); + } + } +} + +#[inline] +#[must_use] +pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { + ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) +} + +#[cfg(have_basepri)] +pub const fn create_mask(_: [u32; N]) -> Mask { + Mask([0; M]) +} + +#[cfg(not(have_basepri))] +pub const fn create_mask(list_of_shifts: [u32; N]) -> Mask { + let mut mask = Mask([0; M]); + let mut i = 0; + + while i < N { + let shift = list_of_shifts[i]; + i += 1; + mask = mask.set_bit(shift); + } + + mask +} + +#[cfg(have_basepri)] +pub const fn compute_mask_chunks(_: [u32; L]) -> usize { + 0 +} + +/// Compute the number of u32 chunks needed to store the Mask value. +/// On M0, M0+ this should always end up being 1. +/// On M23 we will pick a number that allows us to store the highest index used by the code. +/// This means the amount of overhead will vary based on the actually interrupts used by the code. +#[cfg(not(have_basepri))] +pub const fn compute_mask_chunks(ids: [u32; L]) -> usize { + let mut max: usize = 0; + let mut i = 0; + + while i < L { + let id = ids[i] as usize; + i += 1; + + if id > max { + max = id; + } + } + (max + 32) / 32 +} + +#[cfg(have_basepri)] +pub const fn no_basepri_panic() { + // For non-v6 all is fine +} + +#[cfg(not(have_basepri))] +pub const fn no_basepri_panic() { + panic!("Exceptions with shared resources are not allowed when compiling for thumbv6 or thumbv8m.base. Use local resources or `#[lock_free]` shared resources"); +} diff --git a/rtic/src/export/executor.rs b/rtic/src/export/executor.rs new file mode 100644 index 0000000..e64cc43 --- /dev/null +++ b/rtic/src/export/executor.rs @@ -0,0 +1,110 @@ +use super::atomic::{AtomicBool, Ordering}; +use core::{ + cell::UnsafeCell, + future::Future, + mem::{self, MaybeUninit}, + pin::Pin, + task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, +}; + +static WAKER_VTABLE: RawWakerVTable = + RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); + +unsafe fn waker_clone(p: *const ()) -> RawWaker { + RawWaker::new(p, &WAKER_VTABLE) +} + +unsafe fn waker_wake(p: *const ()) { + // The only thing we need from a waker is the function to call to pend the async + // dispatcher. + let f: fn() = mem::transmute(p); + f(); +} + +unsafe fn waker_drop(_: *const ()) { + // nop +} + +//============ +// AsyncTaskExecutor + +/// Executor for an async task. +pub struct AsyncTaskExecutor { + // `task` is protected by the `running` flag. + task: UnsafeCell>, + running: AtomicBool, + pending: AtomicBool, +} + +unsafe impl Sync for AsyncTaskExecutor {} + +impl AsyncTaskExecutor { + /// Create a new executor. + #[inline(always)] + pub const fn new() -> Self { + Self { + task: UnsafeCell::new(MaybeUninit::uninit()), + running: AtomicBool::new(false), + pending: AtomicBool::new(false), + } + } + + /// Check if there is an active task in the executor. + #[inline(always)] + pub fn is_running(&self) -> bool { + self.running.load(Ordering::Relaxed) + } + + /// Checks if a waker has pended the executor and simultaneously clears the flag. + #[inline(always)] + fn check_and_clear_pending(&self) -> bool { + // Ordering::Acquire to enforce that update of task is visible to poll + self.pending + .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + } + + // Used by wakers to indicate that the executor needs to run. + #[inline(always)] + pub fn set_pending(&self) { + self.pending.store(true, Ordering::Release); + } + + /// Spawn a future + #[inline(always)] + pub fn spawn(&self, future: impl Fn() -> F) -> bool { + // Try to reserve the executor for a future. + if self + .running + .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) + .is_ok() + { + // This unsafe is protected by `running` being false and the atomic setting it to true. + unsafe { + self.task.get().write(MaybeUninit::new(future())); + } + self.set_pending(); + + true + } else { + false + } + } + + /// Poll the future in the executor. + #[inline(always)] + pub fn poll(&self, wake: fn()) { + if self.is_running() && self.check_and_clear_pending() { + let waker = unsafe { Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)) }; + let mut cx = Context::from_waker(&waker); + let future = unsafe { Pin::new_unchecked(&mut *(self.task.get() as *mut F)) }; + + match future.poll(&mut cx) { + Poll::Ready(_) => { + self.running.store(false, Ordering::Release); + } + Poll::Pending => {} + } + } + } +} diff --git a/rtic/src/lib.rs b/rtic/src/lib.rs new file mode 100644 index 0000000..e8b8140 --- /dev/null +++ b/rtic/src/lib.rs @@ -0,0 +1,121 @@ +//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. +//! +//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the +//! library is `rtic`. +//! +//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic +//! +//! The user level documentation can be found [here]. +//! +//! [here]: https://rtic.rs +//! +//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports +//! section), which is the main component of the framework. +//! +//! # Minimum Supported Rust Version (MSRV) +//! +//! This crate is compiled and tested with the latest toolchain (rolling) as of the release date. +//! If you run into compilation errors, try the latest stable release of the rust toolchain. +//! +//! # Semantic Versioning +//! +//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics +//! require a *semver bump* (since 1.0.0 a new major version release), with the exception of breaking changes +//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch +//! release. +//! +//! [SemVer]: https://semver.org/spec/v2.0.0.html + +#![deny(missing_docs)] +#![deny(rust_2021_compatibility)] +#![deny(rust_2018_compatibility)] +#![deny(rust_2018_idioms)] +#![no_std] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", + html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" +)] +//deny_warnings_placeholder_for_ci +#![allow(clippy::inline_always)] + +use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; +pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; +pub use rtic_macros::app; +pub use rtic_monotonic::{self, Monotonic}; + +/// module `mutex::prelude` provides `Mutex` and multi-lock variants. Recommended over `mutex_prelude` +pub mod mutex { + pub use rtic_core::prelude; + pub use rtic_core::Mutex; +} + +#[doc(hidden)] +pub mod export; + +/// Sets the given `interrupt` as pending +/// +/// This is a convenience function around +/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) +pub fn pend(interrupt: I) +where + I: InterruptNumber, +{ + NVIC::pend(interrupt); +} + +use core::cell::UnsafeCell; + +/// Internal replacement for `static mut T` +/// +/// Used to represent RTIC Resources +/// +/// Soundness: +/// 1) Unsafe API for internal use only +/// 2) ``get_mut(&self) -> *mut T`` +/// returns a raw mutable pointer to the inner T +/// casting to &mut T is under control of RTIC +/// RTIC ensures &mut T to be unique under Rust aliasing rules. +/// +/// Implementation uses the underlying ``UnsafeCell`` +/// self.0.get() -> *mut T +/// +/// 3) get(&self) -> *const T +/// returns a raw immutable (const) pointer to the inner T +/// casting to &T is under control of RTIC +/// RTIC ensures &T to be shared under Rust aliasing rules. +/// +/// Implementation uses the underlying ``UnsafeCell`` +/// self.0.get() -> *mut T, demoted to *const T +/// +#[repr(transparent)] +pub struct RacyCell(UnsafeCell); + +impl RacyCell { + /// Create a ``RacyCell`` + #[inline(always)] + pub const fn new(value: T) -> Self { + RacyCell(UnsafeCell::new(value)) + } + + /// Get `*mut T` + /// + /// # Safety + /// + /// See documentation notes for [`RacyCell`] + #[inline(always)] + pub unsafe fn get_mut(&self) -> *mut T { + self.0.get() + } + + /// Get `*const T` + /// + /// # Safety + /// + /// See documentation notes for [`RacyCell`] + #[inline(always)] + pub unsafe fn get(&self) -> *const T { + self.0.get() + } +} + +unsafe impl Sync for RacyCell {} diff --git a/rtic/tests/tests.rs b/rtic/tests/tests.rs new file mode 100644 index 0000000..9fb88a1 --- /dev/null +++ b/rtic/tests/tests.rs @@ -0,0 +1,7 @@ +use trybuild::TestCases; + +#[test] +fn ui() { + let t = TestCases::new(); + t.compile_fail("ui/*.rs"); +} diff --git a/rtic/ui/exception-invalid.rs b/rtic/ui/exception-invalid.rs new file mode 100644 index 0000000..4f8e943 --- /dev/null +++ b/rtic/ui/exception-invalid.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic::app(device = lm3s6965)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task(binds = NonMaskableInt)] + fn nmi(_: nmi::Context) {} +} diff --git a/rtic/ui/exception-invalid.stderr b/rtic/ui/exception-invalid.stderr new file mode 100644 index 0000000..3212368 --- /dev/null +++ b/rtic/ui/exception-invalid.stderr @@ -0,0 +1,5 @@ +error: only exceptions with configurable priority can be used as hardware tasks + --> $DIR/exception-invalid.rs:17:8 + | +17 | fn nmi(_: nmi::Context) {} + | ^^^ diff --git a/rtic/ui/extern-interrupt-not-enough.rs b/rtic/ui/extern-interrupt-not-enough.rs new file mode 100644 index 0000000..94c8ee1 --- /dev/null +++ b/rtic/ui/extern-interrupt-not-enough.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic::app(device = lm3s6965)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task] + async fn a(_: a::Context) {} +} diff --git a/rtic/ui/extern-interrupt-not-enough.stderr b/rtic/ui/extern-interrupt-not-enough.stderr new file mode 100644 index 0000000..e6c01b9 --- /dev/null +++ b/rtic/ui/extern-interrupt-not-enough.stderr @@ -0,0 +1,5 @@ +error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) + --> ui/extern-interrupt-not-enough.rs:17:14 + | +17 | async fn a(_: a::Context) {} + | ^ diff --git a/rtic/ui/extern-interrupt-used.rs b/rtic/ui/extern-interrupt-used.rs new file mode 100644 index 0000000..42de4c0 --- /dev/null +++ b/rtic/ui/extern-interrupt-used.rs @@ -0,0 +1,18 @@ +#![no_main] + +#[rtic::app(device = lm3s6965, dispatchers = [UART0])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task(binds = UART0)] + fn a(_: a::Context) {} +} diff --git a/rtic/ui/extern-interrupt-used.stderr b/rtic/ui/extern-interrupt-used.stderr new file mode 100644 index 0000000..7565739 --- /dev/null +++ b/rtic/ui/extern-interrupt-used.stderr @@ -0,0 +1,5 @@ +error: dispatcher interrupts can't be used as hardware tasks + --> $DIR/extern-interrupt-used.rs:16:20 + | +16 | #[task(binds = UART0)] + | ^^^^^ diff --git a/rtic/ui/task-priority-too-high.rs b/rtic/ui/task-priority-too-high.rs new file mode 100644 index 0000000..44e4a25 --- /dev/null +++ b/rtic/ui/task-priority-too-high.rs @@ -0,0 +1,44 @@ +#![no_main] + +#[rtic::app(device = lm3s6965)] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } + + #[task(binds = GPIOA, priority = 1)] + fn gpioa(_: gpioa::Context) {} + + #[task(binds = GPIOB, priority = 2)] + fn gpiob(_: gpiob::Context) {} + + #[task(binds = GPIOC, priority = 3)] + fn gpioc(_: gpioc::Context) {} + + #[task(binds = GPIOD, priority = 4)] + fn gpiod(_: gpiod::Context) {} + + #[task(binds = GPIOE, priority = 5)] + fn gpioe(_: gpioe::Context) {} + + #[task(binds = UART0, priority = 6)] + fn uart0(_: uart0::Context) {} + + #[task(binds = UART1, priority = 7)] + fn uart1(_: uart1::Context) {} + + // OK, this is the maximum priority supported by the device + #[task(binds = SSI0, priority = 8)] + fn ssi0(_: ssi0::Context) {} + + // this value is too high! + #[task(binds = I2C0, priority = 9)] + fn i2c0(_: i2c0::Context) {} +} diff --git a/rtic/ui/task-priority-too-high.stderr b/rtic/ui/task-priority-too-high.stderr new file mode 100644 index 0000000..1256377 --- /dev/null +++ b/rtic/ui/task-priority-too-high.stderr @@ -0,0 +1,15 @@ +warning: unused variable: `cx` + --> ui/task-priority-too-high.rs:12:13 + | +12 | fn init(cx: init::Context) -> (Shared, Local) { + | ^^ help: if this is intentional, prefix it with an underscore: `_cx` + | + = note: `#[warn(unused_variables)]` on by default + +error[E0080]: evaluation of constant value failed + --> ui/task-priority-too-high.rs:3:1 + | +3 | #[rtic::app(device = lm3s6965)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Maximum priority used by interrupt vector 'I2C0' is more than supported by hardware', $DIR/ui/task-priority-too-high.rs:3:1 + | + = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/rtic/ui/unknown-interrupt.rs b/rtic/ui/unknown-interrupt.rs new file mode 100644 index 0000000..3c6c69f --- /dev/null +++ b/rtic/ui/unknown-interrupt.rs @@ -0,0 +1,15 @@ +#![no_main] + +#[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] +mod app { + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(cx: init::Context) -> (Shared, Local) { + (Shared {}, Local {}) + } +} diff --git a/rtic/ui/unknown-interrupt.stderr b/rtic/ui/unknown-interrupt.stderr new file mode 100644 index 0000000..c7d3269 --- /dev/null +++ b/rtic/ui/unknown-interrupt.stderr @@ -0,0 +1,5 @@ +error[E0599]: no variant or associated item named `UnknownInterrupt` found for enum `Interrupt` in the current scope + --> $DIR/unknown-interrupt.rs:3:47 + | +3 | #[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] + | ^^^^^^^^^^^^^^^^ variant or associated item not found in `Interrupt` diff --git a/rtic/ui/v6m-interrupt-not-enough.rs_no b/rtic/ui/v6m-interrupt-not-enough.rs_no new file mode 100644 index 0000000..3fbf3cf --- /dev/null +++ b/rtic/ui/v6m-interrupt-not-enough.rs_no @@ -0,0 +1,54 @@ +//! v6m-interrupt-not-enough.rs_no (not run atm) +//! +//! Expected behavior: +//! should pass +//! > cargo build --example m0_perf_err --target thumbv7m-none-eabi --release +//! +//! should fail +//! > cargo build --example m0_perf_err --target thumbv6m-none-eabi --release +//! Compiling cortex-m-rtic v1.0.0 (/home/pln/rust/rtic/cortex-m-rtic) +//! error[E0308]: mismatched types +//! --> examples/m0_perf_err.rs:25:1 +//! | +//! 25 | #[rtic::app(device = lm3s6965)] +//! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 4 elements, found one with 5 elements +//! | +//! = note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info) + +#![deny(unsafe_code)] +#![deny(warnings)] +#![no_main] +#![no_std] + +use panic_semihosting as _; + +#[rtic::app(device = lm3s6965)] +mod app { + + use cortex_m_semihosting::debug; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { + (Shared {}, Local {}, init::Monotonics()) + } + + #[inline(never)] + #[idle] + fn idle(_cx: idle::Context) -> ! { + debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator + + loop { + cortex_m::asm::nop(); + } + } + + // priority to high for v6m + #[task(binds = GPIOA, priority = 5)] + fn t0(_cx: t0::Context) {} +} diff --git a/rtic/xtask/Cargo.toml b/rtic/xtask/Cargo.toml new file mode 100644 index 0000000..f1c468e --- /dev/null +++ b/rtic/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2018" + +[dependencies] +anyhow = "1.0.43" +os_pipe = "1.1.2" +structopt = "0.3.22" diff --git a/rtic/xtask/src/build.rs b/rtic/xtask/src/build.rs new file mode 100644 index 0000000..148a9fd --- /dev/null +++ b/rtic/xtask/src/build.rs @@ -0,0 +1,13 @@ +use std::{fs, path::Path}; + +const HEX_BUILD_ROOT: &str = "ci/builds"; + +/// make sure we're starting with a clean,but existing slate +pub fn init_build_dir() -> anyhow::Result<()> { + if Path::new(HEX_BUILD_ROOT).exists() { + fs::remove_dir_all(HEX_BUILD_ROOT) + .map_err(|_| anyhow::anyhow!("Could not clear out directory: {}", HEX_BUILD_ROOT))?; + } + fs::create_dir_all(HEX_BUILD_ROOT) + .map_err(|_| anyhow::anyhow!("Could not create directory: {}", HEX_BUILD_ROOT)) +} diff --git a/rtic/xtask/src/command.rs b/rtic/xtask/src/command.rs new file mode 100644 index 0000000..4e90369 --- /dev/null +++ b/rtic/xtask/src/command.rs @@ -0,0 +1,172 @@ +use crate::{RunResult, TestRunError}; +use core::fmt; +use os_pipe::pipe; +use std::{fs::File, io::Read, process::Command}; + +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BuildMode { + Release, + Debug, +} + +#[derive(Debug)] +pub enum CargoCommand<'a> { + Run { + example: &'a str, + target: &'a str, + features: Option<&'a str>, + mode: BuildMode, + }, + BuildAll { + target: &'a str, + features: Option<&'a str>, + mode: BuildMode, + }, + // Size { + // example_paths: Vec<&'a Path>, + // }, + // Clean, +} + +impl<'a> CargoCommand<'a> { + fn name(&self) -> &str { + match self { + CargoCommand::Run { .. } => "run", + // CargoCommand::Size { example_paths: _ } => "rust-size", + CargoCommand::BuildAll { .. } => "build", + } + } + + pub fn args(&self) -> Vec<&str> { + match self { + CargoCommand::Run { + example, + target, + features, + mode, + } => { + let mut args = vec![ + "+nightly", + self.name(), + "--example", + example, + "--target", + target, + "--features", + "test-critical-section", + ]; + + if let Some(feature_name) = features { + args.extend_from_slice(&["--features", feature_name]); + } + if let Some(flag) = mode.to_flag() { + args.push(flag); + } + args + } + CargoCommand::BuildAll { + target, + features, + mode, + } => { + let mut args = vec![ + "+nightly", + self.name(), + "--examples", + "--target", + target, + "--features", + "test-critical-section", + ]; + + if let Some(feature_name) = features { + args.extend_from_slice(&["--features", feature_name]); + } + if let Some(flag) = mode.to_flag() { + args.push(flag); + } + args + } // CargoCommand::Size { example_paths } => { + // example_paths.iter().map(|p| p.to_str().unwrap()).collect() + // } + } + } + + pub fn command(&self) -> &str { + match self { + // we need to cheat a little here: + // `cargo size` can't be ran on multiple files, so we're using `rust-size` instead – + // which isn't a command that starts wizh `cargo`. So we're sneakily swapping them out :) + // CargoCommand::Size { .. } => "rust-size", + _ => "cargo", + } + } +} + +impl BuildMode { + pub fn to_flag(&self) -> Option<&str> { + match self { + BuildMode::Release => Some("--release"), + BuildMode::Debug => None, + } + } +} + +impl fmt::Display for BuildMode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let cmd = match self { + BuildMode::Release => "release", + BuildMode::Debug => "debug", + }; + + write!(f, "{}", cmd) + } +} + +pub fn run_command(command: &CargoCommand) -> anyhow::Result { + let (mut reader, writer) = pipe()?; + println!("👟 {} {}", command.command(), command.args().join(" ")); + + let mut handle = Command::new(command.command()) + .args(command.args()) + .stdout(writer) + .spawn()?; + + // retrieve output and clean up + let mut output = String::new(); + reader.read_to_string(&mut output)?; + let exit_status = handle.wait()?; + + Ok(RunResult { + exit_status, + output, + }) +} + +/// Check if `run` was successful. +/// returns Ok in case the run went as expected, +/// Err otherwise +pub fn run_successful(run: &RunResult, expected_output_file: String) -> Result<(), TestRunError> { + let mut file_handle = + File::open(expected_output_file.clone()).map_err(|_| TestRunError::FileError { + file: expected_output_file.clone(), + })?; + let mut expected_output = String::new(); + file_handle + .read_to_string(&mut expected_output) + .map_err(|_| TestRunError::FileError { + file: expected_output_file.clone(), + })?; + + if expected_output != run.output { + Err(TestRunError::FileCmpError { + expected: expected_output.clone(), + got: run.output.clone(), + }) + } else if !run.exit_status.success() { + Err(TestRunError::CommandError(run.clone())) + } else { + Ok(()) + } +} diff --git a/rtic/xtask/src/main.rs b/rtic/xtask/src/main.rs new file mode 100644 index 0000000..7eada91 --- /dev/null +++ b/rtic/xtask/src/main.rs @@ -0,0 +1,176 @@ +mod build; +mod command; + +use anyhow::bail; +use core::fmt; +use std::{ + error::Error, + ffi::OsString, + path::{Path, PathBuf}, + process, + process::ExitStatus, + str, +}; +use structopt::StructOpt; + +use crate::{ + build::init_build_dir, + command::{run_command, run_successful, BuildMode, CargoCommand}, +}; + +const ARMV6M: &str = "thumbv6m-none-eabi"; +const ARMV7M: &str = "thumbv7m-none-eabi"; + +#[derive(Debug, StructOpt)] +struct Options { + #[structopt(short, long)] + target: String, +} + +#[derive(Debug, Clone)] +pub struct RunResult { + exit_status: ExitStatus, + output: String, +} + +#[derive(Debug)] +pub enum TestRunError { + FileCmpError { expected: String, got: String }, + FileError { file: String }, + PathConversionError(OsString), + CommandError(RunResult), + IncompatibleCommand, +} + +impl fmt::Display for TestRunError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TestRunError::FileCmpError { expected, got } => { + writeln!(f, "Differing output in files.")?; + writeln!(f, "")?; + writeln!(f, "Expected:")?; + writeln!(f, "{}", expected)?; + writeln!(f, "")?; + writeln!(f, "Got:")?; + write!(f, "{}", got) + } + TestRunError::FileError { file } => { + write!(f, "File error on: {}", file) + } + TestRunError::CommandError(e) => { + write!( + f, + "Command failed with exit status {}: {}", + e.exit_status, e.output + ) + } + TestRunError::PathConversionError(p) => { + write!(f, "Can't convert path from `OsString` to `String`: {:?}", p) + } + TestRunError::IncompatibleCommand => { + write!(f, "Can't run that command in this context") + } + } + } +} + +impl Error for TestRunError {} + +fn main() -> anyhow::Result<()> { + // if there's an `xtask` folder, we're *probably* at the root of this repo (we can't just + // check the name of `env::current_dir()` because people might clone it into a different name) + let probably_running_from_repo_root = Path::new("./xtask").exists(); + if probably_running_from_repo_root == false { + bail!("xtasks can only be executed from the root of the `cortex-m-rtic` repository"); + } + + let targets = [ARMV7M, ARMV6M]; + + let examples: Vec<_> = std::fs::read_dir("./examples")? + .filter_map(|p| p.ok()) + .map(|p| p.path()) + .filter(|p| p.display().to_string().ends_with(".rs")) + .map(|path| path.file_stem().unwrap().to_str().unwrap().to_string()) + .collect(); + + println!("examples: {examples:?}"); + + let opts = Options::from_args(); + let target = &opts.target; + + init_build_dir()?; + + if target == "all" { + for t in targets { + run_test(t, &examples)?; + } + } else if targets.contains(&target.as_str()) { + run_test(&target, &examples)?; + } else { + eprintln!( + "The target you specified is not available. Available targets are:\ + \n{:?}\n\ + as well as `all` (testing on all of the above)", + targets + ); + process::exit(1); + } + + Ok(()) +} + +fn run_test(target: &str, examples: &[String]) -> anyhow::Result<()> { + arm_example(&CargoCommand::BuildAll { + target, + features: None, + mode: BuildMode::Release, + })?; + + for example in examples { + let cmd = CargoCommand::Run { + example, + target, + features: None, + mode: BuildMode::Release, + }; + + arm_example(&cmd)?; + } + + Ok(()) +} + +// run example binary `example` +fn arm_example(command: &CargoCommand) -> anyhow::Result<()> { + match *command { + CargoCommand::Run { example, .. } => { + let run_file = format!("{}.run", example); + let expected_output_file = ["ci", "expected", &run_file] + .iter() + .collect::() + .into_os_string() + .into_string() + .map_err(|e| TestRunError::PathConversionError(e))?; + + // command is either build or run + let cargo_run_result = run_command(&command)?; + println!("{}", cargo_run_result.output); + + match &command { + CargoCommand::Run { .. } => { + run_successful(&cargo_run_result, expected_output_file)?; + } + _ => (), + } + + Ok(()) + } + CargoCommand::BuildAll { .. } => { + // command is either build or run + let cargo_run_result = run_command(&command)?; + println!("{}", cargo_run_result.output); + + Ok(()) + } // _ => Err(anyhow::Error::new(TestRunError::IncompatibleCommand)), + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index e28b55d..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,4 +0,0 @@ -[toolchain] -channel = "nightly" -components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] -targets = [ "thumbv6m-none-eabi", "thumbv7m-none-eabi" ] diff --git a/src/export.rs b/src/export.rs deleted file mode 100644 index cdca972..0000000 --- a/src/export.rs +++ /dev/null @@ -1,324 +0,0 @@ -pub use bare_metal::CriticalSection; -pub use cortex_m::{ - asm::nop, - asm::wfi, - interrupt, - peripheral::{scb::SystemHandler, DWT, NVIC, SCB, SYST}, - Peripherals, -}; -//pub use portable_atomic as atomic; -pub use atomic_polyfill as atomic; - -pub mod executor; - -/// Mask is used to store interrupt masks on systems without a BASEPRI register (M0, M0+, M23). -/// It needs to be large enough to cover all the relevant interrupts in use. -/// For M0/M0+ there are only 32 interrupts so we only need one u32 value. -/// For M23 there can be as many as 480 interrupts. -/// Rather than providing space for all possible interrupts, we just detect the highest interrupt in -/// use at compile time and allocate enough u32 chunks to cover them. -#[derive(Copy, Clone)] -pub struct Mask([u32; M]); - -impl core::ops::BitOrAssign for Mask { - fn bitor_assign(&mut self, rhs: Self) { - for i in 0..M { - self.0[i] |= rhs.0[i]; - } - } -} - -#[cfg(not(have_basepri))] -impl Mask { - /// Set a bit inside a Mask. - const fn set_bit(mut self, bit: u32) -> Self { - let block = bit / 32; - - if block as usize >= M { - panic!("Generating masks for thumbv6/thumbv8m.base failed! Are you compiling for thumbv6 on an thumbv7 MCU or using an unsupported thumbv8m.base MCU?"); - } - - let offset = bit - (block * 32); - self.0[block as usize] |= 1 << offset; - self - } -} - -#[cfg(have_basepri)] -use cortex_m::register::basepri; - -#[cfg(have_basepri)] -#[inline(always)] -pub fn run(priority: u8, f: F) -where - F: FnOnce(), -{ - if priority == 1 { - // If the priority of this interrupt is `1` then BASEPRI can only be `0` - f(); - unsafe { basepri::write(0) } - } else { - let initial = basepri::read(); - f(); - unsafe { basepri::write(initial) } - } -} - -#[cfg(not(have_basepri))] -#[inline(always)] -pub fn run(_priority: u8, f: F) -where - F: FnOnce(), -{ - f(); -} - -/// Const helper to check architecture -pub const fn have_basepri() -> bool { - #[cfg(have_basepri)] - { - true - } - - #[cfg(not(have_basepri))] - { - false - } -} - -#[inline(always)] -pub fn assert_send() -where - T: Send, -{ -} - -#[inline(always)] -pub fn assert_sync() -where - T: Sync, -{ -} - -/// Lock implementation using BASEPRI and global Critical Section (CS) -/// -/// # Safety -/// -/// The system ceiling is raised from current to ceiling -/// by either -/// - raising the BASEPRI to the ceiling value, or -/// - disable all interrupts in case we want to -/// mask interrupts with maximum priority -/// -/// Dereferencing a raw pointer inside CS -/// -/// The priority.set/priority.get can safely be outside the CS -/// as being a context local cell (not affected by preemptions). -/// It is merely used in order to omit masking in case current -/// priority is current priority >= ceiling. -/// -/// Lock Efficiency: -/// Experiments validate (sub)-zero cost for CS implementation -/// (Sub)-zero as: -/// - Either zero OH (lock optimized out), or -/// - Amounting to an optimal assembly implementation -/// - The BASEPRI value is folded to a constant at compile time -/// - CS entry, single assembly instruction to write BASEPRI -/// - CS exit, single assembly instruction to write BASEPRI -/// - priority.set/get optimized out (their effect not) -/// - On par or better than any handwritten implementation of SRP -/// -/// Limitations: -/// The current implementation reads/writes BASEPRI once -/// even in some edge cases where this may be omitted. -/// Total OH of per task is max 2 clock cycles, negligible in practice -/// but can in theory be fixed. -/// -#[cfg(have_basepri)] -#[inline(always)] -pub unsafe fn lock( - ptr: *mut T, - ceiling: u8, - nvic_prio_bits: u8, - _mask: &[Mask; 3], - f: impl FnOnce(&mut T) -> R, -) -> R { - if ceiling == (1 << nvic_prio_bits) { - let r = interrupt::free(|_| f(&mut *ptr)); - r - } else { - let current = basepri::read(); - basepri::write(logical2hw(ceiling, nvic_prio_bits)); - let r = f(&mut *ptr); - basepri::write(current); - r - } -} - -/// Lock implementation using interrupt masking -/// -/// # Safety -/// -/// The system ceiling is raised from current to ceiling -/// by computing a 32 bit `mask` (1 bit per interrupt) -/// 1: ceiling >= priority > current -/// 0: else -/// -/// On CS entry, `clear_enable_mask(mask)` disables interrupts -/// On CS exit, `set_enable_mask(mask)` re-enables interrupts -/// -/// The priority.set/priority.get can safely be outside the CS -/// as being a context local cell (not affected by preemptions). -/// It is merely used in order to omit masking in case -/// current priority >= ceiling. -/// -/// Dereferencing a raw pointer is done safely inside the CS -/// -/// Lock Efficiency: -/// Early experiments validate (sub)-zero cost for CS implementation -/// (Sub)-zero as: -/// - Either zero OH (lock optimized out), or -/// - Amounting to an optimal assembly implementation -/// - if ceiling == (1 << nvic_prio_bits) -/// - we execute the closure in a global critical section (interrupt free) -/// - CS entry cost, single write to core register -/// - CS exit cost, single write to core register -/// else -/// - The `mask` value is folded to a constant at compile time -/// - CS entry, single write of the 32 bit `mask` to the `icer` register -/// - CS exit, single write of the 32 bit `mask` to the `iser` register -/// - priority.set/get optimized out (their effect not) -/// - On par or better than any hand written implementation of SRP -/// -/// Limitations: -/// Current implementation does not allow for tasks with shared resources -/// to be bound to exception handlers, as these cannot be masked in HW. -/// -/// Possible solutions: -/// - Mask exceptions by global critical sections (interrupt::free) -/// - Temporary lower exception priority -/// -/// These possible solutions are set goals for future work -#[cfg(not(have_basepri))] -#[inline(always)] -pub unsafe fn lock( - ptr: *mut T, - ceiling: u8, - _nvic_prio_bits: u8, - masks: &[Mask; 3], - f: impl FnOnce(&mut T) -> R, -) -> R { - if ceiling >= 4 { - // safe to manipulate outside critical section - // execute closure under protection of raised system ceiling - - // safe to manipulate outside critical section - interrupt::free(|_| f(&mut *ptr)) - } else { - // safe to manipulate outside critical section - let mask = compute_mask(0, ceiling, masks); - clear_enable_mask(mask); - - // execute closure under protection of raised system ceiling - let r = f(&mut *ptr); - - set_enable_mask(mask); - - // safe to manipulate outside critical section - r - } -} - -#[cfg(not(have_basepri))] -#[inline(always)] -fn compute_mask(from_prio: u8, to_prio: u8, masks: &[Mask; 3]) -> Mask { - let mut res = Mask([0; M]); - masks[from_prio as usize..to_prio as usize] - .iter() - .for_each(|m| res |= *m); - res -} - -// enables interrupts -#[cfg(not(have_basepri))] -#[inline(always)] -unsafe fn set_enable_mask(mask: Mask) { - for i in 0..M { - // This check should involve compile time constants and be optimized out. - if mask.0[i] != 0 { - (*NVIC::PTR).iser[i].write(mask.0[i]); - } - } -} - -// disables interrupts -#[cfg(not(have_basepri))] -#[inline(always)] -unsafe fn clear_enable_mask(mask: Mask) { - for i in 0..M { - // This check should involve compile time constants and be optimized out. - if mask.0[i] != 0 { - (*NVIC::PTR).icer[i].write(mask.0[i]); - } - } -} - -#[inline] -#[must_use] -pub fn logical2hw(logical: u8, nvic_prio_bits: u8) -> u8 { - ((1 << nvic_prio_bits) - logical) << (8 - nvic_prio_bits) -} - -#[cfg(have_basepri)] -pub const fn create_mask(_: [u32; N]) -> Mask { - Mask([0; M]) -} - -#[cfg(not(have_basepri))] -pub const fn create_mask(list_of_shifts: [u32; N]) -> Mask { - let mut mask = Mask([0; M]); - let mut i = 0; - - while i < N { - let shift = list_of_shifts[i]; - i += 1; - mask = mask.set_bit(shift); - } - - mask -} - -#[cfg(have_basepri)] -pub const fn compute_mask_chunks(_: [u32; L]) -> usize { - 0 -} - -/// Compute the number of u32 chunks needed to store the Mask value. -/// On M0, M0+ this should always end up being 1. -/// On M23 we will pick a number that allows us to store the highest index used by the code. -/// This means the amount of overhead will vary based on the actually interrupts used by the code. -#[cfg(not(have_basepri))] -pub const fn compute_mask_chunks(ids: [u32; L]) -> usize { - let mut max: usize = 0; - let mut i = 0; - - while i < L { - let id = ids[i] as usize; - i += 1; - - if id > max { - max = id; - } - } - (max + 32) / 32 -} - -#[cfg(have_basepri)] -pub const fn no_basepri_panic() { - // For non-v6 all is fine -} - -#[cfg(not(have_basepri))] -pub const fn no_basepri_panic() { - panic!("Exceptions with shared resources are not allowed when compiling for thumbv6 or thumbv8m.base. Use local resources or `#[lock_free]` shared resources"); -} diff --git a/src/export/executor.rs b/src/export/executor.rs deleted file mode 100644 index e64cc43..0000000 --- a/src/export/executor.rs +++ /dev/null @@ -1,110 +0,0 @@ -use super::atomic::{AtomicBool, Ordering}; -use core::{ - cell::UnsafeCell, - future::Future, - mem::{self, MaybeUninit}, - pin::Pin, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -}; - -static WAKER_VTABLE: RawWakerVTable = - RawWakerVTable::new(waker_clone, waker_wake, waker_wake, waker_drop); - -unsafe fn waker_clone(p: *const ()) -> RawWaker { - RawWaker::new(p, &WAKER_VTABLE) -} - -unsafe fn waker_wake(p: *const ()) { - // The only thing we need from a waker is the function to call to pend the async - // dispatcher. - let f: fn() = mem::transmute(p); - f(); -} - -unsafe fn waker_drop(_: *const ()) { - // nop -} - -//============ -// AsyncTaskExecutor - -/// Executor for an async task. -pub struct AsyncTaskExecutor { - // `task` is protected by the `running` flag. - task: UnsafeCell>, - running: AtomicBool, - pending: AtomicBool, -} - -unsafe impl Sync for AsyncTaskExecutor {} - -impl AsyncTaskExecutor { - /// Create a new executor. - #[inline(always)] - pub const fn new() -> Self { - Self { - task: UnsafeCell::new(MaybeUninit::uninit()), - running: AtomicBool::new(false), - pending: AtomicBool::new(false), - } - } - - /// Check if there is an active task in the executor. - #[inline(always)] - pub fn is_running(&self) -> bool { - self.running.load(Ordering::Relaxed) - } - - /// Checks if a waker has pended the executor and simultaneously clears the flag. - #[inline(always)] - fn check_and_clear_pending(&self) -> bool { - // Ordering::Acquire to enforce that update of task is visible to poll - self.pending - .compare_exchange(true, false, Ordering::Acquire, Ordering::Relaxed) - .is_ok() - } - - // Used by wakers to indicate that the executor needs to run. - #[inline(always)] - pub fn set_pending(&self) { - self.pending.store(true, Ordering::Release); - } - - /// Spawn a future - #[inline(always)] - pub fn spawn(&self, future: impl Fn() -> F) -> bool { - // Try to reserve the executor for a future. - if self - .running - .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed) - .is_ok() - { - // This unsafe is protected by `running` being false and the atomic setting it to true. - unsafe { - self.task.get().write(MaybeUninit::new(future())); - } - self.set_pending(); - - true - } else { - false - } - } - - /// Poll the future in the executor. - #[inline(always)] - pub fn poll(&self, wake: fn()) { - if self.is_running() && self.check_and_clear_pending() { - let waker = unsafe { Waker::from_raw(RawWaker::new(wake as *const (), &WAKER_VTABLE)) }; - let mut cx = Context::from_waker(&waker); - let future = unsafe { Pin::new_unchecked(&mut *(self.task.get() as *mut F)) }; - - match future.poll(&mut cx) { - Poll::Ready(_) => { - self.running.store(false, Ordering::Release); - } - Poll::Pending => {} - } - } - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index e8b8140..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,121 +0,0 @@ -//! Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers. -//! -//! **IMPORTANT**: This crate is published as [`cortex-m-rtic`] on crates.io but the name of the -//! library is `rtic`. -//! -//! [`cortex-m-rtic`]: https://crates.io/crates/cortex-m-rtic -//! -//! The user level documentation can be found [here]. -//! -//! [here]: https://rtic.rs -//! -//! Don't forget to check the documentation of the `#[app]` attribute (listed under the reexports -//! section), which is the main component of the framework. -//! -//! # Minimum Supported Rust Version (MSRV) -//! -//! This crate is compiled and tested with the latest toolchain (rolling) as of the release date. -//! If you run into compilation errors, try the latest stable release of the rust toolchain. -//! -//! # Semantic Versioning -//! -//! Like the Rust project, this crate adheres to [SemVer]: breaking changes in the API and semantics -//! require a *semver bump* (since 1.0.0 a new major version release), with the exception of breaking changes -//! that fix soundness issues -- those are considered bug fixes and can be landed in a new patch -//! release. -//! -//! [SemVer]: https://semver.org/spec/v2.0.0.html - -#![deny(missing_docs)] -#![deny(rust_2021_compatibility)] -#![deny(rust_2018_compatibility)] -#![deny(rust_2018_idioms)] -#![no_std] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg", - html_favicon_url = "https://raw.githubusercontent.com/rtic-rs/cortex-m-rtic/master/book/en/src/RTIC.svg" -)] -//deny_warnings_placeholder_for_ci -#![allow(clippy::inline_always)] - -use cortex_m::{interrupt::InterruptNumber, peripheral::NVIC}; -pub use rtic_core::{prelude as mutex_prelude, Exclusive, Mutex}; -pub use rtic_macros::app; -pub use rtic_monotonic::{self, Monotonic}; - -/// module `mutex::prelude` provides `Mutex` and multi-lock variants. Recommended over `mutex_prelude` -pub mod mutex { - pub use rtic_core::prelude; - pub use rtic_core::Mutex; -} - -#[doc(hidden)] -pub mod export; - -/// Sets the given `interrupt` as pending -/// -/// This is a convenience function around -/// [`NVIC::pend`](../cortex_m/peripheral/struct.NVIC.html#method.pend) -pub fn pend(interrupt: I) -where - I: InterruptNumber, -{ - NVIC::pend(interrupt); -} - -use core::cell::UnsafeCell; - -/// Internal replacement for `static mut T` -/// -/// Used to represent RTIC Resources -/// -/// Soundness: -/// 1) Unsafe API for internal use only -/// 2) ``get_mut(&self) -> *mut T`` -/// returns a raw mutable pointer to the inner T -/// casting to &mut T is under control of RTIC -/// RTIC ensures &mut T to be unique under Rust aliasing rules. -/// -/// Implementation uses the underlying ``UnsafeCell`` -/// self.0.get() -> *mut T -/// -/// 3) get(&self) -> *const T -/// returns a raw immutable (const) pointer to the inner T -/// casting to &T is under control of RTIC -/// RTIC ensures &T to be shared under Rust aliasing rules. -/// -/// Implementation uses the underlying ``UnsafeCell`` -/// self.0.get() -> *mut T, demoted to *const T -/// -#[repr(transparent)] -pub struct RacyCell(UnsafeCell); - -impl RacyCell { - /// Create a ``RacyCell`` - #[inline(always)] - pub const fn new(value: T) -> Self { - RacyCell(UnsafeCell::new(value)) - } - - /// Get `*mut T` - /// - /// # Safety - /// - /// See documentation notes for [`RacyCell`] - #[inline(always)] - pub unsafe fn get_mut(&self) -> *mut T { - self.0.get() - } - - /// Get `*const T` - /// - /// # Safety - /// - /// See documentation notes for [`RacyCell`] - #[inline(always)] - pub unsafe fn get(&self) -> *const T { - self.0.get() - } -} - -unsafe impl Sync for RacyCell {} diff --git a/tests/tests.rs b/tests/tests.rs deleted file mode 100644 index 9fb88a1..0000000 --- a/tests/tests.rs +++ /dev/null @@ -1,7 +0,0 @@ -use trybuild::TestCases; - -#[test] -fn ui() { - let t = TestCases::new(); - t.compile_fail("ui/*.rs"); -} diff --git a/ui/exception-invalid.rs b/ui/exception-invalid.rs deleted file mode 100644 index 4f8e943..0000000 --- a/ui/exception-invalid.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task(binds = NonMaskableInt)] - fn nmi(_: nmi::Context) {} -} diff --git a/ui/exception-invalid.stderr b/ui/exception-invalid.stderr deleted file mode 100644 index 3212368..0000000 --- a/ui/exception-invalid.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: only exceptions with configurable priority can be used as hardware tasks - --> $DIR/exception-invalid.rs:17:8 - | -17 | fn nmi(_: nmi::Context) {} - | ^^^ diff --git a/ui/extern-interrupt-not-enough.rs b/ui/extern-interrupt-not-enough.rs deleted file mode 100644 index 94c8ee1..0000000 --- a/ui/extern-interrupt-not-enough.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task] - async fn a(_: a::Context) {} -} diff --git a/ui/extern-interrupt-not-enough.stderr b/ui/extern-interrupt-not-enough.stderr deleted file mode 100644 index e6c01b9..0000000 --- a/ui/extern-interrupt-not-enough.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: not enough interrupts to dispatch all software tasks (need: 1; given: 0) - --> ui/extern-interrupt-not-enough.rs:17:14 - | -17 | async fn a(_: a::Context) {} - | ^ diff --git a/ui/extern-interrupt-used.rs b/ui/extern-interrupt-used.rs deleted file mode 100644 index 42de4c0..0000000 --- a/ui/extern-interrupt-used.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965, dispatchers = [UART0])] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task(binds = UART0)] - fn a(_: a::Context) {} -} diff --git a/ui/extern-interrupt-used.stderr b/ui/extern-interrupt-used.stderr deleted file mode 100644 index 7565739..0000000 --- a/ui/extern-interrupt-used.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: dispatcher interrupts can't be used as hardware tasks - --> $DIR/extern-interrupt-used.rs:16:20 - | -16 | #[task(binds = UART0)] - | ^^^^^ diff --git a/ui/task-priority-too-high.rs b/ui/task-priority-too-high.rs deleted file mode 100644 index 44e4a25..0000000 --- a/ui/task-priority-too-high.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965)] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } - - #[task(binds = GPIOA, priority = 1)] - fn gpioa(_: gpioa::Context) {} - - #[task(binds = GPIOB, priority = 2)] - fn gpiob(_: gpiob::Context) {} - - #[task(binds = GPIOC, priority = 3)] - fn gpioc(_: gpioc::Context) {} - - #[task(binds = GPIOD, priority = 4)] - fn gpiod(_: gpiod::Context) {} - - #[task(binds = GPIOE, priority = 5)] - fn gpioe(_: gpioe::Context) {} - - #[task(binds = UART0, priority = 6)] - fn uart0(_: uart0::Context) {} - - #[task(binds = UART1, priority = 7)] - fn uart1(_: uart1::Context) {} - - // OK, this is the maximum priority supported by the device - #[task(binds = SSI0, priority = 8)] - fn ssi0(_: ssi0::Context) {} - - // this value is too high! - #[task(binds = I2C0, priority = 9)] - fn i2c0(_: i2c0::Context) {} -} diff --git a/ui/task-priority-too-high.stderr b/ui/task-priority-too-high.stderr deleted file mode 100644 index 1256377..0000000 --- a/ui/task-priority-too-high.stderr +++ /dev/null @@ -1,15 +0,0 @@ -warning: unused variable: `cx` - --> ui/task-priority-too-high.rs:12:13 - | -12 | fn init(cx: init::Context) -> (Shared, Local) { - | ^^ help: if this is intentional, prefix it with an underscore: `_cx` - | - = note: `#[warn(unused_variables)]` on by default - -error[E0080]: evaluation of constant value failed - --> ui/task-priority-too-high.rs:3:1 - | -3 | #[rtic::app(device = lm3s6965)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Maximum priority used by interrupt vector 'I2C0' is more than supported by hardware', $DIR/ui/task-priority-too-high.rs:3:1 - | - = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `::core::panic` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/ui/unknown-interrupt.rs b/ui/unknown-interrupt.rs deleted file mode 100644 index 3c6c69f..0000000 --- a/ui/unknown-interrupt.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![no_main] - -#[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] -mod app { - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(cx: init::Context) -> (Shared, Local) { - (Shared {}, Local {}) - } -} diff --git a/ui/unknown-interrupt.stderr b/ui/unknown-interrupt.stderr deleted file mode 100644 index c7d3269..0000000 --- a/ui/unknown-interrupt.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error[E0599]: no variant or associated item named `UnknownInterrupt` found for enum `Interrupt` in the current scope - --> $DIR/unknown-interrupt.rs:3:47 - | -3 | #[rtic::app(device = lm3s6965, dispatchers = [UnknownInterrupt])] - | ^^^^^^^^^^^^^^^^ variant or associated item not found in `Interrupt` diff --git a/ui/v6m-interrupt-not-enough.rs_no b/ui/v6m-interrupt-not-enough.rs_no deleted file mode 100644 index 3fbf3cf..0000000 --- a/ui/v6m-interrupt-not-enough.rs_no +++ /dev/null @@ -1,54 +0,0 @@ -//! v6m-interrupt-not-enough.rs_no (not run atm) -//! -//! Expected behavior: -//! should pass -//! > cargo build --example m0_perf_err --target thumbv7m-none-eabi --release -//! -//! should fail -//! > cargo build --example m0_perf_err --target thumbv6m-none-eabi --release -//! Compiling cortex-m-rtic v1.0.0 (/home/pln/rust/rtic/cortex-m-rtic) -//! error[E0308]: mismatched types -//! --> examples/m0_perf_err.rs:25:1 -//! | -//! 25 | #[rtic::app(device = lm3s6965)] -//! | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 4 elements, found one with 5 elements -//! | -//! = note: this error originates in the attribute macro `rtic::app` (in Nightly builds, run with -Z macro-backtrace for more info) - -#![deny(unsafe_code)] -#![deny(warnings)] -#![no_main] -#![no_std] - -use panic_semihosting as _; - -#[rtic::app(device = lm3s6965)] -mod app { - - use cortex_m_semihosting::debug; - - #[shared] - struct Shared {} - - #[local] - struct Local {} - - #[init] - fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { - (Shared {}, Local {}, init::Monotonics()) - } - - #[inline(never)] - #[idle] - fn idle(_cx: idle::Context) -> ! { - debug::exit(debug::EXIT_SUCCESS); // Exit QEMU simulator - - loop { - cortex_m::asm::nop(); - } - } - - // priority to high for v6m - #[task(binds = GPIOA, priority = 5)] - fn t0(_cx: t0::Context) {} -} diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml deleted file mode 100644 index f1c468e..0000000 --- a/xtask/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "xtask" -version = "0.1.0" -edition = "2018" - -[dependencies] -anyhow = "1.0.43" -os_pipe = "1.1.2" -structopt = "0.3.22" diff --git a/xtask/src/build.rs b/xtask/src/build.rs deleted file mode 100644 index 148a9fd..0000000 --- a/xtask/src/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::{fs, path::Path}; - -const HEX_BUILD_ROOT: &str = "ci/builds"; - -/// make sure we're starting with a clean,but existing slate -pub fn init_build_dir() -> anyhow::Result<()> { - if Path::new(HEX_BUILD_ROOT).exists() { - fs::remove_dir_all(HEX_BUILD_ROOT) - .map_err(|_| anyhow::anyhow!("Could not clear out directory: {}", HEX_BUILD_ROOT))?; - } - fs::create_dir_all(HEX_BUILD_ROOT) - .map_err(|_| anyhow::anyhow!("Could not create directory: {}", HEX_BUILD_ROOT)) -} diff --git a/xtask/src/command.rs b/xtask/src/command.rs deleted file mode 100644 index 4e90369..0000000 --- a/xtask/src/command.rs +++ /dev/null @@ -1,172 +0,0 @@ -use crate::{RunResult, TestRunError}; -use core::fmt; -use os_pipe::pipe; -use std::{fs::File, io::Read, process::Command}; - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum BuildMode { - Release, - Debug, -} - -#[derive(Debug)] -pub enum CargoCommand<'a> { - Run { - example: &'a str, - target: &'a str, - features: Option<&'a str>, - mode: BuildMode, - }, - BuildAll { - target: &'a str, - features: Option<&'a str>, - mode: BuildMode, - }, - // Size { - // example_paths: Vec<&'a Path>, - // }, - // Clean, -} - -impl<'a> CargoCommand<'a> { - fn name(&self) -> &str { - match self { - CargoCommand::Run { .. } => "run", - // CargoCommand::Size { example_paths: _ } => "rust-size", - CargoCommand::BuildAll { .. } => "build", - } - } - - pub fn args(&self) -> Vec<&str> { - match self { - CargoCommand::Run { - example, - target, - features, - mode, - } => { - let mut args = vec![ - "+nightly", - self.name(), - "--example", - example, - "--target", - target, - "--features", - "test-critical-section", - ]; - - if let Some(feature_name) = features { - args.extend_from_slice(&["--features", feature_name]); - } - if let Some(flag) = mode.to_flag() { - args.push(flag); - } - args - } - CargoCommand::BuildAll { - target, - features, - mode, - } => { - let mut args = vec![ - "+nightly", - self.name(), - "--examples", - "--target", - target, - "--features", - "test-critical-section", - ]; - - if let Some(feature_name) = features { - args.extend_from_slice(&["--features", feature_name]); - } - if let Some(flag) = mode.to_flag() { - args.push(flag); - } - args - } // CargoCommand::Size { example_paths } => { - // example_paths.iter().map(|p| p.to_str().unwrap()).collect() - // } - } - } - - pub fn command(&self) -> &str { - match self { - // we need to cheat a little here: - // `cargo size` can't be ran on multiple files, so we're using `rust-size` instead – - // which isn't a command that starts wizh `cargo`. So we're sneakily swapping them out :) - // CargoCommand::Size { .. } => "rust-size", - _ => "cargo", - } - } -} - -impl BuildMode { - pub fn to_flag(&self) -> Option<&str> { - match self { - BuildMode::Release => Some("--release"), - BuildMode::Debug => None, - } - } -} - -impl fmt::Display for BuildMode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let cmd = match self { - BuildMode::Release => "release", - BuildMode::Debug => "debug", - }; - - write!(f, "{}", cmd) - } -} - -pub fn run_command(command: &CargoCommand) -> anyhow::Result { - let (mut reader, writer) = pipe()?; - println!("👟 {} {}", command.command(), command.args().join(" ")); - - let mut handle = Command::new(command.command()) - .args(command.args()) - .stdout(writer) - .spawn()?; - - // retrieve output and clean up - let mut output = String::new(); - reader.read_to_string(&mut output)?; - let exit_status = handle.wait()?; - - Ok(RunResult { - exit_status, - output, - }) -} - -/// Check if `run` was successful. -/// returns Ok in case the run went as expected, -/// Err otherwise -pub fn run_successful(run: &RunResult, expected_output_file: String) -> Result<(), TestRunError> { - let mut file_handle = - File::open(expected_output_file.clone()).map_err(|_| TestRunError::FileError { - file: expected_output_file.clone(), - })?; - let mut expected_output = String::new(); - file_handle - .read_to_string(&mut expected_output) - .map_err(|_| TestRunError::FileError { - file: expected_output_file.clone(), - })?; - - if expected_output != run.output { - Err(TestRunError::FileCmpError { - expected: expected_output.clone(), - got: run.output.clone(), - }) - } else if !run.exit_status.success() { - Err(TestRunError::CommandError(run.clone())) - } else { - Ok(()) - } -} diff --git a/xtask/src/main.rs b/xtask/src/main.rs deleted file mode 100644 index 7eada91..0000000 --- a/xtask/src/main.rs +++ /dev/null @@ -1,176 +0,0 @@ -mod build; -mod command; - -use anyhow::bail; -use core::fmt; -use std::{ - error::Error, - ffi::OsString, - path::{Path, PathBuf}, - process, - process::ExitStatus, - str, -}; -use structopt::StructOpt; - -use crate::{ - build::init_build_dir, - command::{run_command, run_successful, BuildMode, CargoCommand}, -}; - -const ARMV6M: &str = "thumbv6m-none-eabi"; -const ARMV7M: &str = "thumbv7m-none-eabi"; - -#[derive(Debug, StructOpt)] -struct Options { - #[structopt(short, long)] - target: String, -} - -#[derive(Debug, Clone)] -pub struct RunResult { - exit_status: ExitStatus, - output: String, -} - -#[derive(Debug)] -pub enum TestRunError { - FileCmpError { expected: String, got: String }, - FileError { file: String }, - PathConversionError(OsString), - CommandError(RunResult), - IncompatibleCommand, -} - -impl fmt::Display for TestRunError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TestRunError::FileCmpError { expected, got } => { - writeln!(f, "Differing output in files.")?; - writeln!(f, "")?; - writeln!(f, "Expected:")?; - writeln!(f, "{}", expected)?; - writeln!(f, "")?; - writeln!(f, "Got:")?; - write!(f, "{}", got) - } - TestRunError::FileError { file } => { - write!(f, "File error on: {}", file) - } - TestRunError::CommandError(e) => { - write!( - f, - "Command failed with exit status {}: {}", - e.exit_status, e.output - ) - } - TestRunError::PathConversionError(p) => { - write!(f, "Can't convert path from `OsString` to `String`: {:?}", p) - } - TestRunError::IncompatibleCommand => { - write!(f, "Can't run that command in this context") - } - } - } -} - -impl Error for TestRunError {} - -fn main() -> anyhow::Result<()> { - // if there's an `xtask` folder, we're *probably* at the root of this repo (we can't just - // check the name of `env::current_dir()` because people might clone it into a different name) - let probably_running_from_repo_root = Path::new("./xtask").exists(); - if probably_running_from_repo_root == false { - bail!("xtasks can only be executed from the root of the `cortex-m-rtic` repository"); - } - - let targets = [ARMV7M, ARMV6M]; - - let examples: Vec<_> = std::fs::read_dir("./examples")? - .filter_map(|p| p.ok()) - .map(|p| p.path()) - .filter(|p| p.display().to_string().ends_with(".rs")) - .map(|path| path.file_stem().unwrap().to_str().unwrap().to_string()) - .collect(); - - println!("examples: {examples:?}"); - - let opts = Options::from_args(); - let target = &opts.target; - - init_build_dir()?; - - if target == "all" { - for t in targets { - run_test(t, &examples)?; - } - } else if targets.contains(&target.as_str()) { - run_test(&target, &examples)?; - } else { - eprintln!( - "The target you specified is not available. Available targets are:\ - \n{:?}\n\ - as well as `all` (testing on all of the above)", - targets - ); - process::exit(1); - } - - Ok(()) -} - -fn run_test(target: &str, examples: &[String]) -> anyhow::Result<()> { - arm_example(&CargoCommand::BuildAll { - target, - features: None, - mode: BuildMode::Release, - })?; - - for example in examples { - let cmd = CargoCommand::Run { - example, - target, - features: None, - mode: BuildMode::Release, - }; - - arm_example(&cmd)?; - } - - Ok(()) -} - -// run example binary `example` -fn arm_example(command: &CargoCommand) -> anyhow::Result<()> { - match *command { - CargoCommand::Run { example, .. } => { - let run_file = format!("{}.run", example); - let expected_output_file = ["ci", "expected", &run_file] - .iter() - .collect::() - .into_os_string() - .into_string() - .map_err(|e| TestRunError::PathConversionError(e))?; - - // command is either build or run - let cargo_run_result = run_command(&command)?; - println!("{}", cargo_run_result.output); - - match &command { - CargoCommand::Run { .. } => { - run_successful(&cargo_run_result, expected_output_file)?; - } - _ => (), - } - - Ok(()) - } - CargoCommand::BuildAll { .. } => { - // command is either build or run - let cargo_run_result = run_command(&command)?; - println!("{}", cargo_run_result.output); - - Ok(()) - } // _ => Err(anyhow::Error::new(TestRunError::IncompatibleCommand)), - } -} -- cgit v1.2.3